C# Language
Réflexion
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:
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.
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
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
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);
où 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);
- où
typeName
est le nom du type que vous recherchez (y compris l'espace de noms) etKnownType
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);