6

这是一个打印 a 的方法签名的简单应用程序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继承的属性时MethodCallExpression,它总是返回As MethodInfo(根类)。

但是,在 Visual Studio 和我对每个调用的“转到定义”中Foo(),我按预期被带到了每个被覆盖的方法。

为什么MethodCallExpression.Method会有这种行为?规范中有什么关于这个的吗?为什么VS和Method物业有出入?我已经使用 .NET 4.0 和 4.5 进行了测试。

4

1 回答 1

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# 规范对此几乎没有什么要说的,它让它实现定义(但微软的实现似乎没有定义(文档)它):

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

于 2013-05-07T14:03:40.550 回答