C# Language
Riflessione
Ricerca…
introduzione
Reflection è un meccanismo in linguaggio C # per accedere alle proprietà degli oggetti dinamici in runtime. In genere, reflection viene utilizzato per recuperare le informazioni sul tipo di oggetto dinamico e sui valori degli attributi dell'oggetto. Nell'applicazione REST, ad esempio, è possibile utilizzare la riflessione per scorrere l'oggetto di risposta serializzato.
Nota: in base alle linee guida MS, il codice critico delle prestazioni dovrebbe evitare la riflessione. Vedere https://msdn.microsoft.com/en-us/library/ff647790.aspx
Osservazioni
Reflection consente al codice di accedere alle informazioni su assiemi, moduli e tipi in fase di esecuzione (esecuzione del programma). Questo può quindi essere ulteriormente utilizzato per creare, modificare o accedere dinamicamente ai tipi. I tipi includono proprietà, metodi, campi e attributi.
Ulteriori letture:
Ottieni un System.Type
Per un'istanza di un tipo:
var theString = "hello";
var theType = theString.GetType();
Dal tipo stesso:
var theType = typeof(string);
Ottieni i membri di un tipo
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.");
}
}
}
Uscita ( vedi nota sull'ordine di uscita più in basso ):
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.
Possiamo anche utilizzare GetMembers()
senza passare BindingFlags
. Ciò restituirà tutti i membri pubblici di quel tipo specifico.
Una cosa da notare che GetMembers
non restituisce i membri in alcun ordine particolare, quindi non fare mai affidamento sull'ordine che GetMembers
ti restituisce.
Ottieni un metodo e invocalo
Ottieni il metodo di istanza e invocalo
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);
}
}
Produzione:
inferno
Ottieni il metodo statico e invocalo
D'altra parte, se il metodo è statico, non è necessaria un'istanza per chiamarlo.
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
Produzione:
7,38905609893065
Ottenere e impostare le proprietà
Utilizzo di 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);
L'impostazione delle proprietà di sola lettura implementate automaticamente può essere effettuata tramite il relativo campo di supporto (in .NET Framework il nome del campo di supporto è "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);
Attributi personalizzati
Trova proprietà con un attributo personalizzato - MyAttribute
var props = t.GetProperties(BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Instance).Where(
prop => Attribute.IsDefined(prop, typeof(MyAttribute)));
Trova tutti gli attributi personalizzati su una determinata proprietà
var attributes = typeof(t).GetProperty("Name").GetCustomAttributes(false);
Enumerare tutte le classi con attributo personalizzato - MyAttribute
static IEnumerable<Type> GetTypesWithAttribute(Assembly assembly) {
foreach(Type type in assembly.GetTypes()) {
if (type.GetCustomAttributes(typeof(MyAttribute), true).Length > 0) {
yield return type;
}
}
}
Leggi il valore di un attributo personalizzato in fase di esecuzione
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);
}
}
uso
//Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass'
var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
Andare in loop attraverso tutte le proprietà di una 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));
}
Determinazione di argomenti generici di istanze di tipi generici
Se si dispone di un'istanza di un tipo generico ma per qualche motivo non si conosce il tipo specifico, è possibile determinare gli argomenti generici che sono stati utilizzati per creare questa istanza.
Supponiamo che qualcuno abbia creato un'istanza di List<T>
come tale e lo trasmetta a un metodo:
var myList = new List<int>();
ShowGenericArguments(myList);
dove ShowGenericArguments
ha questa firma:
public void ShowGenericArguments(object o)
quindi al momento della compilazione non hai idea di quali argomenti generici sono stati usati per creare o
. Reflection fornisce molti metodi per ispezionare i tipi generici. Inizialmente, possiamo determinare se il tipo di o
è del tutto generico:
public void ShowGenericArguments(object o)
{
if (o == null) return;
Type t = o.GetType();
if (!t.IsGenericType) return;
...
Type.IsGenericType
restituisce true
se il tipo è di tipo generico e false
caso contrario.
Ma questo non è tutto ciò che vogliamo sapere. List<>
è un tipo generico. Ma vogliamo solo esaminare le istanze di specifici tipi generici costruiti . Un tipo generico costruito è ad esempio un List<int>
che ha un argomento di tipo specifico per tutti i suoi parametri generici.
La classe Type
fornisce altre due proprietà, IsConstructedGenericType
e IsGenericTypeDefinition
, per distinguere questi tipi generici costruiti da definizioni di tipi generici:
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
Per enumerare gli argomenti generici di un'istanza, possiamo utilizzare il metodo GetGenericArguments()
che restituisce una matrice Type
contenente gli argomenti di tipo generico:
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);
}
Quindi la chiamata dall'alto ( ShowGenericArguments(myList)
) produce questo risultato:
Int32
Ottieni un metodo generico e invocalo
Diciamo che hai lezione con metodi generici. E devi chiamare le sue funzioni con la riflessione.
public class Sample
{
public void GenericMethod<T>()
{
// ...
}
public static void StaticMethod<T>()
{
//...
}
}
Diciamo che vogliamo chiamare GenericMethod con tipo string.
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
Per il metodo statico non hai bisogno di un'istanza. Quindi anche il primo argomento sarà nullo.
MethodInfo method = typeof(Sample).GetMethod("StaticMethod");
MethodInfo generic = method.MakeGenericMethod(typeof(string));
generic.Invoke(null, null);
Crea un'istanza di un tipo generico e invoca il suo metodo
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[] { });
Istanze di istanziazione che implementano un'interfaccia (es. Attivazione di plugin)
Se si desidera che l'applicazione per supportare un sistema di plug-in, ad esempio per caricare i plugin da assemblee situati in plugins
cartella:
interface IPlugin
{
string PluginDescription { get; }
void DoWork();
}
Questa classe si troverà in una dll separata
class HelloPlugin : IPlugin
{
public string PluginDescription => "A plugin that says Hello";
public void DoWork()
{
Console.WriteLine("Hello");
}
}
Il caricatore di plugin dell'applicazione troverà i file dll, otterrà tutti i tipi in quegli assembly che implementano IPlugin
e ne creerà istanze.
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>();
}
Creazione di un'istanza di un tipo
Il modo più semplice è usare la classe Activator
.
Tuttavia, anche se le prestazioni di Activator
sono state migliorate da .NET 3.5, l'uso di Activator.CreateInstance()
è un'opzione errata a volte, a causa di prestazioni (relativamente) basse: Test 1 , Test 2 , Test 3 ...
Con 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
È possibile passare un array di oggetti a Activator.CreateInstance
se si dispone di più di un parametro.
// 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);
Per un tipo generico
Il metodo MakeGenericType
trasforma un tipo generico aperto (come List<>
) in un tipo concreto (come List<string>
) applicando argomenti tipo ad esso.
// 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 sintassi di List<>
non è consentita al di fuori di un typeof
espressione.
Senza classe Activator
Usando la new
parola chiave (lo farà per i costruttori senza parametri)
T GetInstance<T>() where T : new()
{
T instance = new T();
return instance;
}
Utilizzo del metodo 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" });
Uso degli alberi di espressione
Gli alberi di espressione rappresentano il codice in una struttura dati ad albero, in cui ogni nodo è un'espressione. Come spiega MSDN :
L'espressione è una sequenza di uno o più operandi e zero o più operatori che possono essere valutati in un singolo valore, oggetto, metodo o spazio dei nomi. Le espressioni possono essere costituite da un valore letterale, un'invocazione di metodo, un operatore e i suoi operandi o un nome semplice. I nomi semplici possono essere il nome di una variabile, un membro del tipo, un parametro del metodo, uno spazio dei nomi o un tipo.
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.");
}
}
Potrebbe essere usato in questo modo:
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");
}
Utilizzo di FormatterServices.GetUninitializedObject
T instance = (T)FormatterServices.GetUninitializedObject(typeof(T));
In caso di utilizzo di costruttori e di inizializzatori di campo FormatterServices.GetUninitializedObject
non verranno chiamati. È pensato per essere utilizzato nei serializzatori e nei motori remoti
Ottieni un tipo per nome con namespace
Per fare questo è necessario un riferimento all'assembly che contiene il tipo. Se hai un altro tipo disponibile che sai è nello stesso assembly di quello che desideri, puoi farlo:
typeof(KnownType).Assembly.GetType(typeName);
- dove
typeName
è il nome del tipo che stai cercando (incluso lo spazio dei nomi), eKnownType
è il tipo che conosci è nello stesso assembly.
Meno efficiente ma più generale è il seguente:
Type t = null;
foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
{
if (ass.FullName.StartsWith("System."))
continue;
t = ass.GetType(typeName);
if (t != null)
break;
}
Si noti il controllo per escludere gli assembly dello spazio dei nomi System di scansione per velocizzare la ricerca. Se il tuo tipo potrebbe effettivamente essere un tipo CLR, dovrai eliminare queste due righe.
Se ti capita di avere il nome completo del tipo completo di assembly incluso l'assembly, puoi semplicemente farlo
Type.GetType(fullyQualifiedName);
Ottieni un delegato fortemente digitato su un metodo o una proprietà tramite Reflection
Quando la prestazione è un problema, invocare un metodo tramite reflection (cioè tramite il metodo MethodInfo.Invoke
) non è l'ideale. Tuttavia, è relativamente semplice ottenere un delegato con una forte caratterizzazione più performante utilizzando la funzione Delegate.CreateDelegate
. La penalizzazione delle prestazioni per l'uso della riflessione viene sostenuta solo durante il processo di creazione dei delegati. Una volta che il delegato è stato creato, c'è una penalità da prestazione minima a quella per invocarlo:
// 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));
Questa tecnica può essere estesa anche alle proprietà. Se abbiamo una classe chiamata MyClass
con una proprietà int
nome MyIntProperty
, il codice per ottenere un getter fortemente tipizzato sarebbe (l'esempio seguente presuppone che 'target' sia un'istanza valida di 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));
... e lo stesso può essere fatto per il 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);