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:

Reflexion (C #)

Reflexion in .Net Framework

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.

Demo anzeigen

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

Demo anzeigen

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

Demo anzeigen

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 des typeName Typs (einschließlich des Namespaces) und KnownType 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);


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow