C# Language
Reflexion
Suche…
Einführung
Reflection ist ein Mechanismus der C # -Sprache für den Zugriff auf dynamische Objekteigenschaften zur Laufzeit. Normalerweise werden über Reflection Informationen über den dynamischen Objekttyp und die Objektattributwerte abgerufen. In der REST-Anwendung kann Reflection beispielsweise verwendet werden, um durch das serialisierte Antwortobjekt zu iterieren.
Anmerkung: Gemäß den MS-Richtlinien sollte der kritische Code die Reflexion vermeiden. Siehe https://msdn.microsoft.com/de-de/library/ff647790.aspx
Bemerkungen
Mit Reflection kann der Code zur Laufzeit (Programmausführung) auf Informationen zu Baugruppen, Modulen und Typen zugreifen. Dies kann dann weiter verwendet werden, um Typen dynamisch zu erstellen, zu ändern oder auf sie zuzugreifen. Typen umfassen Eigenschaften, Methoden, Felder und Attribute.
Weiterführende Literatur:
Holen Sie sich einen System.Type
Für eine Instanz eines Typs:
var theString = "hello";
var theType = theString.GetType();
Vom Typ selbst:
var theType = typeof(string);
Holen Sie sich die Mitglieder eines Typs
using System;
using System.Reflection;
using System.Linq;
public class Program
{
public static void Main()
{
var members = typeof(object)
.GetMembers(BindingFlags.Public |
BindingFlags.Static |
BindingFlags.Instance);
foreach (var member in members)
{
bool inherited = member.DeclaringType.Equals( typeof(object).Name );
Console.WriteLine($"{member.Name} is a {member.MemberType}, " +
$"it has {(inherited ? "":"not")} been inherited.");
}
}
}
Ausgabe ( siehe Hinweis zur Ausgabereihenfolge weiter unten ):
GetType is a Method, it has not been inherited. GetHashCode is a Method, it has not been inherited. ToString is a Method, it has not been inherited. Equals is a Method, it has not been inherited. Equals is a Method, it has not been inherited. ReferenceEquals is a Method, it has not been inherited. .ctor is a Constructor, it has not been inherited.
Wir können auch GetMembers()
ohne BindingFlags
. Dadurch werden alle öffentlichen Mitglieder dieses bestimmten Typs zurückgegeben.
Beachten Sie, dass GetMembers
die Mitglieder nicht in einer bestimmten Reihenfolge zurückgibt. GetMembers
Sie sich daher niemals auf die Reihenfolge, die GetMembers
Ihnen zurückgibt.
Holen Sie sich eine Methode und rufen Sie sie auf
Holen Sie sich die Instanzmethode und rufen Sie sie auf
using System;
public class Program
{
public static void Main()
{
var theString = "hello";
var method = theString
.GetType()
.GetMethod("Substring",
new[] {typeof(int), typeof(int)}); //The types of the method arguments
var result = method.Invoke(theString, new object[] {0, 4});
Console.WriteLine(result);
}
}
Ausgabe:
Hölle
Statische Methode abrufen und aufrufen
Wenn die Methode jedoch statisch ist, benötigen Sie keine Instanz, um sie aufzurufen.
var method = typeof(Math).GetMethod("Exp");
var result = method.Invoke(null, new object[] {2});//Pass null as the first argument (no need for an instance)
Console.WriteLine(result); //You'll get e^2
Ausgabe:
7.38905609893065
Eigenschaften abrufen und einstellen
Grundnutzung:
PropertyInfo prop = myInstance.GetType().GetProperty("myProperty");
// get the value myInstance.myProperty
object value = prop.GetValue(myInstance);
int newValue = 1;
// set the value myInstance.myProperty to newValue
prop.setValue(myInstance, newValue);
Das Festlegen von schreibgeschützten, automatisch implementierten Eigenschaften kann über das Sicherungsfeld erfolgen (in .NET Framework heißt der Name des Sicherungsfelds "k__BackingField"):
// get backing field info
FieldInfo fieldInfo = myInstance.GetType()
.GetField("<myProperty>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
int newValue = 1;
// set the value of myInstance.myProperty backing field to newValue
fieldInfo.SetValue(myInstance, newValue);
Benutzerdefinierte Attribute
Suchen Sie nach Eigenschaften mit einem benutzerdefinierten Attribut - MyAttribute
var props = t.GetProperties(BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Instance).Where(
prop => Attribute.IsDefined(prop, typeof(MyAttribute)));
Finden Sie alle benutzerdefinierten Attribute einer bestimmten Eigenschaft
var attributes = typeof(t).GetProperty("Name").GetCustomAttributes(false);
Zählen Sie alle Klassen mit dem benutzerdefinierten Attribut MyAttribute
static IEnumerable<Type> GetTypesWithAttribute(Assembly assembly) {
foreach(Type type in assembly.GetTypes()) {
if (type.GetCustomAttributes(typeof(MyAttribute), true).Length > 0) {
yield return type;
}
}
}
Liest den Wert eines benutzerdefinierten Attributs zur Laufzeit
public static class AttributeExtensions
{
/// <summary>
/// Returns the value of a member attribute for any member in a class.
/// (a member is a Field, Property, Method, etc...)
/// <remarks>
/// If there is more than one member of the same name in the class, it will return the first one (this applies to overloaded methods)
/// </remarks>
/// <example>
/// Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass':
/// var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
/// </example>
/// <param name="type">The class that contains the member as a type</param>
/// <param name="MemberName">Name of the member in the class</param>
/// <param name="valueSelector">Attribute type and property to get (will return first instance if there are multiple attributes of the same type)</param>
/// <param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events</param>
/// </summary>
public static TValue GetAttribute<TAttribute, TValue>(this Type type, string MemberName, Func<TAttribute, TValue> valueSelector, bool inherit = false) where TAttribute : Attribute
{
var att = type.GetMember(MemberName).FirstOrDefault().GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault() as TAttribute;
if (att != null)
{
return valueSelector(att);
}
return default(TValue);
}
}
Verwendungszweck
//Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass'
var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
Durchlaufen aller Eigenschaften einer Klasse
Type type = obj.GetType();
//To restrict return properties. If all properties are required don't provide flag.
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
PropertyInfo[] properties = type.GetProperties(flags);
foreach (PropertyInfo property in properties)
{
Console.WriteLine("Name: " + property.Name + ", Value: " + property.GetValue(obj, null));
}
Generische Argumente für Instanzen generischer Typen ermitteln
Wenn Sie über eine Instanz eines generischen Typs verfügen, den bestimmten Typ jedoch nicht kennen, möchten Sie möglicherweise die generischen Argumente ermitteln, die zum Erstellen dieser Instanz verwendet wurden.
Angenommen, jemand hat eine Instanz von List<T>
und an eine Methode übergeben:
var myList = new List<int>();
ShowGenericArguments(myList);
wo ShowGenericArguments
diese Signatur hat:
public void ShowGenericArguments(object o)
So haben Sie zum Zeitpunkt der Kompilierung keine Ahnung, mit welchen generischen Argumenten o
. Reflection bietet eine Vielzahl von Methoden zur Überprüfung generischer Typen. Zuerst können wir feststellen, ob der Typ von o
ein generischer Typ ist:
public void ShowGenericArguments(object o)
{
if (o == null) return;
Type t = o.GetType();
if (!t.IsGenericType) return;
...
Type.IsGenericType
gibt true
zurück true
wenn es sich um einen generischen Typ handelt, Type.IsGenericType
false
.
Aber das ist nicht alles, was wir wissen wollen. List<>
selbst ist ebenfalls ein generischer Typ. Wir möchten jedoch nur Instanzen bestimmter konstruierter generischer Typen untersuchen. Ein konstruierter generischer Typ ist zum Beispiel eine List<int>
, die einen bestimmten Typ Argument für alle generischen Parameter hat.
Die Type
Klasse stellt zwei weitere Eigenschaften IsConstructedGenericType
, IsConstructedGenericType
und IsGenericTypeDefinition
, um diese IsGenericTypeDefinition
Typen von generischen Typdefinitionen zu unterscheiden:
typeof(List<>).IsGenericType // true
typeof(List<>).IsGenericTypeDefinition // true
typeof(List<>).IsConstructedGenericType// false
typeof(List<int>).IsGenericType // true
typeof(List<int>).IsGenericTypeDefinition // false
typeof(List<int>).IsConstructedGenericType// true
Um die generischen Argumente einer Instanz GetGenericArguments()
, können wir die GetGenericArguments()
Methode verwenden, die ein Type
Array zurückgibt, das die generischen GetGenericArguments()
enthält:
public void ShowGenericArguments(object o)
{
if (o == null) return;
Type t = o.GetType();
if (!t.IsConstructedGenericType) return;
foreach(Type genericTypeArgument in t.GetGenericArguments())
Console.WriteLine(genericTypeArgument.Name);
}
Der Aufruf von oben ( ShowGenericArguments(myList)
) führt also zu dieser Ausgabe:
Int32
Holen Sie sich eine generische Methode und rufen Sie sie auf
Nehmen wir an, Sie haben Klassen mit generischen Methoden. Und Sie müssen seine Funktionen mit Reflektion aufrufen.
public class Sample
{
public void GenericMethod<T>()
{
// ...
}
public static void StaticMethod<T>()
{
//...
}
}
Nehmen wir an, wir möchten GenericMethod mit dem Typ string aufrufen.
Sample sample = new Sample();//or you can get an instance via reflection
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(typeof(string));
generic.Invoke(sample, null);//Since there are no arguments, we are passing null
Für die statische Methode benötigen Sie keine Instanz. Daher ist das erste Argument auch null.
MethodInfo method = typeof(Sample).GetMethod("StaticMethod");
MethodInfo generic = method.MakeGenericMethod(typeof(string));
generic.Invoke(null, null);
Erstellen Sie eine Instanz eines generischen Typs und rufen Sie die Methode auf
var baseType = typeof(List<>);
var genericType = baseType.MakeGenericType(typeof(String));
var instance = Activator.CreateInstance(genericType);
var method = genericType.GetMethod("GetHashCode");
var result = method.Invoke(instance, new object[] { });
Instanziieren von Klassen, die eine Schnittstelle implementieren (z. B. Plugin-Aktivierung)
Wenn Sie möchten, dass Ihre Anwendung ein Plug-In-System unterstützt, z. B. zum Laden von Plug-Ins aus Assemblys im plugins
Ordner:
interface IPlugin
{
string PluginDescription { get; }
void DoWork();
}
Diese Klasse befindet sich in einer separaten DLL
class HelloPlugin : IPlugin
{
public string PluginDescription => "A plugin that says Hello";
public void DoWork()
{
Console.WriteLine("Hello");
}
}
Der Plugin-Loader Ihrer Anwendung würde die DLL-Dateien finden, alle Typen in den Assemblys IPlugin
, die IPlugin
implementieren, und Instanzen davon erstellen.
public IEnumerable<IPlugin> InstantiatePlugins(string directory)
{
var pluginAssemblyNames = Directory.GetFiles(directory, "*.addin.dll").Select(name => new FileInfo(name).FullName).ToArray();
//load the assemblies into the current AppDomain, so we can instantiate the types later
foreach (var fileName in pluginAssemblyNames)
AppDomain.CurrentDomain.Load(File.ReadAllBytes(fileName));
var assemblies = pluginAssemblyNames.Select(System.Reflection.Assembly.LoadFile);
var typesInAssembly = assemblies.SelectMany(asm => asm.GetTypes());
var pluginTypes = typesInAssembly.Where(type => typeof (IPlugin).IsAssignableFrom(type));
return pluginTypes.Select(Activator.CreateInstance).Cast<IPlugin>();
}
Eine Instanz eines Typs erstellen
Der einfachste Weg ist die Verwendung der Activator
Klasse.
Obwohl die Leistung von Activator
seit .NET 3.5 verbessert wurde, ist die Verwendung von Activator.CreateInstance()
wegen (relativ) geringer Leistung manchmal eine schlechte Option: Test 1 , Test 2 , Test 3 ...
Mit Activator
Klasse
Type type = typeof(BigInteger);
object result = Activator.CreateInstance(type); //Requires parameterless constructor.
Console.WriteLine(result); //Output: 0
result = Activator.CreateInstance(type, 123); //Requires a constructor which can receive an 'int' compatible argument.
Console.WriteLine(result); //Output: 123
Sie können ein Objektarray an Activator.CreateInstance
wenn Sie mehr als einen Parameter haben.
// With a constructor such as MyClass(int, int, string)
Activator.CreateInstance(typeof(MyClass), new object[] { 1, 2, "Hello World" });
Type type = typeof(someObject);
var instance = Activator.CreateInstance(type);
Für einen generischen Typ
Die MakeGenericType
Methode MakeGenericType
einen offenen generischen Typ (wie List<>
) in einen konkreten Typ (wie List<string>
) um, indem MakeGenericType
werden.
// generic List with no parameters
Type openType = typeof(List<>);
// To create a List<string>
Type[] tArgs = { typeof(string) };
Type target = openType.MakeGenericType(tArgs);
// Create an instance - Activator.CreateInstance will call the default constructor.
// This is equivalent to calling new List<string>().
List<string> result = (List<string>)Activator.CreateInstance(target);
Die List<>
-Syntax ist außerhalb eines typeof
Ausdrucks nicht zulässig.
Ohne Activator
Klasse
Verwenden eines new
Schlüsselworts (funktioniert für parameterlose Konstruktoren)
T GetInstance<T>() where T : new()
{
T instance = new T();
return instance;
}
Invoke-Methode verwenden
// Get the instance of the desired constructor (here it takes a string as a parameter).
ConstructorInfo c = typeof(T).GetConstructor(new[] { typeof(string) });
// Don't forget to check if such constructor exists
if (c == null)
throw new InvalidOperationException(string.Format("A constructor for type '{0}' was not found.", typeof(T)));
T instance = (T)c.Invoke(new object[] { "test" });
Ausdrucksbäume verwenden
Ausdrucksbäume repräsentieren Code in einer baumartigen Datenstruktur, wobei jeder Knoten ein Ausdruck ist. Wie MSDN erklärt:
Ausdruck ist eine Folge von einem oder mehreren Operanden und null oder mehr Operatoren, die für einen einzelnen Wert, ein Objekt, eine Methode oder einen Namespace ausgewertet werden können. Ausdrücke können aus einem Literalwert, einem Methodenaufruf, einem Operator und seinen Operanden oder einem einfachen Namen bestehen. Einfache Namen können der Name einer Variablen, eines Typmitglieds, eines Methodenparameters, eines Namespaces oder eines Typs sein.
public class GenericFactory<TKey, TType>
{
private readonly Dictionary<TKey, Func<object[], TType>> _registeredTypes; // dictionary, that holds constructor functions.
private object _locker = new object(); // object for locking dictionary, to guarantee thread safety
public GenericFactory()
{
_registeredTypes = new Dictionary<TKey, Func<object[], TType>>();
}
/// <summary>
/// Find and register suitable constructor for type
/// </summary>
/// <typeparam name="TType"></typeparam>
/// <param name="key">Key for this constructor</param>
/// <param name="parameters">Parameters</param>
public void Register(TKey key, params Type[] parameters)
{
ConstructorInfo ci = typeof(TType).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.HasThis, parameters, new ParameterModifier[] { }); // Get the instance of ctor.
if (ci == null)
throw new InvalidOperationException(string.Format("Constructor for type '{0}' was not found.", typeof(TType)));
Func<object[], TType> ctor;
lock (_locker)
{
if (!_registeredTypes.TryGetValue(key, out ctor)) // check if such ctor already been registered
{
var pExp = Expression.Parameter(typeof(object[]), "arguments"); // create parameter Expression
var ctorParams = ci.GetParameters(); // get parameter info from constructor
var argExpressions = new Expression[ctorParams.Length]; // array that will contains parameter expessions
for (var i = 0; i < parameters.Length; i++)
{
var indexedAcccess = Expression.ArrayIndex(pExp, Expression.Constant(i));
if (!parameters[i].IsClass && !parameters[i].IsInterface) // check if parameter is a value type
{
var localVariable = Expression.Variable(parameters[i], "localVariable"); // if so - we should create local variable that will store paraameter value
var block = Expression.Block(new[] { localVariable },
Expression.IfThenElse(Expression.Equal(indexedAcccess, Expression.Constant(null)),
Expression.Assign(localVariable, Expression.Default(parameters[i])),
Expression.Assign(localVariable, Expression.Convert(indexedAcccess, parameters[i]))
),
localVariable
);
argExpressions[i] = block;
}
else
argExpressions[i] = Expression.Convert(indexedAcccess, parameters[i]);
}
var newExpr = Expression.New(ci, argExpressions); // create expression that represents call to specified ctor with the specified arguments.
_registeredTypes.Add(key, Expression.Lambda(newExpr, new[] { pExp }).Compile() as Func<object[], TType>); // compile expression to create delegate, and add fucntion to dictionary
}
}
}
/// <summary>
/// Returns instance of registered type by key.
/// </summary>
/// <typeparam name="TType"></typeparam>
/// <param name="key"></param>
/// <param name="args"></param>
/// <returns></returns>
public TType Create(TKey key, params object[] args)
{
Func<object[], TType> foo;
if (_registeredTypes.TryGetValue(key, out foo))
{
return (TType)foo(args);
}
throw new ArgumentException("No type registered for this key.");
}
}
Könnte so verwendet werden:
public class TestClass
{
public TestClass(string parameter)
{
Console.Write(parameter);
}
}
public void TestMethod()
{
var factory = new GenericFactory<string, TestClass>();
factory.Register("key", typeof(string));
TestClass newInstance = factory.Create("key", "testParameter");
}
Verwenden von FormatterServices.GetUninitializedObject
T instance = (T)FormatterServices.GetUninitializedObject(typeof(T));
Bei Verwendung von FormatterServices.GetUninitializedObject
Konstruktoren und Feldinitialisierer nicht aufgerufen. Es ist für die Verwendung in Serialisierern und Remoting-Engines vorgesehen
Rufen Sie einen Typ mit Namen mit Namespace ab
Dazu benötigen Sie einen Verweis auf die Assembly, die den Typ enthält. Wenn Sie über einen anderen Typ verfügen, von dem Sie wissen, dass er sich in derselben Assembly befindet, wie Sie möchten, können Sie Folgendes tun:
typeof(KnownType).Assembly.GetType(typeName);
- Dabei ist
typeName
der Name destypeName
Typs (einschließlich des Namespaces) undKnownType
der Typ, von dem Sie wissen, dass er sich in derselben Assembly befindet.
Weniger effizient, aber allgemeiner ist wie folgt:
Type t = null;
foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
{
if (ass.FullName.StartsWith("System."))
continue;
t = ass.GetType(typeName);
if (t != null)
break;
}
Beachten Sie die Option, um das Scannen von System-Namespace-Assemblys auszuschließen, um die Suche zu beschleunigen. Wenn Ihr Typ tatsächlich ein CLR-Typ ist, müssen Sie diese beiden Zeilen löschen.
Wenn Sie den vollständig Assembly-qualifizierten Typnamen einschließlich der Assembly haben, können Sie diesen einfach mitbestellen
Type.GetType(fullyQualifiedName);
Erhalten Sie einen stark typisierten Delegierten über Reflection zu einer Methode oder Eigenschaft
Wenn Leistung ein Problem ist, ist das Aufrufen einer Methode über die Reflektion (dh über die MethodInfo.Invoke
Methode) nicht ideal. Es ist jedoch relativ einfach, mit der Funktion Delegate.CreateDelegate
einen leistungsfähigeren Delegate.CreateDelegate
. Die Leistungseinbußen für die Verwendung von Reflektionen treten nur während des Delegierungserstellungsprozesses auf. Nachdem der Delegat erstellt wurde, gibt es wenig oder gar keine Leistungseinbußen, wenn er aufgerufen wird:
// Get a MethodInfo for the Math.Max(int, int) method...
var maxMethod = typeof(Math).GetMethod("Max", new Type[] { typeof(int), typeof(int) });
// Now get a strongly-typed delegate for Math.Max(int, int)...
var stronglyTypedDelegate = (Func<int, int, int>)Delegate.CreateDelegate(typeof(Func<int, int, int>), null, maxMethod);
// Invoke the Math.Max(int, int) method using the strongly-typed delegate...
Console.WriteLine("Max of 3 and 5 is: {0}", stronglyTypedDelegate(3, 5));
Diese Technik kann auch auf Eigenschaften erweitert werden. Wenn wir eine Klasse namens MyClass
mit einer int
Eigenschaft namens MyIntProperty
, MyIntProperty
der Code zum MyIntProperty
eines stark typisierten Getters (im folgenden Beispiel wird davon MyIntProperty
, dass 'target' eine gültige Instanz von MyClass
):
// Get a MethodInfo for the MyClass.MyIntProperty getter...
var theProperty = typeof(MyClass).GetProperty("MyIntProperty");
var theGetter = theProperty.GetGetMethod();
// Now get a strongly-typed delegate for MyIntProperty that can be executed against any MyClass instance...
var stronglyTypedGetter = (Func<MyClass, int>)Delegate.CreateDelegate(typeof(Func<MyClass, int>), theGetter);
// Invoke the MyIntProperty getter against MyClass instance 'target'...
Console.WriteLine("target.MyIntProperty is: {0}", stronglyTypedGetter(target));
... und dasselbe gilt für den Setter:
// Get a MethodInfo for the MyClass.MyIntProperty setter...
var theProperty = typeof(MyClass).GetProperty("MyIntProperty");
var theSetter = theProperty.GetSetMethod();
// Now get a strongly-typed delegate for MyIntProperty that can be executed against any MyClass instance...
var stronglyTypedSetter = (Action<MyClass, int>)Delegate.CreateDelegate(typeof(Action<MyClass, int>), theSetter);
// Set MyIntProperty to 5...
stronglyTypedSetter(target, 5);