Reflection (C++/CLI)

La reflection consente di controllare i tipi di dati noti in fase di esecuzione. La reflection consente l'enumerazione dei tipi di dati in un determinato assembly e i membri di una determinata classe o tipo valore possono essere individuati. Questo vale indipendentemente dal fatto che il tipo sia stato noto o a cui si fa riferimento in fase di compilazione. Ciò rende la reflection una funzionalità utile per gli strumenti di sviluppo e gestione del codice.

Si noti che il nome dell'assembly specificato è il nome sicuro (vedere Creazione e uso di assembly con nome sicuro), che include la versione dell'assembly, le impostazioni cultura e le informazioni di firma. Si noti anche che è possibile recuperare il nome dello spazio dei nomi in cui è definito il tipo di dati, insieme al nome della classe di base.

Il modo più comune per accedere alle funzionalità di reflection è tramite il GetType metodo . Questo metodo viene fornito da System.Object, da cui derivano tutte le classi di Garbage Collection.

Nota

La reflection su un .exe compilato con il compilatore Microsoft C++ è consentita solo se la .exe viene compilata con le opzioni del compilatore /clr:pure o /clr:safe . Le opzioni del compilatore /clr:pure e /clr:safe sono deprecate in Visual Studio 2015 e non sono disponibili in Visual Studio 2017. Per altre informazioni, vedere /clr (compilazione Common Language Runtime).

Per altre informazioni, vedere System.Reflection.

Esempio: GetType

Il GetType metodo restituisce un puntatore a un Type oggetto classe, che descrive il tipo quando l'oggetto è basato. (L'oggetto L'oggetto type non contiene informazioni specifiche dell'istanza. Un elemento di questo tipo è il nome completo del tipo, che può essere visualizzato come segue:

Si noti che il nome del tipo include l'ambito completo in cui è definito il tipo, incluso lo spazio dei nomi e che viene visualizzato nella sintassi .NET, con un punto come operatore di risoluzione dell'ambito.

// 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'

Esempio: tipi valore boxed

I tipi valore possono essere usati anche con la GetType funzione , ma devono essere boxed per primi.

// 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'

Esempio: typeid

Come per il GetType metodo , l'operatore typeid restituisce un puntatore a un oggetto Type , quindi questo codice indica il nome del tipo System.Int32. La visualizzazione dei nomi dei tipi è la funzionalità di base della reflection, ma una tecnica potenzialmente più utile consiste nell'esaminare o individuare i valori validi per i tipi enumerati. A tale scopo, è possibile usare la funzione statica Enum::GetNames , che restituisce una matrice di stringhe, ognuna contenente un valore di enumerazione in formato testo. Nell'esempio seguente viene recuperata una matrice di stringhe che descrive i valori di enumerazione dei valori per l'enumerazione Options (CLR) e le visualizza in un ciclo.

Se viene aggiunta una quarta opzione all'enumerazione Options , questo codice segnala la nuova opzione senza ricompilazione, anche se l'enumerazione è definita in un assembly separato.

// 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

Esempio: Membri e proprietà GetType

L'oggetto GetType supporta una serie di membri e proprietà che possono essere utilizzati per esaminare un tipo. Questo codice recupera e visualizza alcune di queste informazioni:

// 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

Esempio: enumerazione di tipi

La reflection consente inoltre l'enumerazione dei tipi all'interno di un assembly e dei membri all'interno delle classi. Per illustrare questa funzionalità, definire una classe semplice:

// 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; }
   }
};

Esempio: ispezione degli assembly

Se il codice precedente viene compilato in una DLL denominata vcpp_reflection_6.dll, è possibile usare la reflection per esaminare il contenuto di questo assembly. Ciò comporta l'uso della funzione API reflection statica xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType per caricare l'assembly. Questa funzione restituisce l'indirizzo di un oggetto Assembly che può quindi essere sottoposto a query sui moduli e sui tipi all'interno.

