Отражение для определения методов расширения - PullRequest
68 голосов
/ 18 ноября 2008

В C # есть метод, использующий отражение, чтобы определить, был ли метод добавлен в класс как метод расширения?

Учитывая метод расширения, такой как показанный ниже, можно ли определить, что Reverse () был добавлен к классу строк?

public static class StringExtensions
{
    public static string Reverse(this string value)
    {
        char[] cArray = value.ToCharArray();
        Array.Reverse(cArray);
        return new string(cArray);
    }
}

Мы ищем механизм для определения в модульном тестировании, что метод расширения был соответствующим образом добавлен разработчиком. Одна из причин попытаться сделать это состоит в том, что возможно, что подобный метод будет добавлен разработчиком к фактическому классу, и, если это так, компилятор подберет этот метод.

Ответы [ 6 ]

104 голосов
/ 18 ноября 2008

Вы должны посмотреть во всех сборках, где может быть определен метод расширения.

Найдите классы, украшенные ExtensionAttribute, а затем методы в этом классе, которые также украшены ExtensionAttribute. Затем проверьте тип первого параметра и убедитесь, что он соответствует интересующему вас типу.

Вот некоторый полный код. Он может быть более строгим (это не проверка того, что тип не является вложенным или наличие хотя бы одного параметра), но он должен помочь вам.

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;
    }
}
10 голосов
/ 09 января 2012

Основываясь на ответе Джона Скита, я создал собственное расширение для типа System.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 /228359/otrazhenie-dlya-opredeleniya-metodov-rasshireniya
        /// </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>();
        }
    }
}

Он получает все сборки из текущего AppDomain и ищет расширенные методы.

Использование:

Type t = typeof(Type);
MethodInfo[] extendedMethods = t.GetExtensionMethods();
MethodInfo extendedMethodInfo = t.GetExtensionMethod("GetExtensionMethods");

Следующим шагом будет расширение System.Type с помощью методов, которые возвращают все методы (в том числе «нормальные» с расширенными)

4 голосов
/ 05 ноября 2011

Будет возвращен список всех методов расширения, определенных в определенном типе, включая общие:

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;
}

Есть только одна проблема: возвращаемый тип не тот, который вы ожидаете от typeof (..), потому что это универсальный тип параметра. Чтобы найти все методы расширения для данного типа, вам нужно сравнить GUID всех базовых типов и интерфейсов типа, например:

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;
}

Для дополнения:

У меня есть словарь объектов типов информации, который я строю при переборе всех типов всех сборок:

private Dictionary<Guid, TypeInfo> typeInfo = new Dictionary<Guid, TypeInfo>();

, где TypeInfo определяется как:

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; }
}
3 голосов
/ 18 ноября 2008

Чтобы прояснить ситуацию, Джон обернулся ... «Добавление» метода расширения к классу никак не меняет класс. Это всего лишь небольшое вращение, выполняемое компилятором C #.

Итак, используя ваш пример, вы можете написать

string rev = myStr.Reverse();

но MSIL, записанный в сборку, будет точно таким же, как если бы вы его написали:

string rev = StringExtensions.Reverse(myStr);

Компилятор просто позволяет вам обмануть себя, думая, что вы вызываете метод String.

2 голосов
/ 18 ноября 2008

Одна из причин, по которой это делается, заключается в том, что возможно, что аналогичный метод будет добавлен разработчиком к фактическому классу, и, если это так, компилятор подберет этот метод.

  • Предположим, что метод расширения void Foo (этот Клиент someCustomer) определен.
  • Предположим также, что Клиент изменен и добавлен метод void Foo () .
  • Затем новый метод в Customer будет скрывать / скрывать метод расширения.

Единственный способ вызвать старый метод Foo на этом этапе:

CustomerExtension.Foo(myCustomer);
0 голосов
/ 20 октября 2016
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;
    }
}

Выходы:

Ложная

Правда

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...