ChatGPT解决这个技术问题 Extra ChatGPT

为什么 C# 编译器在静态方法调用实例方法时不会出错?

以下代码具有调用实例方法 Bar() 的静态方法 Foo()

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

J
Jeppe Stig Nielsen

更新:以下答案写于 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 示例的解释是,为了保持一致,当类型是动态的时,我们也 first 找到最佳重载(仅检查参数编号和参数类型等,而不是静态与非静态),并且只有 然后 检查静态。但这意味着静态检查必须等到运行时。因此观察到的行为。

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


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

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

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

static Bar(SomeType obj)

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


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

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

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

“动态”关键字是为动态和脚本语言定义的,其中方法可以随时定义,甚至在运行时。 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 可以是 != is;使用动态,不需要这样做
@MarcGravell,请澄清一下?