以下代码具有调用实例方法 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(...)
方法。即使在运行时不可能调用实例方法以外的任何方法时,即使是密封版本也可以编译。
dynamic
。
更新:以下答案写于 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可以推断出他们为什么选择做这个有趣的事情的一些背景。
Foo 有一个动态参数“x”,这意味着 Bar(x) 是一个动态表达式。
Example 完全有可能拥有如下方法:
static Bar(SomeType obj)
在这种情况下,将解析正确的方法,因此语句 Bar(x) 是完全有效的。有一个实例方法 Bar(x) 的事实是无关紧要的,甚至没有考虑过:根据定义,由于 Bar(x) 是一个动态表达式,我们将解析延迟到运行时。
“动态”表达式将在运行时绑定,因此如果您定义具有正确签名的静态方法或实例方法,编译器将不会检查它。
“正确”的方法将在运行时确定。编译器在运行时无法知道那里是否有有效的方法。
“动态”关键字是为动态和脚本语言定义的,其中方法可以随时定义,甚至在运行时。 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++;
}
}
不定期副业成功案例分享
dynamic
时,我认为 C# 的设计者说:“当它是dynamic
表达式时,我们不会考虑 (2) 编译时。”所以我在这里的目的是想出一个想法,为什么他们选择在运行时才检查静态与实例。我想说,这个检查发生在 binding-time。