C#提供了在函数重载时使用的以下signature characteristics。
我们知道,重载只考虑参数;它们的数量和类型,但多态性的目标是根据调用策略提供相同的名称但不同的用法。
如果我有一个包含两个具有相同名称和签名的方法的类,而一个是静态而另一个不是,则C#编译器会抛出错误; “类已经定义了一个名为'foo'的成员,它具有相同的参数类型”。对这两种方法的调用将会有所不同;一个具有对象名称,另一个具有类名称。因此,呼叫策略没有歧义。那为什么会抛出错误?
class Example {
public void foo() { }
public static void foo() { }
}
class Program
{
static void Main(string[] args)
{
Example e = new Example();
e.foo();
}
}
答案 0 :(得分:13)
抛出错误的原因是可以从非静态方法调用静态方法而不指定类型名称。在这种情况下,编译器无法确定正在调用哪种方法。
public class Foo()
{
public static void MyMethod() {};
public void MyMethod() {}
public void SomeOtherMethod()
{
MyMethod(); // which method we're calling static or non-static ?
}
}
修改强>
刚刚找到关于你案件的SO post。您可能也想检查一下。
答案 1 :(得分:5)
发生此错误是因为这是在C# Language Specification中定义行为的方式。任何"含糊不清的"使用(或消除歧义的方法)是无关紧要的,尽管这种推理和边缘情况可能导致设计者没有明确允许这样的区分......或者它可能只是一个C#编码的底层.NET CLI / CLR限制 1 。
来自" 3.6签名和重载"在C#规范中(并与链接文档一致),格式化为项目符号:
方法的签名由
组成
- 方法的名称,
- 类型参数的数量和
- 每个形式参数的类型和种类(值,参考或输出) ..
方法修饰符(包括static
) not 在此处被视为方法签名的一部分。
并且,从" 1.6.6方法"我们有限制和同意的总结:
方法的签名在声明方法的类中必须是唯一的。方法的签名包括方法的名称,类型参数的数量以及{参数的数量,修饰符和类型}。
此限制适用于考虑多态性的方法之前(并且独立于此)。
另外,作为结束语:实例方法必须是虚拟的,或者通过接口访问C#中的运行时多态。 (方法隐藏和方法重载都可以说是编译时多态的一种形式,但这是另一个主题..)
1 对此的支持仅仅是因为.NET CLI / CLR本身的限制而不值得绕过(即出于互操作性原因)。来自" I.8.6.1.5方法签名"在ECMA-335中:
方法签名由
组成
- 一个调用约定[CLS规则15:" 仅调用约定 CLS支持的是标准的托管呼叫约定"],
- 通用参数的数量,如果方法是通用的,
- [省略规则]
- 零个或多个参数签名的列表 - 方法的每个参数一个 - 和,
- 结果值的类型签名(如果生成一个)。
方法签名由方法定义声明。 只能向a添加一个约束 方法签名以及参数签名 [CLS规则15:" vararg约束不 CLS的一部分"]:
- 可以包含vararg约束以指示超过此点的所有参数都是 可选的。当它出现时,调用约定应该是支持变量的约定 参数列表。
C#/ CLS和ECMA签名组件之间的交集因此是方法名称,"通用参数的数量"和"零个或多个参数签名的列表"。
答案 2 :(得分:1)
我觉得你的问题是“为什么标准选择禁止声明只有静态关键字不同的两种方法?”,因此答案“因为标准说的那样”对我来说不合适。
现在,问题是,可能有任何原因。标准是法律,它可以是任意的。如果没有参与语言设计的人的帮助,我们所能做的就是推测原因,试图揭示法律的精神。
这是我的猜测。我认为这个选择有三个主要原因:
C ++和Java是C#的灵感语言,观察与这些语言相同的重载规则是有意义的。至于为什么在这些语言中这种方式,我不知道。我在SO C++上发现了一个类似的问题,虽然没有回答为什么会这样(在“标准如此说”之外)。
正如其他人和OP所指出的那样,允许静态关键字除外的相同签名强制用户以明确的方式调用方法(通过为类名或实例名添加前缀)。这增加了代码的复杂程度。当然,这已经可以用字段和参数完成。但是有些人不同意这种用法,并且更喜欢为字段选择不同的名称(在字段前面加上_或m_)。
这是我的理解,所以我可能完全错了(至少@ user2864740认为这个论点是可疑的 - 请参阅注释),但我觉得静态成员是在OOP中引入“函数式编程”的一种方式。它们没有绑定到特定的实例,因此它们不会修改对象的内部状态(如果它们修改了另一个对象的状态,那么它们应该是这个其他对象的非静态方法),在某种程度上它们是“纯粹的”。 因此,我不明白“纯函数”在语义上如何足够接近常规对象方法,以便它们共享相同的名称。
答案 3 :(得分:1)
在C#语言设计团队中工作的同一问题was asked to Eric Gunnerson,他的回答是:
就编译器而言,这两个函数之间确实没有歧义。但是,用户方面很可能会感到困惑。在文档中很难找到正确的方法,一旦这样做,就很难确保您正在调用正确的版本(即,当您需要实例版本时,可能会意外地调用静态版本)。
因此,不允许这样做的原因是设计使然。
答案 4 :(得分:0)
查看这个简单的伪代码:
class A
{
public void B(){...}
public static void B(){...}
}
...
A A = new A();
A.B(); // <== which one is going to be called?
答案 5 :(得分:0)
i)问题假设-获得以下行为:
ii)上下文:
当今大多数编程都使用DI-几乎是一种反模式,可以从依赖的直接实例中调用非静态方法(之前没有使用DI注入依赖)。
iii)解决方案:
class Program
{
static void Main(string[] args)
{
// instead of class initialization we would have these registrations, e.g.:
// diContainer.Resolve<IMyApplication>().With<MyDIApplication>();
// diContainer.Resolve<ITerminator>().With<Terminator>();
IMyApplication app = new MyDIApplication(new Terminator());
app.Run();
}
public interface IMyApplication { void Run(); }
public class MyDIApplication : IMyApplication
{
private readonly ITerminator terminator;
public MyDIApplication(ITerminator terminatorDependency)
{
this.terminator = terminatorDependency;
}
public void Run()
{
terminator.Terminate(); // instance method call
Terminator.Terminate(); // static method call
}
}
public interface ITerminator { void Terminate(); }
public class Terminator : ITerminator
{
public static void Terminate() => Console.WriteLine("Static method call.");
void ITerminator.Terminate() => Console.WriteLine("Non-static method call.");
}
}
结论:
是的,两个Terminate方法的签名并不相同,因为非静态方法是接口的显式实现,与静态方法不冲突,
但是实际上,当在依赖项注入的上下文中使用此解决方案时,我们真正关心的是结果,而不是管道-这是我们设法从类中调用静态方法,并且返回的收益几乎相同,名称和args作为非静态方法,用于注入DI的该类的实例。