Recherche…


Introduction

Reflection est un mécanisme de langage C # permettant d'accéder aux propriétés des objets dynamiques lors de l'exécution. En règle générale, la réflexion est utilisée pour récupérer les informations sur le type d'objet dynamique et les valeurs d'attribut d'objet. Dans l'application REST, par exemple, la réflexion peut être utilisée pour parcourir un objet de réponse sérialisé.

Remarque: Selon les directives MS, le code de performance critique doit éviter la réflexion. Voir https://msdn.microsoft.com/en-us/library/ff647790.aspx

Remarques

Reflection permet au code d'accéder aux informations sur les assemblys, les modules et les types au moment de l'exécution (exécution du programme). Cela peut ensuite être utilisé pour créer, modifier ou accéder de manière dynamique aux types. Les types incluent les propriétés, les méthodes, les champs et les attributs.

Lectures complémentaires:

Réflexion (C #)

Réflexion dans le cadre .Net

Obtenir un System.Type

Pour une instance d'un type:

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

Du type lui-même:

var theType = typeof(string);

Obtenir les membres d'un type

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

Sortie ( voir note sur l'ordre de sortie plus bas ):

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.

Nous pouvons également utiliser GetMembers() sans passer par BindingFlags . Cela retournera tous les membres publics de ce type spécifique.

Une chose à noter que GetMembers ne GetMembers pas les membres dans un ordre particulier, alors ne comptez jamais sur l'ordre que GetMembers vous renvoie.

Voir la démo

Obtenez une méthode et invoquez-la

Obtenir la méthode d'instance et l'invoquer

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

Sortie:

enfer

Voir la démo

Obtenez la méthode statique et invoquez-la

D'un autre côté, si la méthode est statique, vous n'avez pas besoin d'une instance pour l'appeler.

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

Sortie:

7.38905609893065

Voir la démo

Obtenir et définir les propriétés

Utilisation de base:

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

La définition des propriétés implémentées automatiquement en lecture seule peut être effectuée via son champ de sauvegarde (dans le nom du champ de sauvegarde de .NET Framework, "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);

Attributs personnalisés

Rechercher des propriétés avec un attribut personnalisé - MyAttribute

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

Rechercher tous les attributs personnalisés sur une propriété donnée

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

Énumérez toutes les classes avec un attribut personnalisé - MyAttribute

static IEnumerable<Type> GetTypesWithAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(MyAttribute), true).Length > 0) {
            yield return type;
        }
    }
}

Valeur de lecture d'un attribut personnalisé à l'exécution

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

Usage

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

Faire le tour de toutes les propriétés d'une classe

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

Détermination des arguments génériques des instances de types génériques

Si vous avez une instance d'un type générique mais que, pour une raison quelconque, vous ne connaissez pas le type spécifique, vous souhaiterez peut-être déterminer les arguments génériques utilisés pour créer cette instance.

Disons que quelqu'un a créé une instance de List<T> comme ça et la passe à une méthode:

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

ShowGenericArguments a cette signature:

public void ShowGenericArguments(object o)

au moment de la compilation, vous n'avez aucune idée des arguments génériques utilisés pour créer o . Reflection fournit de nombreuses méthodes pour inspecter les types génériques. Au début, nous pouvons déterminer si le type de o est un type générique du tout:

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

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

Type.IsGenericType renvoie true si le type est un type générique et false si ce n'est pas le cas.

Mais ce n'est pas tout ce que nous voulons savoir. List<> est lui-même un type générique. Mais nous voulons seulement examiner les instances de types génériques construits spécifiques. Un type générique construit est par exemple un List<int> qui possède un argument de type spécifique pour tous ses paramètres génériques.

La classe Type fournit deux propriétés supplémentaires, IsConstructedGenericType et IsGenericTypeDefinition , pour distinguer ces types génériques construits des définitions de type génériques:

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

Pour énumérer les arguments génériques d'une instance, nous pouvons utiliser la méthode GetGenericArguments() qui renvoie un tableau Type contenant les arguments de type générique:

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

L'appel du dessus ( ShowGenericArguments(myList) ) aboutit à cette sortie:

Int32

Obtenez une méthode générique et invoquez-la

Disons que vous avez une classe avec des méthodes génériques. Et vous devez appeler ses fonctions avec réflexion.

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

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

