80

C# では、メソッドが拡張メソッドとしてクラスに追加されたかどうかを判断するためにリフレクションを使用する手法はありますか?

以下に示すような拡張メソッドが与えられた場合、文字列クラスに Reverse() が追加されていると判断できますか?

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

拡張メソッドが開発者によって適切に追加されたことを単体テストで判断するメカニズムを探しています。これを試みる理由の 1 つは、開発者が同様のメソッドを実際のクラスに追加する可能性があり、その場合、コンパイラがそのメソッドを取得するためです。

4

6 に答える 6

122

拡張メソッドが定義されている可能性のあるすべてのアセンブリを調べる必要があります。

で装飾されたクラスを探しExtensionAttribute、次にそのクラス内で同じく で装飾されているメソッド探しExtensionAttributeます。次に、最初のパラメーターの型をチェックして、目的の型と一致するかどうかを確認します。

ここにいくつかの完全なコードがあります。より厳密になる可能性があります (型がネストされていないこと、または少なくとも 1 つのパラメーターがあることを確認していません) ですが、助けになるはずです。

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;
    }
}
于 2008-11-18T17:42:15.713 に答える
5

これは、一般的なものを含む、特定の型で定義されたすべての拡張メソッドのリストを返します。

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

これには 1 つだけ問題があります。返される Type は、typeof(..) で期待するものとは異なります。これは、ジェネリック パラメーターの型であるためです。特定の型のすべての拡張メソッドを見つけるには、次のように Type のすべての基本型とインターフェイスの 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; }
}
于 2011-11-04T23:01:57.083 に答える
3

Jon が見逃した点を明確にするために... クラスに拡張メソッドを「追加」しても、クラスはまったく変更されません。これは、C# コンパイラによって実行されるほんの少しのスピンです。

したがって、あなたの例を使用して、次のように書くことができます

string rev = myStr.Reverse();

ただし、アセンブリに書き込まれる MSIL は、記述した場合とまったく同じになります。

string rev = StringExtensions.Reverse(myStr);

コンパイラは、String のメソッドを呼び出していると思い込ませているだけです。

于 2008-11-18T18:05:54.403 に答える
2

これを試みる理由の 1 つは、開発者が同様のメソッドを実際のクラスに追加する可能性があり、その場合、コンパイラがそのメソッドを取得するためです。

  • 拡張メソッドvoid Foo(this Customer someCustomer)が定義されているとします。
  • また、Customer が変更され、メソッドvoid Foo()が追加されたとします。
  • 次に、Customer の新しいメソッドが拡張メソッドをカバー/非表示にします。

その時点で古い Foo メソッドを呼び出す唯一の方法は次のとおりです。

CustomerExtension.Foo(myCustomer);
于 2008-11-18T19:05:59.463 に答える
0
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;
    }
}

出力:

間違い

真実

于 2016-10-20T19:35:29.993 に答える