Sök…


Introduktion

Reflektion är en C # språkmekanism för åtkomst till dynamiska objektegenskaper under körning. Normalt används reflektion för att hämta informationen om värden för dynamisk objekttyp och objektattribut. I REST-applikation, till exempel, kan reflektion användas för att iterera genom serialiserade svarobjekt.

Anmärkning: Enligt MS-riktlinjerna bör prestanda kritisk kod undvika reflektion. Se https://msdn.microsoft.com/en-us/library/ff647790.aspx

Anmärkningar

Reflektion tillåter kod att få åtkomst till information om enheter, moduler och typer vid körning (programutförande). Detta kan sedan användas ytterligare för att dynamiskt skapa, ändra eller komma åt typer. Typerna inkluderar egenskaper, metoder, fält och attribut.

Vidare läsning :

Reflektion (C #)

Reflektion i .Net Framework

Skaffa ett system

För en instans av en typ:

var theString = "hello";
var theType = theString.GetType();

Från själva typen:

var theType = typeof(string);

Få medlemmar av en typ

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.");
    }
  }
}

Utgång ( se anmärkning om utgångsorder längre ner ):

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.

Vi kan också använda GetMembers() utan att passera några BindingFlags . Detta kommer att returnera alla offentliga medlemmar av den specifika typen.

En sak att notera att GetMembers inte returnerar medlemmarna i någon särskild ordning, så lita aldrig på beställningen att GetMembers returnerar dig.

Visa demo

Skaffa en metod och åberopa den

Hämta instansmetod och åberopa den

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

Produktion:

Helvete

Visa demo

Hämta statisk metod och åberopa den

Å andra sidan, om metoden är statisk, behöver du inte en instans för att kalla den.

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

Produktion:

7,38905609893065

Visa demo

Få och ställa in egenskaper

Grundläggande användning:

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

Ställa in skrivskyddade automatiskt implementerade egenskaper kan göras genom dets stödfält (i. NET Framework-namnet på stödfältet är "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);

Anpassade attribut

Hitta egenskaper med ett anpassat attribut - MyAttribute

var props = t.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | 
            BindingFlags.Instance).Where(
            prop => Attribute.IsDefined(prop, typeof(MyAttribute)));

Hitta alla anpassade attribut på en given egenskap

var attributes = typeof(t).GetProperty("Name").GetCustomAttributes(false);

Räkna upp alla klasser med anpassat 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;
        }
    }
}

Läs värdet på ett anpassat attribut vid körning

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

Användande

//Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass'
var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);

Looping genom alla egenskaper hos en klass

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

Bestämma generiska argument för instanser av generiska typer

Om du har en instans av en generisk typ men av någon anledning inte känner till den specifika typen kanske du vill bestämma de generiska argument som användes för att skapa den här instansen.

Låt oss säga att någon skapade en instans av List<T> så och överför den till en metod:

var myList = new List<int>();
ShowGenericArguments(myList);

där ShowGenericArguments har denna signatur:

public void ShowGenericArguments(object o)

så vid sammanställningstiden har du ingen aning om vilka generiska argument som har använts för att skapa o . Reflektion ger många metoder för att inspektera generiska typer. Till att börja med kan vi avgöra om typen av o är en generisk typ alls:

public void ShowGenericArguments(object o)
{
    if (o == null) return;

    Type t = o.GetType();
    if (!t.IsGenericType) return;
    ...

Type.IsGenericType returnerar true om typen är en generisk typ och false om inte.

Men det är inte allt vi vill veta. List<> sig är också en generisk typ. Men vi vill bara undersöka fall av specifika konstruerade generiska typer. En konstruerad generisk typ är till exempel en List<int> som har ett specifikt typargument för alla dess generiska parametrar .

Klassen Type ger ytterligare två egenskaper, IsConstructedGenericType och IsGenericTypeDefinition , för att skilja dessa konstruerade generiska typer från generiska typdefinitioner:

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

För att räkna upp de generiska argumenten för en instans kan vi använda GetGenericArguments() som returnerar en Type som innehåller de generiska typargumenten:

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

Så samtalet ovanifrån ( ShowGenericArguments(myList) ) resulterar i denna utgång:

Int32

Skaffa en generisk metod och åberopa den

Låt oss säga att du har klass med generiska metoder. Och du måste ringa dess funktioner med reflektion.

public class Sample
{
    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}

Låt oss säga att vi vill ringa GenericMethod med typsträng.

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 den statiska metoden behöver du inte en instans. Därför kommer det första argumentet också att vara noll.

MethodInfo method = typeof(Sample).GetMethod("StaticMethod");
MethodInfo generic = method.MakeGenericMethod(typeof(string));
generic.Invoke(null, null);

Skapa en instans av en generisk typ och åberopa den metod

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[] { });

