Programing

확장 방법을 식별하기위한 반영

crosscheck 2020. 10. 27. 07:51
반응형

확장 방법을 식별하기위한 반영


C #에서 리플렉션을 사용하여 메서드가 확장 메서드로 클래스에 추가되었는지 확인하는 기술이 있습니까?

아래 표시된 것과 같은 확장 메서드가 주어지면 Reverse ()가 문자열 클래스에 추가되었는지 확인할 수 있습니까?

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

개발자가 확장 메서드를 적절하게 추가했는지 단위 테스트에서 확인할 수있는 메커니즘을 찾고 있습니다. 이를 시도하는 한 가지 이유는 개발자가 실제 클래스에 유사한 메서드를 추가 할 수 있고, 그렇다면 컴파일러가 해당 메서드를 선택할 수 있기 때문입니다.


확장 메서드를 정의 할 수있는 모든 어셈블리를 살펴 봐야합니다.

장식 클래스 봐 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;
    }
}

John Skeet의 답변을 기반으로 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://stackoverflow.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>();
        }
    }
}

현재 AppDomain에서 모든 어셈블리를 가져오고 확장 된 메서드를 검색합니다.

용법:

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

다음 단계는 모든 메소드 (확장 된 메소드가있는 "일반"메소드)를 리턴하는 메소드로 System.Type을 확장하는 것입니다.


그러면 일반 형식을 포함하여 특정 형식에 정의 된 모든 확장 메서드 목록이 반환됩니다.

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

여기에는 한 가지 문제가 있습니다. 반환 된 Type은 일반적인 매개 변수 유형이기 때문에 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; }
}

Jon이 강조한 요점을 명확히하기 위해 ... 클래스에 확장 메서드를 "추가"한다고해서 클래스가 변경되는 것은 아닙니다. C # 컴파일러가 수행하는 약간의 회전입니다.

따라서 귀하의 예를 사용하여 다음과 같이 작성할 수 있습니다.

string rev = myStr.Reverse();

but the MSIL written to the assembly will be exactly as if you had written it:

string rev = StringExtensions.Reverse(myStr);

The compiler is merely letting you fool yourself into thinking you are calling an method of String.


One reason to attempt this is that it is possible that a similar method would be added to the actual class by the developer and, if it was, the compiler will pick that method up.

  • Suppose an extension method void Foo(this Customer someCustomer) is defined.
  • Suppose, also, that Customer is modified and the method void Foo() is added.
  • Then, the new method on Customer will cover/hide the extension method.

The only way to call the old Foo method at that point is:

CustomerExtension.Foo(myCustomer);

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

Outputs:

False

True

참고URL : https://stackoverflow.com/questions/299515/reflection-to-identify-extension-methods

반응형