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.
Observe que el nombre del ensamblado proporcionado es un nombre seguro (consulte Creación y uso de ensamblados con nombre seguro) que incluye información de la versión de ensamblado, de referencia cultural y 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 obtener acceso a las características de reflexión es utilizar el método GetType. Este método lo proporciona System.Object, del que derivan todas las clases de recolección de elementos no utilizados.
Nota:
La reflexión en un archivo .exe creado con el compilador de Microsoft C++ se permite solo si el archivo .exe se ha creado con las opciones del compilador /clr:pure o /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. Para obtener más información, consulte /clr (Compilación de Common Language Runtime).
Para obtener más información, consulte: System.Reflection
Ejemplo: GetType
El método GetType
devuelve un puntero a un objeto de clase Type, que describe el tipo en el que se basa el objeto. (El objeto Type no contiene ninguna información específica de la instancia). Un elemento de este tipo es el nombre completo del tipo, que se puede mostrar de la siguiente manera:
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 conversión boxing aplicada
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 ocurre con el método GetType
, el operador typeid devuelve un puntero a un objeto Type, por lo que este código indica el nombre de tipo System.Int32. 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 puede realizarse 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. El siguiente ejemplo recupera una matriz de cadenas que describe los valores de enumeración de valor para la enumeración Options (CLR) y los muestra en un bucle.
Si se agrega la cuarta opción a la enumeración Options, este código informará la nueva opción sin recopilació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: miembros y propiedades 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 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 al que se le pueden consultar los módulos y tipos que incluye.
Una vez que el sistema de reflexión cargue 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. Mediante un bucle, cada Type en la matriz es consultado sobre los miembros del tipo a través de la función Type::GetMembers. Esta función devuelve una matriz de objetos MethodInfo, cada uno de los cuales contiene información sobre la función miembro, el miembro de datos o la propiedad en el tipo.
Observe 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 por el 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 de complemento mediante la reflexión
En los siguientes ejemplos de código se muestra el uso de la reflexión para implementar una arquitectura sencilla de "complemento". La primera lista es la aplicación y la segunda el complemento. La aplicación es un formulario de varios documentos que se rellena mediante cualquier clase basada en formularios que se encuentre en el archivo DLL del complemento proporcionado como un argumento de línea de comandos.
La aplicación intenta cargar el ensamblado proporcionado mediante el método System.Reflection.Assembly.Load. Si se ejecuta 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 calificar como un complemento.
A continuación, se crean instancias de clases compatibles con el método System.Activator.CreateInstance, que acepta un Type como argumento y devuelve un puntero a una nueva instancia. 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 principal de la aplicación recorta las extensiones proporcionadas, por lo que el siguiente ejemplo de código funciona en cualquier caso.
Ejemplo de aplicación
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 público Form.
// 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 en la lista anterior, se detectarán y crearán instancias de cada una de estas tres clases, a pesar de que todos eran desconocidos para la aplicación host en el 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);
}
}
};
Procedimiento para enumerar tipos de datos en ensamblados mediante reflexión
En el código siguiente se muestra la enumeración de tipos públicos y miembros mediante System.Reflection.
Dado el nombre de un ensamblado, ya sea en el directorio local o en el GAC, el código siguiente intentará abrir el ensamblado y recuperar las descripciones. Si se ejecuta correctamente, cada tipo se muestra con sus miembros públicos.
Tenga en cuenta que System.Reflection.Assembly.Load requiere que no se use ninguna extensión de archivo. Por lo tanto, al usar "mscorlib.dll" como argumento de línea de comandos se producirá un error, mientras que el uso de "mscorlib" solo dará como resultado la presentación de los tipos de .NET Framework. Si no se proporciona ningún nombre de ensamblado, el código detectará e informará los tipos dentro del ensamblado actual (el 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);
}