Läsa in och använda typer dynamiskt
Reflektion tillhandahåller infrastruktur som används av språkkompilatorer för att implementera implicit sen bindning. Bindning är processen för att hitta deklarationen (dvs. implementeringen) som motsvarar en unikt angiven typ. När den här processen inträffar vid körning i stället för vid kompileringstillfället kallas den för sen bindning. Med Visual Basic kan du använda implicit sen bindning i koden. Visual Basic-kompilatorn anropar en hjälpmetod som använder reflektion för att hämta objekttypen. Argumenten som skickas till hjälpmetoden gör att lämplig metod anropas vid körning. Dessa argument är den instans (ett objekt) som metoden ska anropas på, namnet på den anropade metoden (en sträng) och argumenten som skickas till den anropade metoden (en matris med objekt).
I följande exempel använder Visual Basic-kompilatorn reflektion implicit för att anropa en metod för ett objekt vars typ inte är känd vid kompileringstiden. En HelloWorld
klass har en PrintHello
metod som skriver ut "Hello World" sammanfogad med text som skickas till PrintHello
metoden. Metoden PrintHello
som anropas i det här exemplet är faktiskt en Type.InvokeMember. Med Visual Basic-koden kan PrintHello
metoden anropas som om typen av objektet (helloObj
) var känt vid kompileringstid (tidig bindning) i stället för vid körning (sen bindning).
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
Anpassad bindning
Förutom att användas implicit av kompilatorer för sen bindning kan reflektion användas explicit i kod för att utföra sen bindning.
Den vanliga språkkörningen stöder flera programmeringsspråk, och bindningsreglerna för dessa språk skiljer sig åt. I det tidiga fallet kan kodgeneratorer helt styra den här bindningen. Men vid sen bindning genom reflektion måste bindningen styras av en anpassad bindning. Klassen Binder ger anpassad kontroll över val och anrop av medlemmar.
Med hjälp av anpassad bindning kan du läsa in en sammansättning vid körning, hämta information om typer i den sammansättningen, ange den typ som du vill använda och sedan anropa metoder eller åtkomstfält eller egenskaper för den typen. Den här tekniken är användbar om du inte känner till ett objekts typ vid kompileringstillfället, till exempel när objekttypen är beroende av användarindata.
I följande exempel visas en enkel anpassad bindemedel som inte ger någon konvertering av argumenttyp. Kod för Simple_Type.dll
föregår huvudexemplet. Kom ihåg att skapa Simple_Type.dll
och sedan inkludera en referens till den i projektet vid byggtiden.
// 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();
}
// 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.
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
InvokeMember och CreateInstance
Använd Type.InvokeMember för att anropa en medlem av en typ. Metoderna CreateInstance
för olika klasser, till exempel Activator.CreateInstance och Assembly.CreateInstance, är specialiserade former av InvokeMember
som skapar nya instanser av den angivna typen. Klassen Binder
används för överbelastningsmatchning och argumenttvång i dessa metoder.
I följande exempel visas de tre möjliga kombinationerna av argumenttvång (typkonvertering) och medlemsval. I fall 1 krävs inget argumenttvång eller medlemsval. I fall 2 krävs endast medlemsval. I fall 3 krävs endast argumenttvång.
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();
}
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 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
Överbelastningsmatchning krävs när fler än en medlem med samma namn är tillgänglig. Metoderna Binder.BindToMethod och Binder.BindToField används för att matcha bindningen till en enskild medlem. Binder.BindToMethod
tillhandahåller även egenskapsmatchning via egenskapsåtkomsterna get
och set
.
BindToMethod
MethodBase returnerar att anropa, eller en null-referens (Nothing
i Visual Basic) om inget sådant anrop är möjligt. Returvärdet MethodBase
behöver inte vara en av dem som finns i matchningsparametern , även om det är det vanliga fallet.
När ByRef-argument finns kanske anroparen vill få tillbaka dem. Binder
Därför kan en klient mappa argumentmatrisen tillbaka till sitt ursprungliga formulär om BindToMethod
den har manipulerat argumentmatrisen. För att göra detta måste anroparen garanteras att argumentens ordning är oförändrad. När argument skickas efter namn ordnar Binder
du om argumentmatrisen, och det är vad anroparen ser. Mer information finns i Binder.ReorderArgumentArray.
Uppsättningen med tillgängliga medlemmar är de medlemmar som definieras i typen eller någon bastyp. Om BindingFlags anges returneras medlemmar med hjälpmedel i uppsättningen. Om BindingFlags.NonPublic
inte anges måste pärmen framtvinga hjälpmedelsregler. När du anger eller bindningsflaggan Public
måste du också ange Instance
eller Static
bindningsflaggan, annars returneras inga medlemmar.NonPublic
Om det bara finns en medlem i det angivna namnet krävs ingen återanrop och bindningen görs för den metoden. Fall 1 i kodexemplet illustrerar den här punkten: Endast en PrintBob
metod är tillgänglig och därför behövs ingen motringning.
Om det finns fler än en medlem i den tillgängliga uppsättningen skickas alla dessa metoder till BindToMethod
, som väljer lämplig metod och returnerar den. I fall 2 i kodexemplet finns det två metoder med namnet PrintValue
. Lämplig metod väljs av anropet till BindToMethod
.
ChangeType utför argumenttvång (typkonvertering), som konverterar de faktiska argumenten till typen av formella argument för den valda metoden. ChangeType
anropas för varje argument även om typerna matchar exakt.
I fall 3 i kodexemplet skickas ett faktiskt argument av typen String
med värdet "5,5" till en metod med ett formellt argument av typen Double
. För att anropet ska lyckas måste strängvärdet "5.5" konverteras till ett dubbelt värde. ChangeType
utför den här konverteringen.
ChangeType
utför endast förlustlösa eller vidgade tvång, enligt följande tabell.
Source type | Måltyp |
---|---|
Alla typer | Dess bastyp |
Alla typer | Gränssnitt som implementeras |
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, Enkel, Dubbel |
UInt64 | Enkel, dubbel |
Int64 | Enkel, dubbel |
Enstaka | Dubbel |
Typ av icke-referens | Referenstyp |
Klassen Type har Get
metoder som använder parametrar av typen Binder
för att matcha referenser till en viss medlem. Type.GetConstructor, Type.GetMethodoch Type.GetProperty sök efter en viss medlem av den aktuella typen genom att ange signaturinformation för medlemmen. Binder.SelectMethod och Binder.SelectProperty anropas igen för att välja den angivna signaturinformationen för lämpliga metoder.