Réflexion (C++-CLI)
La réflexion permet d’inspecter les types de données connus au moment de l’exécution. La réflexion permet l’énumération des types de données dans un assembly donné, et les membres d’une classe ou d’un type valeur donné peuvent être découverts. Cela est vrai, que le type ait été connu ou référencé au moment de la compilation. Cela rend la réflexion utile pour les outils de développement et de gestion du code.
Notez que le nom de l’assembly fourni est le nom fort (voir Création et utilisation d’assemblys avec nom fort), qui inclut la version de l’assembly, la culture et les informations de signature. Notez également que le nom de l’espace de noms dans lequel le type de données est défini peut être récupéré, ainsi que le nom de la classe de base.
Le moyen le plus courant d’accéder aux fonctionnalités de réflexion consiste à utiliser la GetType méthode. Cette méthode est fournie par System.Object, à partir de laquelle toutes les classes collectées par le garbage-collected dérivent.
Remarque
La réflexion sur une .exe générée avec le compilateur Microsoft C++ n’est autorisée que si le .exe est généré avec les options du compilateur /clr :pure ou /clr :safe . Les options du compilateur /clr :pure et /clr :safe sont déconseillées dans Visual Studio 2015 et indisponibles dans Visual Studio 2017. Pour plus d’informations, consultez /clr (Compilation Common Language Runtime).
Pour plus d’informations, consultez System.Reflection
Exemple : GetType
La GetType
méthode retourne un pointeur vers un Type objet de classe, qui décrit le type sur lequel l’objet est basé. (Le L’objet Type ne contient aucune information spécifique à l’instance.) Un de ces éléments est le nom complet du type, qui peut être affiché comme suit :
Notez que le nom du type inclut l’étendue complète dans laquelle le type est défini, y compris l’espace de noms, et qu’il est affiché dans la syntaxe .NET, avec un point comme opérateur de résolution d’étendue.
// 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'
Exemple : types valeur boxed
Les types valeur peuvent également être utilisés avec la GetType
fonction, mais ils doivent d’abord être boxés.
// 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'
Exemple : typeid
Comme avec la GetType
méthode, l’opérateur typeid retourne un pointeur vers un objet Type . Ce code indique donc le nom de type System.Int32. L’affichage des noms de types est la fonctionnalité de réflexion la plus simple, mais une technique potentiellement plus utile consiste à inspecter ou à découvrir les valeurs valides pour les types énumérés. Pour ce faire, utilisez la fonction Enum ::GetNames statique, qui retourne un tableau de chaînes, chacune contenant une valeur d’énumération sous forme de texte. L’exemple suivant récupère un tableau de chaînes qui décrit les valeurs d’énumération de valeur pour l’énumération Options (CLR) et les affiche dans une boucle.
Si une quatrième option est ajoutée à l’énumération Options , ce code signale la nouvelle option sans recompilation, même si l’énumération est définie dans un assembly distinct.
// 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
Exemple : Membres et propriétés GetType
L’objet GetType
prend en charge un certain nombre de membres et de propriétés qui peuvent être utilisés pour examiner un type. Ce code récupère et affiche certaines de ces informations :
// 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
Exemple : énumération de types
La réflexion permet également l’énumération des types au sein d’un assembly et des membres dans les classes. Pour illustrer cette fonctionnalité, définissez une classe 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; }
}
};
Exemple : inspection des assemblys
Si le code ci-dessus est compilé dans une DLL appelée vcpp_reflection_6.dll, vous pouvez ensuite utiliser la réflexion pour inspecter le contenu de cet assembly. Cela implique l’utilisation de la fonction d’API de réflexion statique xref :System.Reflection.Assembly.Load%2A ?displayProperty=nameWithType pour charger l’assembly. Cette fonction retourne l’adresse d’un objet Assembly qui peut ensuite être interrogée sur les modules et les types dans utilisant.
Une fois que le système de réflexion charge correctement l’assembly, un tableau d’objets Type est récupéré avec la Assembly.GetTypes fonction. Chaque élément de tableau contient des informations sur un type différent, même si dans ce cas, une seule classe est définie. À l’aide d’une boucle, chaque type de ce tableau est interrogé sur les membres de type à l’aide de la fonction Type ::GetMembers . Cette fonction retourne un tableau d’objets MethodInfo , chaque objet contenant des informations sur la fonction membre, le membre de données ou la propriété dans le type.
Notez que la liste des méthodes inclut les fonctions explicitement définies dans TestClass et les fonctions héritées implicitement de la classe System ::Object . Dans le cadre de la description dans .NET plutôt que dans la syntaxe Visual C++, les propriétés apparaissent en tant que membre de données sous-jacent accessible par les fonctions get/set. Les fonctions get/set apparaissent dans cette liste sous forme de méthodes régulières. La réflexion est prise en charge par le common language runtime, et non par le compilateur Microsoft C++.
Bien que vous utilisiez ce code pour inspecter un assembly que vous avez défini, vous pouvez également utiliser ce code pour inspecter les assemblys .NET. Par exemple, si vous modifiez TestAssembly en mscorlib, vous verrez une liste de chaque type et méthode défini dans 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);
}
Guide pratique pour implémenter une architecture de composant plug-in à l’aide de la réflexion
Les exemples de code suivants illustrent l’utilisation de la réflexion pour implémenter une architecture simple « plug-in ». La première liste est l’application, et la seconde est le plug-in. L’application est un formulaire de document multiple qui se remplit à l’aide de classes basées sur des formulaires trouvées dans la DLL de plug-in fournie en tant qu’argument de ligne de commande.
L’application tente de charger l’assembly fourni à l’aide de la System.Reflection.Assembly.Load méthode. En cas de réussite, les types à l’intérieur de l’assembly sont énumérés à l’aide de la System.Reflection.Assembly.GetTypes méthode. Chaque type est ensuite vérifié pour la compatibilité à l’aide de la System.Type.IsAssignableFrom méthode. Dans cet exemple, les classes trouvées dans l’assembly fourni doivent être dérivées de la Form classe pour être qualifiées de plug-in.
Les classes compatibles sont ensuite instanciées avec la System.Activator.CreateInstance méthode, qui accepte un Type argument et retourne un pointeur vers une nouvelle instance. Chaque nouvelle instance est ensuite attachée au formulaire et affichée.
Notez que la Load méthode n’accepte pas les noms d’assemblys qui incluent l’extension de fichier. La fonction principale de l’application supprime toutes les extensions fournies, de sorte que l’exemple de code suivant fonctionne dans les deux cas.
Exemple d’application
Le code suivant définit l’application qui accepte les plug-ins. Un nom d’assembly doit être fourni comme premier argument. Cet assembly doit contenir au moins un type dérivé public 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));
}
Exemples de plug-ins
Le code suivant définit trois classes dérivées de Form. Lorsque le nom du nom de l’assembly résultant est transmis à l’exécutable dans la liste précédente, chacune de ces trois classes sera découverte et instanciée, malgré le fait qu’elles étaient toutes inconnues de l’application d’hébergement au moment de la compilation.
// 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);
}
}
};
Guide pratique pour énumérer des types de données dans des assemblys à l’aide de la réflexion
Le code suivant illustre l’énumération des types publics et des membres à l’aide System.Reflectionde .
Étant donné le nom d’un assembly, dans le répertoire local ou dans le GAC, le code ci-dessous tente d’ouvrir l’assembly et de récupérer des descriptions. En cas de réussite, chaque type s’affiche avec ses membres publics.
Notez qu’aucune System.Reflection.Assembly.Load extension de fichier n’est utilisée. Par conséquent, l’utilisation de « mscorlib.dll » comme argument de ligne de commande échoue, tandis que l’utilisation de « mscorlib » entraîne l’affichage des types .NET Framework. Si aucun nom d’assembly n’est fourni, le code détecte et signale les types dans l’assembly actuel (exe résultant de ce code).
Exemple
// 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);
}