c# - vacio - Reflexión para identificar métodos de extensión
metodos de extension c# (6)
En C #, ¿hay alguna técnica que use la reflexión para determinar si un método se ha agregado a una clase como método de extensión?
Dado un método de extensión como el que se muestra a continuación, ¿es posible determinar que se ha agregado Reverse () a la clase de cadena?
public static class StringExtensions
{
public static string Reverse(this string value)
{
char[] cArray = value.ToCharArray();
Array.Reverse(cArray);
return new string(cArray);
}
}
Estamos buscando un mecanismo para determinar en pruebas unitarias que el desarrollador haya agregado el método de extensión apropiadamente. Una razón para intentar esto es que es posible que el desarrollador agregue un método similar a la clase real y, si lo fuera, el compilador seleccionará ese método.
Una razón para intentar esto es que es posible que el desarrollador agregue un método similar a la clase real y, si lo fuera, el compilador seleccionará ese método.
- Supongamos que se define un método de extensión nulo Foo (este Cliente es un Cliente) .
- Supongamos también que Customer se modifica y se agrega el método void Foo () .
- Luego, el nuevo método sobre el Cliente cubrirá / ocultará el método de extensión.
La única forma de llamar al antiguo método de Foo en ese punto es:
CustomerExtension.Foo(myCustomer);
Basado en la respuesta de John Skeet, he creado mi propia extensión para System.Type-type.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace System
{
public static class TypeExtension
{
/// <summary>
/// This Methode extends the System.Type-type to get all extended methods. It searches hereby in all assemblies which are known by the current AppDomain.
/// </summary>
/// <remarks>
/// Insired by Jon Skeet from his answer on http://.com/questions/299515/c-sharp-reflection-to-identify-extension-methods
/// </remarks>
/// <returns>returns MethodInfo[] with the extended Method</returns>
public static MethodInfo[] GetExtensionMethods(this Type t)
{
List<Type> AssTypes = new List<Type>();
foreach (Assembly item in AppDomain.CurrentDomain.GetAssemblies())
{
AssTypes.AddRange(item.GetTypes());
}
var query = from type in AssTypes
where type.IsSealed && !type.IsGenericType && !type.IsNested
from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
where method.IsDefined(typeof(ExtensionAttribute), false)
where method.GetParameters()[0].ParameterType == t
select method;
return query.ToArray<MethodInfo>();
}
/// <summary>
/// Extends the System.Type-type to search for a given extended MethodeName.
/// </summary>
/// <param name="MethodeName">Name of the Methode</param>
/// <returns>the found Methode or null</returns>
public static MethodInfo GetExtensionMethod(this Type t, string MethodeName)
{
var mi = from methode in t.GetExtensionMethods()
where methode.Name == MethodeName
select methode;
if (mi.Count<MethodInfo>() <= 0)
return null;
else
return mi.First<MethodInfo>();
}
}
}
Obtiene todos los ensamblajes del AppDomain actual y busca métodos extendidos.
Uso:
Type t = typeof(Type);
MethodInfo[] extendedMethods = t.GetExtensionMethods();
MethodInfo extendedMethodInfo = t.GetExtensionMethod("GetExtensionMethods");
El siguiente paso sería extender System.Type con métodos, que devuelve todos los métodos (también los "normales" con los extendidos)
Debe buscar en todos los ensamblados donde se puede definir el método de extensión.
Busque las clases decoradas con ExtensionAttribute
, y luego los métodos dentro de esa clase que también están decorados con ExtensionAttribute
. Luego, verifique el tipo del primer parámetro para ver si coincide con el tipo que le interesa.
Aquí hay un código completo. Podría ser más riguroso (no verifica que el tipo no esté anidado, o que haya al menos un parámetro), pero debería darte una mano.
using System;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
public static class FirstExtensions
{
public static void Foo(this string x) {}
public static void Bar(string x) {} // Not an ext. method
public static void Baz(this int x) {} // Not on string
}
public static class SecondExtensions
{
public static void Quux(this string x) {}
}
public class Test
{
static void Main()
{
Assembly thisAssembly = typeof(Test).Assembly;
foreach (MethodInfo method in GetExtensionMethods(thisAssembly,
typeof(string)))
{
Console.WriteLine(method);
}
}
static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly,
Type extendedType)
{
var query = from type in assembly.GetTypes()
where type.IsSealed && !type.IsGenericType && !type.IsNested
from method in type.GetMethods(BindingFlags.Static
| BindingFlags.Public | BindingFlags.NonPublic)
where method.IsDefined(typeof(ExtensionAttribute), false)
where method.GetParameters()[0].ParameterType == extendedType
select method;
return query;
}
}
Esto devolverá una lista de todos los métodos de extensión definidos en un cierto tipo, incluidos los genéricos:
public static IEnumerable<KeyValuePair<Type, MethodInfo>> GetExtensionMethodsDefinedInType(this Type t)
{
if (!t.IsSealed || t.IsGenericType || t.IsNested)
return Enumerable.Empty<KeyValuePair<Type, MethodInfo>>();
var methods = t.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(m => m.IsDefined(typeof(ExtensionAttribute), false));
List<KeyValuePair<Type, MethodInfo>> pairs = new List<KeyValuePair<Type, MethodInfo>>();
foreach (var m in methods)
{
var parameters = m.GetParameters();
if (parameters.Length > 0)
{
if (parameters[0].ParameterType.IsGenericParameter)
{
if (m.ContainsGenericParameters)
{
var genericParameters = m.GetGenericArguments();
Type genericParam = genericParameters[parameters[0].ParameterType.GenericParameterPosition];
foreach (var constraint in genericParam.GetGenericParameterConstraints())
pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m));
}
}
else
pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m));
}
}
return pairs;
}
Solo hay un problema con esto: El tipo devuelto no es el mismo que esperaría con typeof (..), porque es un tipo de parámetro genérico. Para encontrar todos los métodos de extensión para un tipo dado, tendrá que comparar el GUID de todos los tipos de bases e interfaces del Tipo como:
public List<MethodInfo> GetExtensionMethodsOf(Type t)
{
List<MethodInfo> methods = new List<MethodInfo>();
Type cur = t;
while (cur != null)
{
TypeInfo tInfo;
if (typeInfo.TryGetValue(cur.GUID, out tInfo))
methods.AddRange(tInfo.ExtensionMethods);
foreach (var iface in cur.GetInterfaces())
{
if (typeInfo.TryGetValue(iface.GUID, out tInfo))
methods.AddRange(tInfo.ExtensionMethods);
}
cur = cur.BaseType;
}
return methods;
}
Estar Completo:
Guardo un diccionario de objetos de información tipo, que construyo al iterar todos los tipos de todos los ensamblajes:
private Dictionary<Guid, TypeInfo> typeInfo = new Dictionary<Guid, TypeInfo>();
donde TypeInfo
se define como:
public class TypeInfo
{
public TypeInfo()
{
ExtensionMethods = new List<MethodInfo>();
}
public List<ConstructorInfo> Constructors { get; set; }
public List<FieldInfo> Fields { get; set; }
public List<PropertyInfo> Properties { get; set; }
public List<MethodInfo> Methods { get; set; }
public List<MethodInfo> ExtensionMethods { get; set; }
}
Para aclarar un punto que Jon pasó por alto ... "Agregar" un método de extensión a una clase no cambia la clase de ninguna manera. Es solo un pequeño giro realizado por el compilador de C #.
Entonces, usando tu ejemplo, puedes escribir
string rev = myStr.Reverse();
pero el MSIL escrito en la asamblea será exactamente como si lo hubiera escrito:
string rev = StringExtensions.Reverse(myStr);
El compilador simplemente te permite engañarte al pensar que estás llamando a un método de String.
void Main()
{
var test = new Test();
var testWithMethod = new TestWithExtensionMethod();
Tools.IsExtensionMethodCall(() => test.Method()).Dump();
Tools.IsExtensionMethodCall(() => testWithMethod.Method()).Dump();
}
public class Test
{
public void Method() { }
}
public class TestWithExtensionMethod
{
}
public static class Extensions
{
public static void Method(this TestWithExtensionMethod test) { }
}
public static class Tools
{
public static MethodInfo GetCalledMethodInfo(Expression<Action> expr)
{
var methodCall = expr.Body as MethodCallExpression;
return methodCall.Method;
}
public static bool IsExtensionMethodCall(Expression<Action> expr)
{
var methodInfo = GetCalledMethodInfo(expr);
return methodInfo.IsStatic;
}
}
Productos:
Falso
Cierto