C#中的错误:"表达式树可能不包含基本访问" - 为什么?

43

我正在调用一个接受Expression<Func<bool>>的方法。

我正在传递作为表达式的一部分:

this.Bottom == base.lineView.Top

编译器报错:

表达式树不能包含基本访问

于是我简单地将它更改为

this.Bottom == this.lineView.Top

因为该成员已被保护,所以现在它可以正常工作。

但这个错误真的让我困扰:为什么这个 base 会有问题?特别是如果使用 this 代替将会起作用,但语法上结果是相同的(访问相同的变量)?


3
有趣的问题。然而,通常情况下我不会没有理由地使用 base 关键字。也就是说,只有当 this 类型中继承的成员被新成员(使用相同名称)覆盖或隐藏时,我才使用它。也许正因为这个原因,在表达式树中我从未遇到过 base 的问题。 - Jeppe Stig Nielsen
2个回答

43

看了一下System.Linq.Expressions.Expression文档,我认为没有表达“基本成员访问”的表达式类型。请记住,尽管在您的情况下它意味着与this相同,但在其他情况下可能不是这样:

class Test
{
    void Foo()
    {
        Expression<Func<string>> baseString = () => base.ToString();
    }

    public override string ToString()
    {
        return "overridden value";
    }
}

这里代表着对this调用非虚拟的Object.ToString()方法。我看不出这个调用如何在表达式树中表示,因此会产生错误。

现在就引出了一个显而易见的问题,为什么在表达式树中没有非虚拟基类成员调用的表示方式-很抱歉我不能回答那一部分...虽然我可以看到如果您可以在程序中构建该表达式,这将使您能够绕过从类本身内部进行的正常多态性,而是从外部进行(这是正常情况下的情况)。这可能是原因。(固然有其他方法可以非虚拟地调用方法,但那是另一回事,并且我敢说在表达式树“值得信赖”的情况下,其他代码并不适用。)


5
嘿,Jon,你的猜想是正确的。请看我的答案了解详情。 - Eric Lippert
3
@EricLippert:太好了 - 确认总是很不错的 :) - Jon Skeet
一个完美的情况。有趣的问题和很棒的答案,谢谢。我的后续问题是:是否有任何非反射/非反射发射的其他方法来绕过多态性? - Piotr Zierhoffer
2
@PiotrZierhoffer:这是一个问答网站。如果您有新问题,请发布一个新问题。这样做的好处是,(1)许多人会尝试回答它,(2)更有可能被寻找该问题答案的人发现。 - Eric Lippert
当然可以。但我只是预料到会得到一个“不行”的回答。;-) - Piotr Zierhoffer

40

Jon的答案是正确的。我想对Jon的评论进行跟进:

我可以看出,如果您可以以程序方式构建该表达式,那将允许您从外部绕过正常的多态性,而不仅仅是从类本身内部(这是正常情况)绕过。那可能是原因

假设您有

public abstract class B // Prevent instantiation
{
    internal B() {} // Prevent subclassing outside the assembly. 
    public virtual void Dangerous() { ... } 
}
public sealed class D : B 
{ 
  public override void Dangerous() 
  { 
    if (!Allowed()) throw whatever;
    base.Dangerous();
  }

对于部分受信任的代码而言,如果手中有一个 D 的实例,它不能在未经过 D.Dangerous 的安全检查的情况下调用 B.Dangerous 方法。

因此,CLR 验证器限制了在类层次结构之外对虚拟方法进行非虚拟调用(当然,基本调用是非虚拟的)。事实上,它甚至更进一步;你甚至不能在 D 的嵌套类中执行此操作!(当然,如果程序被授予跳过验证的权利,则可以随便做任何事情;你可以在不可验证的代码中引用任意指向内存的指针,这比对虚拟方法进行静态调用更糟糕。)

在我们设计表达式树时,我们不想处理这个棘手的安全问题。最简单的方法就是直接将其禁止。

表达式树还存在许多其他无法轻松解决的安全问题,但这些是另一个话题。


我承认在某些情况下,我通过反射调用了私有成员。的确,这就是为什么我们被告知“private”是为了你自己的保护,即使它可以被同意的、清醒的成年人绕过。但现在似乎在任何情况下都无法调用重写的成员。这是真的吗? - N Jones
1
@NJones:我应该表述得更清楚。CLR 验证器 通过未能验证具有调用的方法来限制您进行此类调用。如果您的程序被授予跳过验证权限,则可以这样做,因为验证器首先不会运行。 - Eric Lippert

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