【问题标题】:Why does the C# compiler not fault code where a static method calls an instance method?为什么 C# 编译器不会在静态方法调用实例方法时出错?
【发布时间】:2012-10-02 07:17:19
【问题描述】:

以下代码有一个静态方法Foo(),调用一个实例方法Bar()

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

它编译时没有错误*,但在运行时会生成运行时绑定器异常。正如预期的那样,删除这些方法的动态参数会导致编译器错误。

那么为什么有一个动态参数可以编译代码呢? ReSharper 也不会将其显示为错误。

编辑 1: *在 Visual Studio 2008 中

编辑 2: 添加了 sealed,因为子类可能包含静态 Bar(...) 方法。当实例方法以外的任何方法都无法在运行时调用时,即使是密封版本也可以编译。

【问题讨论】:

  • +1 提出非常好的问题
  • 这是一个 Eric-Lippert 问题。
  • 我很确定 Jon Skeet 也会知道如何处理这个问题;)@OlivierJacot-Descombes
  • @Olivier,Jon Skeet 可能想要编译代码,所以编译器允许它:-))
  • 这是另一个例子,说明为什么你不应该使用dynamic,除非你真的需要。

标签: c# visual-studio-2008 dynamic compiler-errors


【解决方案1】:

更新:以下答案写于 2012 年,在引入 C# 7.3(2018 年 5 月)之前。在What's new in C# 7.3改进的重载候选部分,第 1 项中,解释了重载解决规则如何更改,以便及早丢弃非静态重载。因此,下面的答案(以及整个问题)现在大多只具有历史意义!


(C# 7.3 之前:)

由于某种原因,重载解析总是在检查静态与非静态之前找到最佳匹配。请尝试使用所有静态类型的代码:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

这不会编译,因为最好的重载是采用string 的重载。但是,嘿,这是一个实例方法,所以编译器会抱怨(而不是采用次优重载)。

补充:所以我认为原始问题的dynamic 示例的解释是,为了保持一致,当类型是动态的时,我们也首先找到最佳重载(仅检查参数编号和参数类型等,而不是静态与非静态),并且只有 然后 检查静态。但这意味着静态检查必须等到运行时。因此观察到的行为。

后期添加:从this blog post by Eric Lippert可以推断出他们为什么选择做这个有趣的事情的一些背景。

【讨论】:

  • 原始问题中没有重载。显示静态过载的答案不相关。回答“好吧,如果你写了这个......”是无效的,因为我没有写那个:-)
  • @MikeScott 我只是想说服你,C# 中的重载解决方案总是这样:(1)找到最佳匹配忽略静态/非静态。 (2) 现在我们知道要使用什么重载,然后检查静态。正因为如此,在语言中引入dynamic 时,我认为C# 的设计者说过:“当它是dynamic 表达式时,我们不会考虑(2)编译时。”所以我在这里的目的是想出一个想法,为什么他们选择在运行时才检查静态与实例。我想说,这个检查发生在 binding-time
  • 很公平,但它仍然没有解释为什么在这种情况下编译器无法解析对实例方法的调用。换句话说,编译器进行解析的方式很简单——它无法识别像我的示例这样的简单情况,即不可能无法解析调用。具有讽刺意味的是:通过使用带有动态参数的单个 Bar() 方法,编译器会忽略该单个 Bar() 方法。
  • 这部分C#编译器是我写的,Jeppe是对的。请投票赞成。重载解析发生在检查给定方法是静态方法还是实例方法之前,在这种情况下,我们将重载解析推迟到运行时,因此也将静态/实例检查推迟到运行时。此外,编译器“尽最大努力”静态查找绝对不全面的动态错误。
【解决方案2】:

Foo 有一个参数“x”是动态的,这意味着 Bar(x) 是一个动态表达式。

Example 完全有可能拥有如下方法:

static Bar(SomeType obj)

在这种情况下,正确的方法将被解析,因此语句 Bar(x) 是完全有效的。有一个实例方法 Bar(x) 的事实是无关紧要的,甚至没有考虑:根据定义,由于 Bar(x) 是一个动态表达式,我们已经延迟解析到运行时。

【讨论】:

  • 但是当你取出实例Bar方法时,它不再编译了。
  • @Justin 有趣 - 警告?还是错误?无论哪种方式,它都可能只验证方法组,将完全重载解决方案留给运行时。
  • @Marc,因为没有另一个 Bar() 方法,所以你没有回答这个问题。鉴于只有一个 Bar() 方法没有重载,你能解释一下吗?当无法调用任何其他方法时,为什么要推迟到运行时?或者有吗?注意:我已经编辑了代码来封装类,它仍然可以编译。
  • @mike 至于为什么要推迟到运行时:因为这就是动态意味着
  • @Mike 不可能不是重点;重要的是它是否必需。动态的全部意义在于它不是编译器的工作。
【解决方案3】:

“动态”表达式将在运行时绑定,因此如果您定义具有正确签名的静态方法或实例方法,编译器将不会对其进行检查。

“正确”的方法将在运行时确定。编译器在运行时无法知道那里是否有有效的方法。

“动态”关键字是为动态和脚本语言定义的,其中方法可以随时定义,甚至在运行时。 Crazy stuff

这里是一个处理整数但不处理字符串的示例,因为该方法在实例上。

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

您可以添加一个方法来处理所有无法处理的“错误”调用

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

【讨论】:

  • 示例中的调用代码不应该是 Example.Bar(...) 而不是 Example.Foo(...)? Foo() 在您的示例中不是无关紧要吗?我真的不明白你的例子。为什么添加静态泛型方法会导致问题?您能否编辑您的答案以包含该方法而不是将其作为选项提供?
  • 但我发布的示例只有一个实例方法并且没有重载,因此在编译时您知道没有可能解决的静态方法。只有至少添加一个,情况才会改变,代码才有效。
  • 但是这个例子仍然有不止一个 Bar() 方法。我的示例只有一种方法。所以不可能调用任何静态 Bar() 方法。调用可以在编译时解决。
  • @Mike 可以是 != 是;使用动态,则不需要这样做
  • @MarcGravell,请澄清一下?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-14
  • 1970-01-01
  • 1970-01-01
  • 2013-08-22
  • 2011-03-02
相关资源
最近更新 更多