Reflexão (C++/CLI)

A reflexão permite que tipos de dados conhecidos sejam inspecionados em runtime. A reflexão permite a enumeração de tipos de dados em um determinado assembly, e os membros de uma determinada classe ou tipo de valor podem ser descobertos. Isso é verdadeiro, independentemente de o tipo ser conhecido ou referenciado em tempo de compilação. Isso torna a reflexão um recurso útil para ferramentas de desenvolvimento e gerenciamento de código.

Observe que o nome do assembly fornecido é o nome forte (consulte Criando e usando assemblies Strong-Named), que inclui a versão do assembly, a cultura e as informações de assinatura. Observe também que o nome do namespace no qual o tipo de dados é definido pode ser recuperado, juntamente com o nome da classe base.

A maneira mais comum de acessar recursos de reflexão é por meio do método GetType. Esse método é fornecido por System.Object, do qual todas as classes coletadas por lixo derivam.

Observação

A reflexão sobre uma .exe criada com o compilador do Microsoft C++ só será permitida se o .exe for criado com as opções do compilador /clr:pure ou /clr:safe. As opções do compilador /clr:pure e /clr:safe estão preteridas no Visual Studio 2015 e não têm suporte no Visual Studio 2017. Para obter mais informações, confira /clr (Compilação do Common Language Runtime).

Para obter mais informações, veja System.Reflection

Exemplo: GetType

O método GetType retorna um ponteiro para um objeto de classe Type, que descreve o tipo quando o objeto é baseado. (O objeto Type não contém nenhuma informação específica da instância.) Um desses itens é o nome completo do tipo, que pode ser exibido da seguinte maneira:

Observe que o nome do tipo inclui o escopo completo no qual o tipo é definido, incluindo o namespace, e que ele é exibido na sintaxe .NET, com um ponto como o operador de resolução de escopo.

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

Exemplo: tipos de valor em caixa

Os tipos de valor também podem ser usados com a função GetType, mas eles devem ser encaixotados primeiro.

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

Exemplo: typeid

Assim como acontece com o método GetType, o operador typeid retorna um ponteiro para um objeto Type, portanto, esse código indica o nome do tipo System.Int32. Exibir nomes de tipo é o recurso mais básico de reflexão, mas uma técnica potencialmente mais útil é inspecionar ou descobrir os valores válidos para tipos enumerados. Isso pode ser feito usando a função Enum::GetNames estática, que retorna uma matriz de cadeias de caracteres, cada uma contendo um valor de enumeração no formulário de texto. O exemplo a seguir recupera uma matriz de cadeias de caracteres que descreve os valores de enumeração de valor para a enumeração Opções (CLR) e exibe-os em um loop.

Se uma quarta opção for adicionada à enumeração Opções, esse código relatará a nova opção sem recompilação, mesmo que a enumeração seja definida em um assembly separado.

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

Exemplo: membros e propriedades GetType

O objeto GetType dá suporte a vários membros e propriedades que podem ser usados para examinar um tipo. Esse código recupera e exibe algumas dessas informações:

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

Exemplo: ma enumeração de tipos.

A reflexão também permite a enumeração de tipos em um assembly e os membros dentro das classes. Para demonstrar esse recurso, defina uma classe simples:

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

Exemplo: inspeção de assemblies

Se o código acima for compilado em uma DLL chamada vcpp_reflection_6.dll, você poderá usar reflexão para inspecionar o conteúdo deste assembly. Isso envolve o uso da função de API de reflexão estática xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType para carregar o assembly. Essa função retorna o endereço de um objeto Assembly que pode ser consultado sobre os módulos e tipos dentro.

Depois que o sistema de reflexão carrega com êxito o assembly, uma matriz de objetos Type é recuperada com a função Assembly.GetTypes. Cada elemento de matriz contém informações sobre um tipo diferente, embora, nesse caso, apenas uma classe seja definida. Usando um loop, cada Tipo nessa matriz é consultado sobre os membros do tipo usando a função Type::GetMembers. Essa função retorna uma matriz de objetos MethodInfo, cada objeto que contém informações sobre a função membro, membro de dados ou propriedade no tipo.

Observe que a lista de métodos inclui as funções explicitamente definidas em TestClass e as funções implicitamente herdadas da classe System::Object. Como parte de ser descrito no .NET em vez de na sintaxe do Visual C++, as propriedades aparecem como o membro de dados subjacente acessado pelas funções get/set. As funções get/set aparecem nesta lista como métodos regulares. Há suporte para reflexão por meio do common language runtime, não pelo compilador do Microsoft C++.

Embora tenha usado esse código para inspecionar um assembly que você definiu, você também pode usar esse código para inspecionar assemblies .NET. Por exemplo, se você alterar TestAssembly para mscorlib, verá uma listagem de todos os tipos e métodos definidos em 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);
}

Como: Implementar uma arquitetura de componente de plug-in usando o Reflection

Os exemplos de código a seguir demonstram o uso da reflexão para implementar uma arquitetura simples de "plug-in". A primeira listagem é o aplicativo e a segunda é o plug-in. O aplicativo é um formulário de documento múltiplo que se preenche usando todas as classes baseadas em formulário encontradas na DLL de plug-in fornecida como um argumento de linha de comando.

O aplicativo tenta carregar o assembly fornecido usando o método System.Reflection.Assembly.Load. Se tiver êxito, os tipos dentro do assembly serão enumerados usando o método System.Reflection.Assembly.GetTypes. Cada tipo é então verificado quanto à compatibilidade usando o método System.Type.IsAssignableFrom. Neste exemplo, as classes encontradas no assembly fornecido devem ser derivadas da classe Form para se qualificarem como um plug-in.

As classes compatíveis são então instanciadas com o método System.Activator.CreateInstance, que aceita um argumento Type e retorna um ponteiro para uma nova instância. Cada nova instância é anexada ao formulário e exibida.

Observe que o método Load não aceita nomes de assembly que incluem a extensão de arquivo. A função principal no aplicativo corta todas as extensões fornecidas, portanto, o exemplo de código a seguir funciona em ambos os casos.

Aplicativo de exemplo

O código a seguir define o aplicativo que aceita plug-ins. Um nome de assembly deve ser fornecido como o primeiro argumento. Esse assembly deve conter pelo menos um 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));
}

Plug-ins de exemplo

O código a seguir define três classes derivadas de Form. Quando o nome do assembly resultante for passado para o executável na listagem anterior, cada uma dessas três classes será descoberta e instanciada, apesar do fato de que todas elas eram desconhecidas para o aplicativo de hospedagem em tempo de compilação.

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

Como: enumerar tipos de dados em assemblies usando reflexão

O código a seguir demonstra a enumeração de tipos públicos e membros usando System.Reflection.

Dado o nome de um assembly, seja no diretório local ou no GAC, o código abaixo tenta abrir o assembly e recuperar descrições. Se for bem-sucedido, cada tipo será exibido com seus membros públicos.

Observe que System.Reflection.Assembly.Load requer que nenhuma extensão de arquivo seja usada. Portanto, o uso de "mscorlib.dll" como um argumento de linha de comando falhará, enquanto usar apenas "mscorlib" resultará na exibição dos tipos .NET Framework. Se nenhum nome de assembly for fornecido, o código detectará e relatará os tipos dentro do assembly atual (o EXE resultante desse código).

Exemplo

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

Confira também