获取泛型方法传递的泛型参数类型和值

10

如何获取传递给已关闭/构造的泛型方法的参数值?

我已经有一段时间没有接触反射了。所有这些都曾经在我的脑海里,嗯,无论怎样。

class Program
{
    static void Main(string[] args)
    {
        new ConcreteFoo().GenericMethod<int>(5);
        Console.ReadKey();
    }
}

class ConcreteFoo
{
    public void GenericMethod<Q>(Q q) 
    {
        var method = MethodInfo.GetCurrentMethod();    
        var parameters = method.GetParameters();    
        if (parameters.Length > 0)
            foreach (var p in parameters)
                Console.WriteLine("Type: {0}", p.ParameterType);

        // That still prints Q as the type. 
        // I've tried GetGenericArguments as well. No luck.                
        // I want to know:
        // 1) The closed type, i.e. the actual generic argument supplied by the caller; and
        // 2) The value of that argument
    }

    public void GenericMethodWithNoGenericParameters<Q>() 
    { 
        // Same here
    }
}

class GenericFoo<T>
{
    public void NonGenericMethod(T t) { /* And here*/ }  
    public void GenericMethod<Q>(Q q) { /* And here */ }
}

更新

这个问题很荒谬,因此被提问者关闭了。他希望保留它,只是为了向孩子们展示爸爸有多愚蠢,如果他们最终成为C#程序员的话。


3
为什么不能使用q.GetType() - Daniel Hilgarth
5
如果你想要的是当前方法的运行时提供的类型,那么typeof(Q)将会给你这个类型。 - Mehmet Ataş
6
为了澄清上面两个评论之间的区别:q.GetType()会获取对象的实际类型,这意味着它可能是子类或者一个实现(如果Q是一个接口)。typeof(Q)将返回调用该方法时使用的泛型类型(无论传递的类型是什么,无论是明确指定:GenericMethod<object>(1)QObject),还是隐式指定:GenericMethod(1)QInt32))。 - Chris Sinclair
1
如果问题已经解决,您应该能够将其删除。 - Daniel Hilgarth
3
谢谢,Chris。对于运行时类型和编译时类型之间的区别做出了卓越的解释。 - Water Cooler v2
显示剩余4条评论
1个回答

8
短答案是typeof(Q)。
长答案(试图解释为什么您不能枚举这些类型并且必须专门编写它们)如下:
每个泛型方法(比其声明类更通用)都有对应的不同MethodInfo实例,用于其所有(曾经)特定化和另一个用于“模板”/开放方法。
您可以使用此来获得您想要的:
class ConcreteFoo {    
   public void GenericMethod<Q>(Q q) {
     var method = MethodInfo.GetCurrentMethod();
     var closedMethod = method.MakeGenericMethod(typeof(Q));

     // etc
   }
}

为什么会这样呢? 这是因为反射中的“枚举操作”都不返回指向已关闭特定化的MethodInfo实例。 如果您像这样枚举由ConcreteFoo声明的静态方法:
var atTime1 = typeof(ConcreteFoo).GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

ConcreteFoo.GenericMethod( true );

var atTime2 = typeof(ConcreteFoo).GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly);

您将获得相同的结果。 就GenericMethod及其特殊化而言,您只会获得与GenericMethod相关联的反射对象(开放变体)。
atTime2不会包含指向新编译的GenericMethod<bool>的额外MethodInfo。
但这并不是一件坏事,对吧? GetMethods()应该返回一致的结果,而不是随时间变化。 当涉及到“导航”操作时,通用方法的代数实际上非常好:
  1. 所有开放的MethodInfos都具有IsGenericMethod = true和IsGenericMethodDefinition = true
  2. 所有封闭的MethodInfos都具有IsGenericMethod = true和IsGenericMethodDefinition = false
  3. 通过在封闭的MethodInfo上调用.GetGenericMethodDefinition(),您可以获得开放的MethodInfo
  4. 通过在开放的MethodInfo上调用.MakeGenericType(params Type[] types),您可以获得任何所需的封闭MethodInfo(无需语法上了解这些类型,并且可能会因不遵守where子句而收到异常)
对于从当前线程的角度而非程序集和类型的角度进行的反射操作也是如此。
MethodBase MethodInfo.GetCurrentMethod()

并且

StackTrace trace = new StackTrace();
IEnumerable<MethodBase> methods = from frame in trace.GetFrames()
                                  select frame.GetMethod();

不要在当前的调用栈的顶部或全局范围内返回泛型方法的实际闭合变体(如果有)。
在某种程度上,你的问题并不荒谬,因为对于 GetCurrentMethod 这种情况,你可以轻松地将它替换为 GetCurrentMethod 加上 MakeGenericMethod 再加上语法可用的 typeof(Whatever) ,但你不能这样说你的调用者。
所以…对于非泛型方法,你可以始终查看你的堆栈并知道这些方法的参数类型。互相调用并最终调用您的方法的方法...但对于泛型方法(这些方法确实是真正封闭的,因为重复一遍,认为一个运行并调用另一个方法并被别人调用(等等)的泛型方法是开放的是不合逻辑的),你无法像了解任何此类方法的本地变量值一样找到参数的类型(这些变量是确定性的,但使其成为可能会存在很大的性能缺陷)。

我刚刚看到了这个答案。我不太擅长跟踪我的过去问题的答案。非常感谢你如此详尽的回答。我明天会仔细阅读它,我可以看到绿色勾勾即将到来。 :-) - Water Cooler v2

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