Instantiera klasser som implementerar ett gränssnitt (t.ex. pluginaktivering)

Om du vill att din applikation ska stödja ett plugin-system, till exempel för att ladda plug-ins från enheter som finns i plugins mappen:

interface IPlugin
{
    string PluginDescription { get; }
    void DoWork();
}

Denna klass skulle placeras i en separat dll

class HelloPlugin : IPlugin
{
    public string PluginDescription => "A plugin that says Hello";
    public void DoWork()
    {
        Console.WriteLine("Hello");
    }
}

Din applikations plugin-loader skulle hitta dll-filerna, få alla typer i de enheter som implementerar IPlugin och skapa instanser av dessa.

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

Skapa en instans av en typ

Det enklaste sättet är att använda Activator klassen.

Trots att Activator prestanda har förbättrats sedan .NET 3.5, är Activator.CreateInstance() dåligt alternativ ibland på grund av (relativt) låg prestanda: Test 1 , Test 2 , Test 3 ...


Med Activator klass

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

Du kan vidarebefordra ett objektuppsättning till Activator.CreateInstance om du har mer än en parameter.

// 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 en generisk typ

Metoden MakeGenericType förvandlar en öppen generisk typ (som List<> ) till en konkret typ (som List<string> ) genom att tillämpa typargument på den.

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

List<> är inte tillåten utanför ett typeof uttryck.


Utan Activator klass

Använda ett new nyckelord (kommer att göra för parameterlösa konstruktörer)

T GetInstance<T>() where T : new()
{
    T instance = new T();
    return instance;
}

Med hjälp av Invoke-metoden

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

Använda uttrycksträd

Uttrycksträd representerar kod i en trädliknande datastruktur, där varje nod är ett uttryck. Som MSDN förklarar:

Expression är en sekvens av en eller flera operander och noll eller fler operatörer som kan utvärderas till ett enda värde, objekt, metod eller namnutrymme. Uttryck kan bestå av ett bokstavsvärde, en metodkallelse, en operatör och dess operander eller ett enkelt namn. Enkla namn kan vara namnet på en variabel, typmedlem, metodparameter, namnutrymme eller typ.

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.");
        }
    }

Kan användas så här:

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

Använda FormatterServices.GetUninitializedObject

T instance = (T)FormatterServices.GetUninitializedObject(typeof(T));

Vid användning av FormatterServices.GetUninitializedObject konstruktörer och fältinitierare kommer inte att ringas. Det är tänkt att användas i serieverkare och ombyggnad av motorer

Skaffa en typ efter namn med namnutrymme

För att göra detta behöver du en hänvisning till enheten som innehåller typen. Om du har en annan typ tillgänglig som du vet är i samma enhet som den du vill kan du göra detta:

typeof(KnownType).Assembly.GetType(typeName);
  • där typeName är namnet på den typ du letar efter (inklusive namnutrymmet) och KnownType är den typ du känner till i samma enhet.

Mindre effektiv men mer allmän är följande:

Type t = null;
foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
{
    if (ass.FullName.StartsWith("System."))
        continue;
    t = ass.GetType(typeName);
    if (t != null)
        break;
}

Lägg märke till kontrollen för att utesluta skanning Systemnamnområden för att påskynda sökningen. Om din typ faktiskt kan vara en CLR-typ måste du ta bort dessa två rader.

Om du råkar ha det helt monteringskvalificerade typnamnet inklusive enheten kan du helt enkelt få det med

Type.GetType(fullyQualifiedName);

Få en starkt typ delegerad till en metod eller egendom via reflektion

När prestanda är en oro, är det inte idealiskt att åberopa en metod via reflektion (dvs via MethodInfo.Invoke metoden). Det är emellertid relativt enkelt att erhålla en mer framträdande stark-typ delegat med funktionen Delegate.CreateDelegate . Prestationsstraffet för att använda reflektion uppstår endast under skapandet av delegerade. När delegaten har skapats finns det ingen prestationsstraff för att åberopa den:

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

Denna teknik kan också utvidgas till egenskaper. Om vi har en klass som heter MyClass med en int egenskap som heter MyIntProperty , skulle koden för att få en starkt typ getter vara (följande exempel antar att "target" är en giltig instans av 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));

... och detsamma kan göras för setteren:

// 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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow