Динамическая загрузка и использование типов
Механизм отражения создает инфраструктуру, используемую такими языковыми компиляторами, как Microsoft Visual Basic 2005 и Jscript, для реализации неявной поздней привязки. Привязка — это процесс поиска объявления (т. е. реализации), соответствующего однозначно заданному типу. Когда данный процесс имеет место во время выполнения, а не во время компиляции, это называется поздним связыванием. В Visual Basic 2005 можно неявно использовать в коде позднее связывание; компилятор Visual Basic вызывает вспомогательный метод, который использует отражение для получения типа объектов. Аргументы, переданные вспомогательному методу, обеспечивают вызов надлежащего метода во время выполнения. Эти аргументы определяют экземпляр (объект), для которого вызывается метод, имя вызываемого метода (строковый параметр) и передаваемые этому методу аргументы (массив объектов).
В следующем примере компилятор Visual Basic неявно использует отражение для вызова метода объекта, тип которого во время компиляции неизвестен. В классе HelloWorld содержится метод PrintHello, который выдает на печать строку "Hello World" вместе с текстом, переданным в метод PrintHello. Вызываемый в этом примере метод PrintHello фактически является методом Type.InvokeMember; код Visual Basic позволяет вызвать метод PrintHello таким образом, словно тип объекта (helloObj) известен во время компиляции (ранняя привязка), а не во время выполнения (поздняя привязка).
Imports System
Module Hello
Sub Main()
' Sets up the variable.
Dim helloObj As Object
' Creates the object.
helloObj = new HelloWorld()
' Invokes the print method as if it was early bound
' even though it is really late bound.
helloObj.PrintHello("Visual Basic Late Bound")
End Sub
End Module
Настраиваемая привязка
Отражение может использоваться для обеспечения поздней привязки как неявно компиляторами, так и явным образом в коде.
Среда CLR поддерживает несколько языков программирования, и правила привязки в этих языках различаются. В случае ранней привязки генераторы кода могут полностью контролировать этот процесс. Однако поздняя привязка, осуществляемая с помощью отражения, должна контролироваться путем настройки привязки. Класс Binder предоставляет настраиваемый элемент управления для выбора и вызова членов.
С помощью настраиваемой привязки можно загрузить сборку во время выполнения, получить сведения о типах, включенных в эту сборку, определить нужный тип, а затем вызвать методы или обратиться к полям или свойствам этого типа. Такой способ применяется в том случае, если тип объекта во время компиляции неизвестен, например зависит от данных, введенных пользователем.
В следующем примере показан простой пользовательский связыватель, в котором не производится преобразование типов аргументов. Основному примеру предшествует код Simple_Type.dll. Необходимо выполнить построение библиотеки Simple_Type.dll, а затем включить в проект ссылку на нее во время построения.
' Code for building SimpleType.dll.
Imports System
Imports System.Reflection
Imports System.Globalization
Imports Simple_Type
Namespace Simple_Type
Public Class MySimpleClass
Public Sub MyMethod(str As String, i As Integer)
Console.WriteLine("MyMethod parameters: {0}, {1}", str, i)
End Sub
Public Sub MyMethod(str As String, i As Integer, j As Integer)
Console.WriteLine("MyMethod parameters: {0}, {1}, {2}",
str, i, j)
End Sub
End Class
End Namespace
Namespace Custom_Binder
Class MyMainClass
Shared Sub Main()
' Get the type of MySimpleClass.
Dim myType As Type = GetType(MySimpleClass)
' Get an instance of MySimpleClass.
Dim myInstance As New MySimpleClass()
Dim myCustomBinder As New MyCustomBinder()
' Get the method information for the particular overload
' being sought.
Dim myMethod As MethodInfo = myType.GetMethod("MyMethod",
BindingFlags.Public Or BindingFlags.Instance,
myCustomBinder, New Type() {GetType(String),
GetType(Integer)}, Nothing)
Console.WriteLine(myMethod.ToString())
' Invoke the overload.
myType.InvokeMember("MyMethod", BindingFlags.InvokeMethod,
myCustomBinder, myInstance,
New Object() {"Testing...", CInt(32)})
End Sub
End Class
' ****************************************************
' A simple custom binder that provides no
' argument type conversion.
' ****************************************************
Class MyCustomBinder
Inherits Binder
Public Overrides Function BindToMethod(bindingAttr As BindingFlags,
match() As MethodBase, ByRef args As Object(),
modIfiers() As ParameterModIfier, culture As CultureInfo,
names() As String, ByRef state As Object) As MethodBase
If match is Nothing Then
Throw New ArgumentNullException("match")
End If
' Arguments are not being reordered.
state = Nothing
' Find a parameter match and return the first method with
' parameters that match the request.
For Each mb As MethodBase in match
Dim parameters() As ParameterInfo = mb.GetParameters()
If ParametersMatch(parameters, args) Then
Return mb
End If
Next mb
Return Nothing
End Function
Public Overrides Function BindToField(bindingAttr As BindingFlags,
match() As FieldInfo, value As Object, culture As CultureInfo) As FieldInfo
If match Is Nothing
Throw New ArgumentNullException("match")
End If
For Each fi As FieldInfo in match
If fi.GetType() = value.GetType() Then
Return fi
End If
Next fi
Return Nothing
End Function
Public Overrides Function SelectMethod(bindingAttr As BindingFlags,
match() As MethodBase, types() As Type,
modifiers() As ParameterModifier) As MethodBase
If match Is Nothing Then
Throw New ArgumentNullException("match")
End If
' Find a parameter match and return the first method with
' parameters that match the request.
For Each mb As MethodBase In match
Dim parameters() As ParameterInfo = mb.GetParameters()
If ParametersMatch(parameters, types) Then
Return mb
End If
Next mb
Return Nothing
End Function
Public Overrides Function SelectProperty(
bindingAttr As BindingFlags, match() As PropertyInfo,
returnType As Type, indexes() As Type,
modIfiers() As ParameterModIfier) As PropertyInfo
If match Is Nothing Then
Throw New ArgumentNullException("match")
End If
For Each pi As PropertyInfo In match
If pi.GetType() = returnType And
ParametersMatch(pi.GetIndexParameters(), indexes) Then
Return pi
End If
Next pi
Return Nothing
End Function
Public Overrides Function ChangeType(
value As Object,
myChangeType As Type,
culture As CultureInfo) As Object
Try
Dim newType As Object
newType = Convert.ChangeType(value, myChangeType)
Return newType
' Throw an InvalidCastException If the conversion cannot
' be done by the Convert.ChangeType method.
Catch
Return Nothing
End Try
End Function
Public Overrides Sub ReorderArgumentArray(ByRef args() As Object, state As Object)
' No operation is needed here because BindToMethod does not
' reorder the args array. The most common implementation
' of this method is shown below.
' ((BinderState)state).args.CopyTo(args, 0)
End Sub
' Returns true only If the type of each object in a matches
' the type of each corresponding object in b.
Private Overloads Function ParametersMatch(a() As ParameterInfo, b() As Object) As Boolean
If a.Length <> b.Length Then
Return false
End If
For i As Integer = 0 To a.Length - 1
If a(i).ParameterType <> b(i).GetType() Then
Return false
End If
Next i
Return true
End Function
' Returns true only If the type of each object in a matches
' the type of each corresponding enTry in b.
Private Overloads Function ParametersMatch(a() As ParameterInfo,
b() As Type) As Boolean
If a.Length <> b.Length Then
Return false
End If
For i As Integer = 0 To a.Length - 1
If a(i).ParameterType <> b(i)
Return false
End If
Next
Return true
End Function
End Class
End Namespace
// Code for building SimpleType.dll.
using System;
using System.Reflection;
using System.Globalization;
using Simple_Type;
namespace Simple_Type
{
public class MySimpleClass
{
public void MyMethod(string str, int i)
{
Console.WriteLine("MyMethod parameters: {0}, {1}", str, i);
}
public void MyMethod(string str, int i, int j)
{
Console.WriteLine("MyMethod parameters: {0}, {1}, {2}",
str, i, j);
}
}
}
namespace Custom_Binder
{
class MyMainClass
{
static void Main()
{
// Get the type of MySimpleClass.
Type myType = typeof(MySimpleClass);
// Get an instance of MySimpleClass.
MySimpleClass myInstance = new MySimpleClass();
MyCustomBinder myCustomBinder = new MyCustomBinder();
// Get the method information for the particular overload
// being sought.
MethodInfo myMethod = myType.GetMethod("MyMethod",
BindingFlags.Public | BindingFlags.Instance,
myCustomBinder, new Type[] {typeof(string),
typeof(int)}, null);
Console.WriteLine(myMethod.ToString());
// Invoke the overload.
myType.InvokeMember("MyMethod", BindingFlags.InvokeMethod,
myCustomBinder, myInstance,
new Object[] {"Testing...", (int)32});
}
}
// ****************************************************
// A simple custom binder that provides no
// argument type conversion.
// ****************************************************
class MyCustomBinder : Binder
{
public override MethodBase BindToMethod(
BindingFlags bindingAttr,
MethodBase[] match,
ref object[] args,
ParameterModifier[] modifiers,
CultureInfo culture,
string[] names,
out object state)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
// Arguments are not being reordered.
state = null;
// Find a parameter match and return the first method with
// parameters that match the request.
foreach (MethodBase mb in match)
{
ParameterInfo[] parameters = mb.GetParameters();
if (ParametersMatch(parameters, args))
{
return mb;
}
}
return null;
}
public override FieldInfo BindToField(BindingFlags bindingAttr,
FieldInfo[] match, object value, CultureInfo culture)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
foreach (FieldInfo fi in match)
{
if (fi.GetType() == value.GetType())
{
return fi;
}
}
return null;
}
public override MethodBase SelectMethod(
BindingFlags bindingAttr,
MethodBase[] match,
Type[] types,
ParameterModifier[] modifiers)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
// Find a parameter match and return the first method with
// parameters that match the request.
foreach (MethodBase mb in match)
{
ParameterInfo[] parameters = mb.GetParameters();
if (ParametersMatch(parameters, types))
{
return mb;
}
}
return null;
}
public override PropertyInfo SelectProperty(
BindingFlags bindingAttr,
PropertyInfo[] match,
Type returnType,
Type[] indexes,
ParameterModifier[] modifiers)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
foreach (PropertyInfo pi in match)
{
if (pi.GetType() == returnType &&
ParametersMatch(pi.GetIndexParameters(), indexes))
{
return pi;
}
}
return null;
}
public override object ChangeType(
object value,
Type myChangeType,
CultureInfo culture)
{
try
{
object newType;
newType = Convert.ChangeType(value, myChangeType);
return newType;
}
// Throw an InvalidCastException if the conversion cannot
// be done by the Convert.ChangeType method.
catch (InvalidCastException)
{
return null;
}
}
public override void ReorderArgumentArray(ref object[] args,
object state)
{
// No operation is needed here because BindToMethod does not
// reorder the args array. The most common implementation
// of this method is shown below.
// ((BinderState)state).args.CopyTo(args, 0);
}
// Returns true only if the type of each object in a matches
// the type of each corresponding object in b.
private bool ParametersMatch(ParameterInfo[] a, object[] b)
{
if (a.Length != b.Length)
{
return false;
}
for (int i = 0; i < a.Length; i++)
{
if (a[i].ParameterType != b[i].GetType())
{
return false;
}
}
return true;
}
// Returns true only if the type of each object in a matches
// the type of each corresponding entry in b.
private bool ParametersMatch(ParameterInfo[] a, Type[] b)
{
if (a.Length != b.Length)
{
return false;
}
for (int i = 0; i < a.Length; i++)
{
if (a[i].ParameterType != b[i])
{
return false;
}
}
return true;
}
}
}
// Code for building SimpleType.dll.
using namespace System;
using namespace System::Reflection;
using namespace System::Globalization;
namespace Simple_Type
{
public ref class MySimpleClass
{
public:
void MyMethod(String^ str, int i)
{
Console::WriteLine("MyMethod parameters: {0}, {1}", str, i);
}
void MyMethod(String^ str, int i, int j)
{
Console::WriteLine("MyMethod parameters: {0}, {1}, {2}",
str, i, j);
}
};
}
using namespace Simple_Type;
namespace Custom_Binder
{
// ****************************************************
// A simple custom binder that provides no
// argument type conversion.
// ****************************************************
public ref class MyCustomBinder : Binder
{
public:
virtual MethodBase^ BindToMethod(
BindingFlags bindingAttr,
array<MethodBase^>^ match,
array<Object^>^% args,
array<ParameterModifier>^ modifiers,
CultureInfo^ culture,
array<String^>^ names,
Object^% state) override
{
if (match == nullptr)
{
throw gcnew ArgumentNullException("match");
}
// Arguments are not being reordered.
state = nullptr;
// Find a parameter match and return the first method with
// parameters that match the request.
for each (MethodBase^ mb in match)
{
array<ParameterInfo^>^ parameters = mb->GetParameters();
if (ParametersMatch(parameters, args))
{
return mb;
}
}
return nullptr;
}
virtual FieldInfo^ BindToField(BindingFlags bindingAttr,
array<FieldInfo^>^ match, Object^ value, CultureInfo^ culture) override
{
if (match == nullptr)
{
throw gcnew ArgumentNullException("match");
}
for each (FieldInfo^ fi in match)
{
if (fi->GetType() == value->GetType())
{
return fi;
}
}
return nullptr;
}
virtual MethodBase^ SelectMethod(
BindingFlags bindingAttr,
array<MethodBase^>^ match,
array<Type^>^ types,
array<ParameterModifier>^ modifiers) override
{
if (match == nullptr)
{
throw gcnew ArgumentNullException("match");
}
// Find a parameter match and return the first method with
// parameters that match the request.
for each (MethodBase^ mb in match)
{
array<ParameterInfo^>^ parameters = mb->GetParameters();
if (ParametersMatch(parameters, types))
{
return mb;
}
}
return nullptr;
}
virtual PropertyInfo^ SelectProperty(
BindingFlags bindingAttr,
array<PropertyInfo^>^ match,
Type^ returnType,
array<Type^>^ indexes,
array<ParameterModifier>^ modifiers) override
{
if (match == nullptr)
{
throw gcnew ArgumentNullException("match");
}
for each (PropertyInfo^ pi in match)
{
if (pi->GetType() == returnType &&
ParametersMatch(pi->GetIndexParameters(), indexes))
{
return pi;
}
}
return nullptr;
}
virtual Object^ ChangeType(
Object^ value,
Type^ myChangeType,
CultureInfo^ culture) override
{
try
{
Object^ newType;
newType = Convert::ChangeType(value, myChangeType);
return newType;
}
// Throw an InvalidCastException if the conversion cannot
// be done by the Convert.ChangeType method.
catch (InvalidCastException^)
{
return nullptr;
}
}
virtual void ReorderArgumentArray(array<Object^>^% args,
Object^ state) override
{
// No operation is needed here because BindToMethod does not
// reorder the args array. The most common implementation
// of this method is shown below.
// ((BinderState^)state).args.CopyTo(args, 0);
}
// Returns true only if the type of each object in a matches
// the type of each corresponding object in b.
private:
bool ParametersMatch(array<ParameterInfo^>^ a, array<Object^>^ b)
{
if (a->Length != b->Length)
{
return false;
}
for (int i = 0; i < a->Length; i++)
{
if (a[i]->ParameterType != b[i]->GetType())
{
return false;
}
}
return true;
}
// Returns true only if the type of each object in a matches
// the type of each corresponding entry in b.
bool ParametersMatch(array<ParameterInfo^>^ a, array<Type^>^ b)
{
if (a->Length != b->Length)
{
return false;
}
for (int i = 0; i < a->Length; i++)
{
if (a[i]->ParameterType != b[i])
{
return false;
}
}
return true;
}
};
public ref class MyMainClass
{
public:
static void Main()
{
// Get the type of MySimpleClass.
Type^ myType = MySimpleClass::typeid;
// Get an instance of MySimpleClass.
MySimpleClass^ myInstance = gcnew MySimpleClass();
MyCustomBinder^ myCustomBinder = gcnew MyCustomBinder();
// Get the method information for the particular overload
// being sought.
MethodInfo^ myMethod = myType->GetMethod("MyMethod",
BindingFlags::Public | BindingFlags::Instance,
myCustomBinder, gcnew array<Type^> {String::typeid,
int::typeid}, nullptr);
Console::WriteLine(myMethod->ToString());
// Invoke the overload.
myType->InvokeMember("MyMethod", BindingFlags::InvokeMethod,
myCustomBinder, myInstance,
gcnew array<Object^> {"Testing...", (int)32});
}
};
}
int main()
{
Custom_Binder::MyMainClass::Main();
}
InvokeMember и CreateInstance
Класс Type.InvokeMember служит для обращения к члену или типу. Методы CreateInstance различных классов, таких как System.Activator и System.Reflection.Assembly, являются специальными формами метода InvokeMember, которые создают новые экземпляры заданного типа. Для разрешения перегруженных версий и приведения аргументов в этих методах используется класс Binder.
В следующем примере показаны три возможных сочетания приведения аргументов (преобразования типов) и выбора членов. В первом случае (Case 1) не требуется выполнять ни приведение аргументов, ни выбор членов. Во втором случае (Case 2) необходим только выбор членов. В третьем случае (Case 3) необходимо только приведение аргументов.
Public Class CustomBinderDriver
Public Shared Sub Main()
Dim t As Type = GetType(CustomBinderDriver)
Dim binder As New CustomBinder()
Dim flags As BindingFlags = BindingFlags.InvokeMethod Or BindingFlags.Instance Or
BindingFlags.Public Or BindingFlags.Static
Dim args() As Object
' Case 1. Neither argument coercion nor member selection is needed.
args = New object() {}
t.InvokeMember ("PrintBob", flags, binder, Nothing, args)
' Case 2. Only member selection is needed.
args = New object() {42}
t.InvokeMember ("PrintValue", flags, binder, Nothing, args)
' Case 3. Only argument coercion is needed.
args = New object() {"5.5"}
t.InvokeMember("PrintNumber", flags, binder, Nothing, args)
End Sub
Public Shared Sub PrintBob()
Console.WriteLine ("PrintBob")
End Sub
Public Shared Sub PrintValue(value As Long)
Console.WriteLine("PrintValue ({0})", value)
End Sub
Public Shared Sub PrintValue(value As String)
Console.WriteLine("PrintValue ""{0}"")", value)
End Sub
Public Shared Sub PrintNumber(value As Double)
Console.WriteLine("PrintNumber ({0})", value)
End Sub
End Class
public class CustomBinderDriver
{
public static void Main()
{
Type t = typeof(CustomBinderDriver);
CustomBinder binder = new CustomBinder();
BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance |
BindingFlags.Public | BindingFlags.Static;
object[] args;
// Case 1. Neither argument coercion nor member selection is needed.
args = new object[] {};
t.InvokeMember("PrintBob", flags, binder, null, args);
// Case 2. Only member selection is needed.
args = new object[] {42};
t.InvokeMember("PrintValue", flags, binder, null, args);
// Case 3. Only argument coercion is needed.
args = new object[] {"5.5"};
t.InvokeMember("PrintNumber", flags, binder, null, args);
}
public static void PrintBob()
{
Console.WriteLine("PrintBob");
}
public static void PrintValue(long value)
{
Console.WriteLine("PrintValue({0})", value);
}
public static void PrintValue(string value)
{
Console.WriteLine("PrintValue\"{0}\")", value);
}
public static void PrintNumber(double value)
{
Console.WriteLine("PrintNumber ({0})", value);
}
}
public ref class CustomBinderDriver
{
public:
static void Main()
{
Type^ t = CustomBinderDriver::typeid;
CustomBinder^ binder = gcnew CustomBinder();
BindingFlags flags = BindingFlags::InvokeMethod | BindingFlags::Instance |
BindingFlags::Public | BindingFlags::Static;
array<Object^>^ args;
// Case 1. Neither argument coercion nor member selection is needed.
args = gcnew array<Object^> {};
t->InvokeMember("PrintBob", flags, binder, nullptr, args);
// Case 2. Only member selection is needed.
args = gcnew array<Object^> {42};
t->InvokeMember("PrintValue", flags, binder, nullptr, args);
// Case 3. Only argument coercion is needed.
args = gcnew array<Object^> {"5.5"};
t->InvokeMember("PrintNumber", flags, binder, nullptr, args);
}
static void PrintBob()
{
Console::WriteLine("PrintBob");
}
static void PrintValue(long value)
{
Console::WriteLine("PrintValue({0})", value);
}
static void PrintValue(String^ value)
{
Console::WriteLine("PrintValue\"{0}\")", value);
}
static void PrintNumber(double value)
{
Console::WriteLine("PrintNumber ({0})", value);
}
};
int main()
{
CustomBinderDriver::Main();
}
Разрешение перегруженных версий необходимо при наличии нескольких членов с одним именем. Для разрешения привязки к отдельному члену используются методы Binder.BindToMethod и Binder.BindToField. Метод Binder.BindToMethod также обеспечивает разрешение свойств при помощи методов обращения к свойствам get и set.
Если подобный вызов невозможен, метод BindToMethod возвращает вызываемый объект MethodBase или пустую ссылку (Nothing в Visual Basic). Возвращаемое значение метода MethodBase не обязано относиться к значениям, содержащимся в параметре match, хотя это обычно имеет место.
Если часть аргументов передается по ссылке, возможно, их потребуется возвратить в вызывающий код. Таким образом, объект Binder клиенту сопоставить массив аргументов обратно его исходной форме, если массив аргументов был изменен методом BindToMethod. Для этого необходимо гарантировать, что порядок аргументов, возвращаемых в вызывающий код, не изменится. Если аргументы передаются по имени, объект Binder изменяет порядок массива аргументов и в таком виде массив передается в вызывающий код. Дополнительные сведения см. в разделе Binder.ReorderArgumentArray.
Набор доступных членов состоит из членов, определенных в этом типе или в каком-либо базовом типе. Если установлен флаг BindingFlags.NonPublic, в наборе будут возвращены члены с любым уровнем доступности. Если флаг BindingFlags.NonPublic не установлен, связыватель должен проследить за соблюдением правил доступности. При определении флага привязки Public или NonPublic необходимо также установить флаг привязки Instance или Static. В обратном случае члены не будут возвращены.
Если существует только один член с данным именем, обратный вызов не требуется и привязка для этого метода считается выполненной. Этому варианту соответствует первый вариант (Case 1) в приведенном выше примере: доступен только один метод PrintBob и поэтому обратный вызов не требуется.
Если в доступном наборе содержится несколько членов, все они передаются в метод BindToMethod, который выбирает нужный метод и возвращает его. Во втором случае (Case 2) в рассматриваемом примере кода существуют два метода с именем PrintValue. Нужный метод выбирается при помощи вызова метода BindToMethod.
Метод ChangeType выполняет приведение аргументов (приведение типов), при котором фактические аргументы преобразуются в тип формальных аргументов выбранного метода. Метод ChangeType вызывается для каждого аргумента, даже если типы полностью совпадают.
В третьем случае (Case 3) рассматриваемого примера фактический аргумент типа String , имеющий значение "5.5", передается в метод с помощью формального аргумента типа Double. Чтобы вызов был успешным, строковое значение "5.5" должно быть преобразовано в тип "double". Это преобразование выполняет метод ChangeType.
Метод ChangeType выполняет только приведение без потерь, иначе называемое расширяющее приведение, что показано в следующей ниже таблице.
Исходный тип |
Целевой тип |
---|---|
Любой тип |
Соответствующий базовый тип |
Любой тип |
Реализуемый интерфейс |
Char |
UInt16, UInt32, Int32, UInt64, Int64, Single, Double |
Byte |
Char, UInt16, Int16, UInt32, Int32, UInt64, Int64, Single, Double |
SByte |
Int16, Int32, Int64, Single, Double |
UInt16 |
UInt32, Int32, UInt64, Int64, Single, Double |
Int16 |
Int32, Int64, Single, Double |
UInt32 |
UInt64, Int64, Single, Double |
Int32 |
Int64, Single, Double |
UInt64 |
Single, Double |
Int64 |
Single, Double |
Single |
Double |
Нессылочный тип |
Ссылочный тип |
В классе Type содержатся методы Get, которые используют параметры с типом Binder для разрешения ссылок на конкретный член. Поиск Type.GetConstructor, Type.GetMethod и Type.GetProperty определенного члена текущего типа путем предоставления сведений о сигнатуре этого члена. Методы Binder.SelectMethod и Binder.SelectProperty вызываются обратно, чтобы выбрать сведения о заданной сигнатуре соответствующих методов.