Disons que nous voulons appeler la méthode GenericMethod avec la chaîne de type.

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

Pour la méthode statique, vous n'avez pas besoin d'instance. Par conséquent, le premier argument sera également nul.

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

Créer une instance d'un type générique et appeler sa méthode

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

Classes d'instanciation qui implémentent une interface (par exemple, activation de plug-in)

Si vous souhaitez que votre application prenne en charge un système de plug-in, par exemple pour charger des plug-ins à partir d'assemblages situés dans le dossier plugins :

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

Cette classe serait située dans une DLL séparée

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

Le chargeur de plug-in de votre application trouvera les fichiers dll, récupérera tous les types dans les assemblys qui implémentent IPlugin et en créera des instances.

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

Création d'une instance d'un type

Le moyen le plus simple est d'utiliser la classe Activator .

Cependant, même si les performances d' Activator ont été améliorées depuis .NET 3.5, l'utilisation d' Activator.CreateInstance() est parfois une mauvaise option en raison de performances (relativement) faibles: Test 1 , Test 2 , Test 3 ...


Avec la classe Activator

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

Vous pouvez passer un tableau d'objets à Activator.CreateInstance si vous avez plusieurs paramètres.

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

Pour un type générique

La méthode MakeGenericType transforme un type générique ouvert (comme List<> ) en un type concret (comme List<string> ) en lui appliquant des arguments de type.

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

La syntaxe List<> n'est pas autorisée en dehors d'une expression de typeof .


Sans classe d' Activator

Utiliser un new mot-clé (fera pour les constructeurs sans paramètre)

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

Utiliser la méthode Invoke

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

Utiliser des arbres d'expression

Les arbres d'expression représentent du code dans une structure de données arborescente, où chaque nœud est une expression. Comme MSDN explique:

L'expression est une séquence d'un ou plusieurs opérandes et de zéro ou plusieurs opérateurs pouvant être évalués en une seule valeur, objet, méthode ou espace de noms. Les expressions peuvent consister en une valeur littérale, une invocation de méthode, un opérateur et ses opérandes ou un nom simple. Les noms simples peuvent être le nom d'une variable, d'un membre de type, d'un paramètre de méthode, d'un espace de noms ou d'un type.

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

Peut être utilisé comme ceci:

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

Utilisation de FormatterServices.GetUninitializedObject

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

En cas d'utilisation de constructeurs FormatterServices.GetUninitializedObject et d'initialisateurs de champs, ils ne seront pas appelés. Il est destiné à être utilisé dans les sérialiseurs et les moteurs à distance

Obtenir un type par nom avec un espace de noms

Pour ce faire, vous avez besoin d'une référence à l'assembly qui contient le type. Si vous avez un autre type disponible que vous savez être dans le même assemblage que celui que vous voulez, vous pouvez le faire:

typeof(KnownType).Assembly.GetType(typeName);
  • typeName est le nom du type que vous recherchez (y compris l'espace de noms) et KnownType est le type que vous connaissez dans le même assembly.

Moins efficace mais plus général est comme suit:

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

Notez la coche pour exclure les assemblys de noms de systèmes d'analyse afin d'accélérer la recherche. Si votre type peut effectivement être un type CLR, vous devrez supprimer ces deux lignes.

Si vous avez le nom de type entièrement assemblé, y compris l'assemblage, vous pouvez simplement l'obtenir avec

Type.GetType(fullyQualifiedName);

Obtenir un délégué fortement typé sur une méthode ou une propriété via la réflexion

Lorsque les performances MethodInfo.Invoke problème, l'appel d'une méthode via la réflexion (c'est-à-dire via la méthode MethodInfo.Invoke ) n'est pas idéal. Cependant, il est relativement simple d'obtenir un délégué plus typé et plus performant à l'aide de la fonction Delegate.CreateDelegate . La pénalité de performance liée à l'utilisation de la réflexion ne survient que pendant le processus de création de délégué. Une fois le délégué créé, il y a peu ou pas de performance pour l'invoquer:

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

Cette technique peut également être étendue aux propriétés. Si nous avons une classe nommée MyClass avec une propriété int nommée MyIntProperty , le code pour obtenir un getter fortement typé serait (l'exemple suivant suppose que 'target' est une instance valide de 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));

... et la même chose peut être faite pour le passeur:

// 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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow