如何使用反射获取泛型类型的扩展方法

10

从互联网上的各种来源中,我总结出了以下函数:

public static Nullable<T> TryParseNullable<T>(this Nullable<T> t, string input) where T : struct
{
    if (string.IsNullOrEmpty(input))
        return default(T);

    Nullable<T> result = new Nullable<T>();
    try
    {
        IConvertible convertibleString = (IConvertible)input;
        result = new Nullable<T>((T)convertibleString.ToType(typeof(T), CultureInfo.CurrentCulture));
    }
    catch (InvalidCastException) { }
    catch (FormatException) { }

    return result;
}

我已将其制作为扩展方法,如果我直接调用它,它就可以正常工作:

int? input = new int?().TryParseNullable("12345");

我的问题出现在我试图从另一个通用函数的上下文中使用反射调用它时。SO充满了描述如何获取通用方法和静态方法的MethodInfo的答案,但我似乎无法正确地将它们放在一起。
我已经正确确定传递的泛型类型本身是一个泛型类型(Nullable<>),现在我想使用反射调用Nullable<>上的TryParseNullable扩展方法:
public static T GetValue<T>(string name, T defaultValue)
{
    string result = getSomeStringValue(name);
    if (string.IsNullOrEmpty(result)) return defaultValue;

    try
    {
        if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            MethodInfo methodInfo;

            //using the TryParse() of the underlying type works but isn't exactly the way i want to do it
            //------------------------------------------------------------------------------------------- 
            NullableConverter nc = new NullableConverter(typeof(T));
            Type t = nc.UnderlyingType;

            methodInfo = t.GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, new[] { typeof(string), t.MakeByRefType() }, null);
            if (methodInfo != null)
            {
                var inputParameters = new object[] { result, null };
                methodInfo.Invoke(null, inputParameters);
                return (T) inputParameters[1];
            }

            //start of the problem area
            //-------------------------

            Type ttype = typeof(T);

            //this works but is undesirable (due to reference to class containing the static method):
            methodInfo = typeof(ParentExtensionsClass).GetMethod("TryParseNullable", BindingFlags.Public | BindingFlags.Static);
            if (methodInfo != null)
                Console.WriteLine(methodInfo);

            //standard way of getting static method, doesn't work (GetMethod() returns null):
            methodInfo = ttype.GetMethod("TryParseNullable", BindingFlags.Public | BindingFlags.Static);
            if (methodInfo != null)
                Console.WriteLine(methodInfo);

            //Jon Skeet's advised method, doesn't work in this case (again GetMethod() returns null):
            //(see footnote for link to this answer)
            methodInfo = ttype.GetMethod("TryParseNullable");
            methodInfo = methodInfo.MakeGenericMethod(ttype);
            if (methodInfo != null)
                Console.WriteLine(methodInfo);

            //another random attempt (also doesn't work):
            methodInfo = ttype.GetMethod("TryParseNullable", BindingFlags.Public | BindingFlags.Static, Type.DefaultBinder, new[] { typeof(string) }, null);
            if (methodInfo != null)
                Console.WriteLine(methodInfo);
        }

        // if we get this far, then we are not handling the type yet
        throw new ArgumentException("The type " + defaultValue.GetType() + " is not yet supported by GetValue<T>.", "T");
    }
    catch (Exception e)
    {
        [snip]
    }
}

能否让有经验的人帮我解决困境?
typeof(T) 返回正确的类型信息,我想也许我在 GetMethod() 调用中使用它有些不正确,或者我没有用正确的参数调用 GetMethod()

1. 参考 Jon Skeet 的回答


1
你确定扩展方法被视为 typeof(T) 的一部分吗?我猜你需要从实现扩展方法的静态类中检索该方法。你尝试过调用 GetMethods() 并检查返回的内容吗? - dlev
谢谢@dlev,那就是答案。扩展方法的真正基本原理之一,我竟然忽略了它。 - slugster
关于你的逻辑,我只是想提醒一下,如果 input == null,为什么要将 default(T) 返回为 T??似乎更正确的逻辑应该是返回 null。 - Matthew Scharley
值得注意的是,您的反射逻辑中可能也存在谬误。扩展方法正在扩展所有 Nullable<T>,其中 T 是任何有效类型之一,而不是 Nullable<>Nullable<> 是它自己的类型,并表示未指定类型参数的类型。通常情况下,它只对反射有用,因为它是一个半成品类,但这就是讨论的主题。您实际上无法实例化 Nullable<> - Matthew Scharley
1个回答

7
问题在于扩展方法不会修改它们“扩展”的类型。实际上,在幕后发生的是编译器透明地将所有似乎是在对象上进行的调用转换为对您的静态方法的调用。
例如:
int? input = new int?().TryParseNullable("12345");
// becomes...
int? input = YourClass.TryParseNullable(new int?(), "12345");

从这里开始,显然可以看出为什么它不会通过反射显示。这也解释了为什么您必须在定义YourClass的命名空间中使用using指令,以使扩展方法对编译器可见。至于如何实际获取该信息,我不确定是否有办法,除非通过遍历所有已声明的类型(如果您在编译时知道这类信息,则可以使用过滤列表),查找具有ExtensionMethodAttribute ([ExtensionMethod]) 的静态方法,然后尝试解析MethodInfo以确定它们是否适用于Nullable<>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接