Reflexión (C++/CLI)
La reflexión permite inspeccionar tipos de datos conocidos en tiempo de ejecución. La reflexión permite enumerar los tipos de datos en un ensamblado especificado y detectar los miembros de una clase o de un tipo de valor dados. Esto es cierto independientemente de si el tipo se conoce o se hace referencia a él en tiempo de compilación. Esto hace que la reflexión sea una característica útil para las herramientas de desarrollo y de administración de código.
Tenga en cuenta que el nombre de ensamblado proporcionado es el nombre seguro (vea Crear y usar ensambladosde Strong-Named ), que incluye la versión del ensamblado, la referencia cultural y la información de firma. Observe además que puede recuperarse el nombre del espacio de nombres en el que se define el tipo de datos, al igual que el nombre de la clase base.
La manera más común de acceder a las características de reflexión es a través del método GetType. System.Objectproporciona este método, del que derivan todas las clases recolectoras de elementos no utilizados.
Nota
La reflexión en .exe compilada con el compilador de Microsoft C++ solo se permite si el .exe se compila con las opciones del compilador /clr:pureo /clr:safe. Las opciones del compilador /clr:pure y /clr:safe están en desuso en Visual Studio 2015 y no están disponibles en Visual Studio 2017. Consulte /clr (compilación de Common Language Runtime) para obtener más información.
Para obtener más información, vea System.Reflection.
Ejemplo: GetType
El GetType método devuelve un puntero a un objeto de clase GetType que describe el tipo en el que se basa el objeto. (El objeto Type no contiene ninguna información específica de instancia). Un elemento de esta clase es el nombre completo del tipo, que puede mostrarse de la siguiente forma:
Observe que el nombre de tipo incluye el ámbito completo en el que se define el tipo, incluido el espacio de nombres, y se muestra en la sintaxis de .NET con un punto como operador de resolución de ámbito.
// vcpp_reflection.cpp
// compile with: /clr
using namespace System;
int main() {
String ^ s = "sample string";
Console::WriteLine("full type name of '{0}' is '{1}'", s, s->GetType());
}
full type name of 'sample string' is 'System.String'
Ejemplo: tipos de valor con cuadro
Los tipos de valor pueden utilizarse también con la función GetType pero se les debe aplicar primero una conversión boxing.
// vcpp_reflection_2.cpp
// compile with: /clr
using namespace System;
int main() {
Int32 i = 100;
Object ^ o = i;
Console::WriteLine("type of i = '{0}'", o->GetType());
}
type of i = 'System.Int32'
Ejemplo: typeid
Al igual que con el método , el operador typeid devuelve un puntero a un objeto Type, por lo que este código indica el nombre GetType de tipo System.Int32. GetType Mostrar los nombres de tipo es la característica de reflexión más básica, pero una técnica posiblemente más útil es inspeccionar o detectar los valores válidos para los tipos enumerados. Esto se puede hacer mediante la función estática Enum::GetNames, que devuelve una matriz de cadenas, cada una de las cuales contiene un valor de enumeración en formato de texto. En el ejemplo siguiente se recupera una matriz de cadenas que describe los valores de enumeración de valores para la enumeración Options (CLR) y los muestra en un bucle .
Si se agrega una cuarta opción a la enumeración Options, este código mostrará la nueva opción sin recompilación, incluso si la enumeración se define en un ensamblado independiente.
// vcpp_reflection_3.cpp
// compile with: /clr
using namespace System;
enum class Options { // not a native enum
Option1, Option2, Option3
};
int main() {
array<String^>^ names = Enum::GetNames(Options::typeid);
Console::WriteLine("there are {0} options in enum '{1}'",
names->Length, Options::typeid);
for (int i = 0 ; i < names->Length ; i++)
Console::WriteLine("{0}: {1}", i, names[i]);
Options o = Options::Option2;
Console::WriteLine("value of 'o' is {0}", o);
}
there are 3 options in enum 'Options'
0: Option1
1: Option2
2: Option3
value of 'o' is Option2
Ejemplo: Propiedades y miembros GetType
El objeto GetType admite un número de miembros y propiedades que pueden utilizarse para examinar un tipo. Este código recupera y muestra una parte de esta información:
// vcpp_reflection_4.cpp
// compile with: /clr
using namespace System;
int main() {
Console::WriteLine("type information for 'String':");
Type ^ t = String::typeid;
String ^ assemblyName = t->Assembly->FullName;
Console::WriteLine("assembly name: {0}", assemblyName);
String ^ nameSpace = t->Namespace;
Console::WriteLine("namespace: {0}", nameSpace);
String ^ baseType = t->BaseType->FullName;
Console::WriteLine("base type: {0}", baseType);
bool isArray = t->IsArray;
Console::WriteLine("is array: {0}", isArray);
bool isClass = t->IsClass;
Console::WriteLine("is class: {0}", isClass);
}
type information for 'String':
assembly name: mscorlib, Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
namespace: System
base type: System.Object
is array: False
is class: True
Ejemplo: enumeración de tipos
La reflexión también permite la enumeración de tipos dentro de un ensamblado y de miembros dentro de las clases. Para demostrar esta característica, defina una clase simple:
// vcpp_reflection_5.cpp
// compile with: /clr /LD
using namespace System;
public ref class TestClass {
int m_i;
public:
TestClass() {}
void SimpleTestMember1() {}
String ^ SimpleMember2(String ^ s) { return s; }
int TestMember(int i) { return i; }
property int Member {
int get() { return m_i; }
void set(int i) { m_i = i; }
}
};
Ejemplo: inspección de ensamblados
Si el código anterior se compila en un archivo DLL denominado vcpp_reflection_6.dll, puede utilizar la reflexión para inspeccionar el contenido de este ensamblado. Esto implica el uso de la función de API de reflexión estática xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType para cargar el ensamblado. Esta función devuelve la dirección de un objeto Assembly que, a continuación, se puede consultar sobre los módulos y tipos dentro de .
Una vez que el sistema de reflexión carga correctamente el ensamblado, se recupera una matriz de objetos Type con la función Assembly.GetTypes. Cada elemento de la matriz contiene información sobre un tipo diferente, aunque en este caso solo se define una clase. Con un bucle , cada tipo de esta matriz se consulta sobre los miembros de tipo mediante la función Type::GetMembers. Esta función devuelve una matriz de objetos MethodInfo, cada objeto que contiene información sobre la función miembro, el miembro de datos o la propiedad del tipo.
Tenga en cuenta que la lista de métodos incluye las funciones definidas explícitamente en TestClass y las funciones heredadas implícitamente de la clase System::Object. Por haberse descrito en .NET y no en la sintaxis de Visual C++, las propiedades aparecen como el miembro de datos subyacente al que se obtiene acceso mediante funciones get o set. Las funciones get y set aparecen en esta lista con métodos periódicas. La reflexión se admite a través de Common Language Runtime, no del compilador de Microsoft C++.
Aunque utilice este código para examinar un ensamblado que haya definido, también puede utilizarlo para examinar ensamblados de .NET. Por ejemplo, si cambia TestAssembly por mscorlib, se mostrará una lista de todos los tipos y métodos definidos en mscorlib.dll.
// vcpp_reflection_6.cpp
// compile with: /clr
using namespace System;
using namespace System::IO;
using namespace System::Reflection;
int main() {
Assembly ^ a = nullptr;
try {
// load assembly -- do not use file extension
// will look for .dll extension first
// then .exe with the filename
a = Assembly::Load("vcpp_reflection_5");
}
catch (FileNotFoundException ^ e) {
Console::WriteLine(e->Message);
return -1;
}
Console::WriteLine("assembly info:");
Console::WriteLine(a->FullName);
array<Type^>^ typeArray = a->GetTypes();
Console::WriteLine("type info ({0} types):", typeArray->Length);
int totalTypes = 0;
int totalMembers = 0;
for (int i = 0 ; i < typeArray->Length ; i++) {
// retrieve array of member descriptions
array<MemberInfo^>^ member = typeArray[i]->GetMembers();
Console::WriteLine(" members of {0} ({1} members):",
typeArray[i]->FullName, member->Length);
for (int j = 0 ; j < member->Length ; j++) {
Console::Write(" ({0})",
member[j]->MemberType.ToString() );
Console::Write("{0} ", member[j]);
Console::WriteLine("");
totalMembers++;
}
totalTypes++;
}
Console::WriteLine("{0} total types, {1} total members",
totalTypes, totalMembers);
}
Cómo: Implementar una arquitectura de componentes complementarios mediante reflexión
En los ejemplos de código siguientes se muestra el uso de la reflexión para implementar una arquitectura de "complemento" simple. La primera lista es la aplicación y la segunda es el complemento. La aplicación es un formulario de varios documentos que se rellena mediante las clases basadas en formularios que se encuentran en el archivo DLL del complemento proporcionado como argumento de línea de comandos.
La aplicación intenta cargar el ensamblado proporcionado mediante el método System.Reflection.Assembly.Load. Si se realiza correctamente, los tipos dentro del ensamblado se enumeran mediante el método System.Reflection.Assembly.GetTypes. A continuación, se comprueba la compatibilidad de cada tipo mediante el método System.Type.IsAssignableFrom. En este ejemplo, las clases que se encuentran en el ensamblado proporcionado deben derivarse de la clase Form para calificarse como complemento.
A continuación, se crean instancias de las clases compatibles con el método System.Activator.CreateInstance, que acepta un tipo como argumento y devuelve un puntero a una nueva instancia. A continuación, cada nueva instancia se adjunta al formulario y se muestra.
Tenga en cuenta que el método Load no acepta nombres de ensamblado que incluyan la extensión de archivo. La función main de la aplicación recorta las extensiones proporcionadas, por lo que el ejemplo de código siguiente funciona en cualquier caso.
Aplicación de ejemplo
El código siguiente define la aplicación que acepta complementos. Se debe proporcionar un nombre de ensamblado como primer argumento. Este ensamblado debe contener al menos un tipo derivado de formulario público.
// plugin_application.cpp
// compile with: /clr /c
#using <system.dll>
#using <system.drawing.dll>
#using <system.windows.forms.dll>
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Reflection;
ref class PluggableForm : public Form {
public:
PluggableForm() {}
PluggableForm(Assembly^ plugAssembly) {
Text = "plug-in example";
Size = Drawing::Size(400, 400);
IsMdiContainer = true;
array<Type^>^ types = plugAssembly->GetTypes( );
Type^ formType = Form::typeid;
for (int i = 0 ; i < types->Length ; i++) {
if (formType->IsAssignableFrom(types[i])) {
// Create an instance given the type description.
Form^ f = dynamic_cast<Form^> (Activator::CreateInstance(types[i]));
if (f) {
f->Text = types[i]->ToString();
f->MdiParent = this;
f->Show();
}
}
}
}
};
int main() {
Assembly^ a = Assembly::LoadFrom("plugin_application.exe");
Application::Run(gcnew PluggableForm(a));
}
Complementos de ejemplo
El código siguiente define tres clases derivadas de Form. Cuando el nombre del ensamblado resultante se pasa al ejecutable de la lista anterior, se detectará y se crearán instancias de cada una de estas tres clases, a pesar de que todas ellas eran desconocidas para la aplicación de hospedaje en tiempo de compilación.
// plugin_assembly.cpp
// compile with: /clr /LD
#using <system.dll>
#using <system.drawing.dll>
#using <system.windows.forms.dll>
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Reflection;
using namespace System::Drawing;
public ref class BlueForm : public Form {
public:
BlueForm() {
BackColor = Color::Blue;
}
};
public ref class CircleForm : public Form {
protected:
virtual void OnPaint(PaintEventArgs^ args) override {
args->Graphics->FillEllipse(Brushes::Green, ClientRectangle);
}
};
public ref class StarburstForm : public Form {
public:
StarburstForm(){
BackColor = Color::Black;
}
protected:
virtual void OnPaint(PaintEventArgs^ args) override {
Pen^ p = gcnew Pen(Color::Red, 2);
Random^ r = gcnew Random( );
Int32 w = ClientSize.Width;
Int32 h = ClientSize.Height;
for (int i=0; i<100; i++) {
float x1 = w / 2;
float y1 = h / 2;
float x2 = r->Next(w);
float y2 = r->Next(h);
args->Graphics->DrawLine(p, x1, y1, x2, y2);
}
}
};
Cómo: Enumerar tipos de datos en ensamblados mediante reflexión
En el código siguiente se muestra la enumeración de tipos y miembros públicos mediante System.Reflection.
Dado el nombre de un ensamblado, ya sea en el directorio local o en la GAC, el código siguiente intenta abrir el ensamblado y recuperar descripciones. Si se realiza correctamente, cada tipo se muestra con sus miembros públicos.
Tenga en cuenta que System.Reflection.Assembly.Load requiere que no se utilice ninguna extensión de archivo. Por lo tanto, se producirá un error al usar "mscorlib.dll" como argumento de línea de comandos, mientras que si se usa solo "mscorlib", se mostrarán los tipos .NET Framework comandos. Si no se proporciona ningún nombre de ensamblado, el código detectará y notificará los tipos dentro del ensamblado actual (el archivo EXE resultante de este código).
Ejemplo
// self_reflection.cpp
// compile with: /clr
using namespace System;
using namespace System::Reflection;
using namespace System::Collections;
public ref class ExampleType {
public:
ExampleType() {}
void Func() {}
};
int main() {
String^ delimStr = " ";
array<Char>^ delimiter = delimStr->ToCharArray( );
array<String^>^ args = Environment::CommandLine->Split( delimiter );
// replace "self_reflection.exe" with an assembly from either the local
// directory or the GAC
Assembly^ a = Assembly::LoadFrom("self_reflection.exe");
Console::WriteLine(a);
int count = 0;
array<Type^>^ types = a->GetTypes();
IEnumerator^ typeIter = types->GetEnumerator();
while ( typeIter->MoveNext() ) {
Type^ t = dynamic_cast<Type^>(typeIter->Current);
Console::WriteLine(" {0}", t->ToString());
array<MemberInfo^>^ members = t->GetMembers();
IEnumerator^ memberIter = members->GetEnumerator();
while ( memberIter->MoveNext() ) {
MemberInfo^ mi = dynamic_cast<MemberInfo^>(memberIter->Current);
Console::Write(" {0}", mi->ToString( ) );
if (mi->MemberType == MemberTypes::Constructor)
Console::Write(" (constructor)");
Console::WriteLine();
}
count++;
}
Console::WriteLine("{0} types found", count);
}