MethodCallExpression.Method始终返回根基类的MethodInfo

时间:2013-05-07 12:33:42

标签: c# .net expression-trees

这是一个打印MethodCallExpression的方法签名的简单应用程序:

using System;
using System.Linq;
using System.Linq.Expressions;

class A
{
    public virtual void Foo() { }
}

class B : A
{
    public override void Foo() { }
}

class C : B
{
    public override void Foo() { }
}

class Program
{
    static void Main(string[] args)
    {
        PrintMethod<A>(a => a.Foo());
        PrintMethod<B>(b => b.Foo());
        PrintMethod<C>(c => c.Foo());

        Console.Read();
    }

    static void PrintMethod<T>(Expression<Action<T>> expression)
    {
        var body = (MethodCallExpression)expression.Body;

        var method1 = body.Method;
        var method2 = typeof(T).GetMethod(body.Method.Name, body.Method.GetParameters().Select(p => p.ParameterType).ToArray());

        Console.WriteLine("body.Method         -> " + method1.DeclaringType.ToString() + " - " + method1.ToString());
        Console.WriteLine("typeof(T).GetMethod -> " + method2.DeclaringType.ToString() + " - " + method2.ToString());
    }
}

我希望该程序可以打印出来:

body.Method         -> A - Void Foo()
typeof(T).GetMethod -> A - Void Foo()
body.Method         -> B - Void Foo() *
typeof(T).GetMethod -> B - Void Foo()
body.Method         -> C - Void Foo() *
typeof(T).GetMethod -> C - Void Foo()

但它打印出来:

body.Method         -> A - Void Foo()
typeof(T).GetMethod -> A - Void Foo()
body.Method         -> A - Void Foo() *
typeof(T).GetMethod -> B - Void Foo()
body.Method         -> A - Void Foo() *
typeof(T).GetMethod -> C - Void Foo()

获取继承的Method的{​​{1}}属性时,它始终返回MethodCallExpression s A(根类)。

但是,在每个MethodInfo调用的Visual Studio和I“Go To Definition”中,我按预期使用了每个被覆盖的方法。

Foo()为何如此表现?这个规格中有什么内容吗?为什么VS和MethodCallExpression.Method属性之间存在差异?我已经使用.NET 4.0和4.5进行了测试。

1 个答案:

答案 0 :(得分:4)

假设您有一个库:

public class A
{
    public virtual void Foo() { }
}

public class B : A
{
    public override void Foo() { }
}

public class C : B
{
    public override void Foo() { }
}

你有一个消费者

new C().Foo();

现在您更新了库,以便C不再覆盖Foo

public class C : B
{
}

消费者是否需要重新编译?

如果消费者虚拟地呼叫C.Foo,那么是,并且消费者必须专门写((A)new C()).Foo()以避免该问题。如果消费者虚拟地呼叫A.Foo,那么不。由于这是唯一的区别,因为在运行时将调用完全相同的函数,因此消费者指定它调用C.Foo是没有意义的。

表达式树记录常规函数调用将记录的相同方法信息。 C#规范几乎没有什么可说的,它留下了实现定义(但Microsoft的实现似乎没有定义(文档)它):

  

将匿名函数转换为表达式树类型会生成表达式树(第4.6节)。更确切地说,对匿名函数转换的评估导致构造表示匿名函数本身的结构的对象结构。表达式树的精确结构以及创建它的确切过程是实现定义的。