C# Language
Reflexion
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 :
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.
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
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
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) ochKnownType
ä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);