C# Language
Reflexión
Buscar..
Introducción
La reflexión es un mecanismo de lenguaje C # para acceder a las propiedades dinámicas de los objetos en tiempo de ejecución. Normalmente, la reflexión se utiliza para obtener información sobre el tipo de objeto dinámico y los valores de atributo de objeto. En la aplicación REST, por ejemplo, la reflexión podría usarse para iterar a través del objeto de respuesta serializado.
Observación: De acuerdo con las directrices de MS, el código crítico de rendimiento debe evitar la reflexión. Consulte https://msdn.microsoft.com/en-us/library/ff647790.aspx
Observaciones
Reflection permite que el código acceda a información sobre los ensamblajes, módulos y tipos en tiempo de ejecución (ejecución del programa). Esto puede luego ser usado para crear, modificar o acceder dinámicamente tipos. Los tipos incluyen propiedades, métodos, campos y atributos.
Otras lecturas :
Obtener un System.Type
Para una instancia de un tipo:
var theString = "hello";
var theType = theString.GetType();
Del propio tipo:
var theType = typeof(string);
Obtener los miembros de 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.");
}
}
}
Salida ( ver nota sobre el orden de salida más abajo ):
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.
También podemos usar GetMembers()
sin pasar BindingFlags
. Esto devolverá a todos los miembros públicos de ese tipo específico.
Una cosa a tener en cuenta es que GetMembers
no devuelve a los miembros en ningún orden en particular, por lo que nunca confíe en el pedido que GetMembers
devuelve GetMembers
.
Consigue un método e invocalo.
Obtener el método de instancia e invocarlo.
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);
}
}
Salida:
infierno
Obtener el método estático e invocarlo.
Por otro lado, si el método es estático, no necesita una instancia para llamarlo.
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
Salida:
7.38905609893065
Obteniendo y configurando propiedades
Uso básico:
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 configuración de las propiedades implementadas automáticamente de solo lectura se puede hacer a través de su campo de respaldo (en .NET Framework, el nombre del campo de respaldo es "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);
Atributos personalizados
Buscar propiedades con un atributo personalizado - MyAttribute
var props = t.GetProperties(BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Instance).Where(
prop => Attribute.IsDefined(prop, typeof(MyAttribute)));
Encuentra todos los atributos personalizados en una propiedad dada
var attributes = typeof(t).GetProperty("Name").GetCustomAttributes(false);
Enumerar todas las clases con atributo personalizado - MyAttribute
static IEnumerable<Type> GetTypesWithAttribute(Assembly assembly) {
foreach(Type type in assembly.GetTypes()) {
if (type.GetCustomAttributes(typeof(MyAttribute), true).Length > 0) {
yield return type;
}
}
}
Leer el valor de un atributo personalizado en tiempo de ejecución
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);
Recorriendo todas las propiedades de una clase.
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));
}
Determinación de argumentos genéricos de instancias de tipos genéricos.
Si tiene una instancia de un tipo genérico, pero por alguna razón no conoce el tipo específico, es posible que desee determinar los argumentos genéricos que se usaron para crear esta instancia.
Digamos que alguien creó una instancia de la List<T>
así y la pasa a un método:
var myList = new List<int>();
ShowGenericArguments(myList);
donde ShowGenericArguments
tiene esta firma:
public void ShowGenericArguments(object o)
así que en el momento de la compilación no tiene idea de qué argumentos genéricos se han utilizado para crear o
. La reflexión proporciona muchos métodos para inspeccionar tipos genéricos. Al principio, podemos determinar si el tipo de o
es un tipo genérico:
public void ShowGenericArguments(object o)
{
if (o == null) return;
Type t = o.GetType();
if (!t.IsGenericType) return;
...
Type.IsGenericType
devuelve true
si el tipo es un tipo genérico y false
si no lo es.
Pero esto no es todo lo que queremos saber. List<>
sí misma es un tipo genérico, también. Pero solo queremos examinar instancias de tipos genéricos construidos específicos. Un tipo genérico construido es, por ejemplo, una List<int>
que tiene un argumento de tipo específico para todos sus parámetros genéricos.
La clase Type
proporciona dos propiedades más, IsConstructedGenericType
e IsGenericTypeDefinition
, para distinguir estos tipos genéricos construidos de definiciones de tipos genéricos:
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
Para enumerar los argumentos genéricos de una instancia, podemos usar el método GetGenericArguments()
que devuelve una matriz de Type
contiene los argumentos de tipo genérico:
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);
}
Así que la llamada desde arriba ( ShowGenericArguments(myList)
) da como resultado esta salida:
Int32
Consigue un método genérico e invocalo.
Digamos que tienes clase con métodos genéricos. Y necesitas llamar sus funciones con reflexión.
public class Sample
{
public void GenericMethod<T>()
{
// ...
}
public static void StaticMethod<T>()
{
//...
}
}
Digamos que queremos llamar al GenericMethod con el tipo de cadena.
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
Para el método estático no necesita una instancia. Por lo tanto el primer argumento también será nulo.
MethodInfo method = typeof(Sample).GetMethod("StaticMethod");
MethodInfo generic = method.MakeGenericMethod(typeof(string));
generic.Invoke(null, null);
Crear una instancia de un tipo genérico e invocar su método.
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[] { });
Clases de creación de instancias que implementan una interfaz (por ejemplo, activación de plugin)
Si desea que su aplicación admita un sistema de complementos, por ejemplo, para cargar complementos de ensamblajes ubicados en la carpeta de plugins
:
interface IPlugin
{
string PluginDescription { get; }
void DoWork();
}
Esta clase estaría ubicada en una dll separada.
class HelloPlugin : IPlugin
{
public string PluginDescription => "A plugin that says Hello";
public void DoWork()
{
Console.WriteLine("Hello");
}
}
El cargador de complementos de su aplicación encontraría los archivos dll, obtendría todos los tipos en los ensamblajes que implementan IPlugin
y creará instancias de esos.
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>();
}
Creando una instancia de un tipo
La forma más sencilla es usar la clase Activator
.
Sin embargo, a pesar de que el rendimiento de Activator
se ha mejorado desde .NET 3.5, el uso de Activator.CreateInstance()
es a veces una mala opción, debido a un rendimiento (relativamente) bajo: Test 1 , Test 2 , Test 3 ...
Con clase 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
Puede pasar una matriz de objetos a Activator.CreateInstance
si tiene más de un parámetro.
// 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);
Para un tipo genérico
El método MakeGenericType
convierte un tipo genérico abierto (como List<>
) en un tipo concreto (como List<string>
) al aplicarle argumentos de tipo.
// 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 sintaxis de List<>
no está permitida fuera de una expresión typeof
.
Sin clase Activator
Usando new
palabra clave (servirá para constructores sin parámetros)
T GetInstance<T>() where T : new()
{
T instance = new T();
return instance;
}
Utilizando el método 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" });
Usando árboles de expresión
Los árboles de expresión representan el código en una estructura de datos similar a un árbol, donde cada nodo es una expresión. Como explica MSDN :
La expresión es una secuencia de uno o más operandos y cero o más operadores que se pueden evaluar a un solo valor, objeto, método o espacio de nombres. Las expresiones pueden consistir en un valor literal, una invocación de método, un operador y sus operandos, o un nombre simple. Los nombres simples pueden ser el nombre de una variable, miembro de tipo, parámetro de método, espacio de nombres o 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.");
}
}
Podría ser usado así:
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");
}
Usando FormatterServices.GetUninitializedObject
T instance = (T)FormatterServices.GetUninitializedObject(typeof(T));
En caso de utilizar FormatterServices.GetUninitializedObject
, no se FormatterServices.GetUninitializedObject
constructores ni los inicializadores de campo. Está destinado a ser usado en serializadores y motores remotos.
Obtener un tipo por nombre con espacio de nombres
Para hacer esto necesita una referencia al conjunto que contiene el tipo. Si tiene otro tipo disponible que sabe que está en el mismo ensamblaje que el que desea, puede hacer esto:
typeof(KnownType).Assembly.GetType(typeName);
- donde
typeName
es el nombre del tipo que está buscando (incluido el espacio de nombres), yKnownType
es el tipo que sabe que se encuentra en el mismo ensamblaje.
Menos eficiente pero más general es el siguiente:
Type t = null;
foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
{
if (ass.FullName.StartsWith("System."))
continue;
t = ass.GetType(typeName);
if (t != null)
break;
}
Observe la marca de verificación para excluir los conjuntos de espacios de nombres del sistema de escaneo para acelerar la búsqueda. Si su tipo puede ser realmente un tipo CLR, tendrá que eliminar estas dos líneas.
Si resulta que tiene el nombre de tipo totalmente calificado para el ensamblaje, incluido el ensamblaje, simplemente puede obtenerlo con
Type.GetType(fullyQualifiedName);
Obtenga un delegado de tipo fuerte en un método o propiedad a través de la reflexión
Cuando el rendimiento es una preocupación, invocar un método a través de la reflexión (es decir, a través del método MethodInfo.Invoke
) no es ideal. Sin embargo, es relativamente sencillo obtener un delegado con un tipo de ejecución más potente utilizando la función Delegate.CreateDelegate
. La penalización de rendimiento por usar la reflexión se incurre solo durante el proceso de creación de delegados. Una vez que se crea el delegado, hay poca penalización de rendimiento para 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));
Esta técnica también puede extenderse a las propiedades. Si tenemos una clase llamada MyClass
con una propiedad int
llamada MyIntProperty
, el código para obtener un getter de tipo fuerte sería (el siguiente ejemplo asume que 'target' es una instancia válida 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));
... y lo mismo se puede hacer para el colocador:
// 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);