Quando il sistema di reflection carica correttamente l'assembly, viene recuperata una matrice di oggetti Type con la Assembly.GetTypes funzione . Ogni elemento matrice contiene informazioni su un tipo diverso, anche se in questo caso viene definita una sola classe. Usando un ciclo, ogni tipo in questa matrice viene sottoposto a query sui membri del tipo usando la funzione Type::GetMembers . Questa funzione restituisce una matrice di oggetti MethodInfo , ogni oggetto contenente informazioni sulla funzione membro, sul membro dati o sulla proprietà nel tipo .

Si noti che l'elenco dei metodi include le funzioni definite in modo esplicito in TestClass e le funzioni ereditate in modo implicito dalla classe System::Object . Come parte della descrizione in .NET anziché nella sintassi di Visual C++, le proprietà vengono visualizzate come membro dati sottostante a cui si accede dalle funzioni get/set. Le funzioni get/set vengono visualizzate in questo elenco come metodi regolari. La reflection è supportata tramite Common Language Runtime, non dal compilatore Microsoft C++.

Anche se questo codice è stato usato per esaminare un assembly definito, è anche possibile usare questo codice per esaminare gli assembly .NET. Ad esempio, se si modifica TestAssembly in mscorlib, verrà visualizzato un elenco di ogni tipo e metodo definito in 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);
}

Procedura: Implementare un'architettura di componenti plug-in tramite reflection

Gli esempi di codice seguenti illustrano l'uso della reflection per implementare una semplice architettura "plug-in". Il primo elenco è l'applicazione e il secondo è il plug-in. L'applicazione è un modulo a documenti multipli che popola se stesso usando qualsiasi classe basata su form presente nella DLL del plug-in fornita come argomento della riga di comando.

L'applicazione tenta di caricare l'assembly fornito usando il System.Reflection.Assembly.Load metodo . In caso di esito positivo, i tipi all'interno dell'assembly vengono enumerati usando il System.Reflection.Assembly.GetTypes metodo . Ogni tipo viene quindi controllato per la compatibilità usando il System.Type.IsAssignableFrom metodo . In questo esempio, le classi trovate nell'assembly fornito devono essere derivate dalla Form classe per qualificarsi come plug-in.

Le classi compatibili vengono quindi create con il System.Activator.CreateInstance metodo , che accetta come Type argomento e restituisce un puntatore a una nuova istanza. Ogni nuova istanza viene quindi collegata al modulo e visualizzata.

Si noti che il Load metodo non accetta nomi di assembly che includono l'estensione di file. La funzione main nell'applicazione taglia tutte le estensioni fornite, quindi l'esempio di codice seguente funziona in entrambi i casi.

App di esempio

Il codice seguente definisce l'applicazione che accetta plug-in. È necessario specificare un nome di assembly come primo argomento. Questo assembly deve contenere almeno un tipo derivato pubblico 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));
}

Plug-in di esempio

Il codice seguente definisce tre classi derivate da Form. Quando il nome del nome dell'assembly risultante viene passato al file eseguibile nell'elenco precedente, ognuna di queste tre classi verrà individuata e creata un'istanza, nonostante il fatto che fossero tutti sconosciuti all'applicazione di hosting in fase di compilazione.

// 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);
      }
   }
};

Procedura: Enumerare i tipi di dati negli assembly tramite reflection

Il codice seguente illustra l'enumerazione di tipi e membri pubblici usando System.Reflection.

Dato il nome di un assembly, nella directory locale o nella GAC, il codice seguente tenta di aprire l'assembly e recuperare le descrizioni. In caso di esito positivo, ogni tipo viene visualizzato con i relativi membri pubblici.

Si noti che System.Reflection.Assembly.Load richiede che non venga usata alcuna estensione di file. Pertanto, l'uso di "mscorlib.dll" come argomento della riga di comando avrà esito negativo, mentre l'uso di "mscorlib" comporterà la visualizzazione dei tipi .NET Framework. Se non viene specificato alcun nome di assembly, il codice rileverà e segnala i tipi all'interno dell'assembly corrente (il file EXE risultante da questo codice).

Esempio

// 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);
}

Vedi anche