繁体   English   中英

为什么静态和非静态方法不能共享相同的签名?

[英]Why can't a static and non-static method share the same signature?

C# 提供了在函数重载时使用的以下签名特征

我们知道重载只考虑参数; 它们的数量和类型,但多态性的目标是根据调用策略提供相同的名称但不同的用法。

如果我有一个包含两个具有相同名称和签名的方法的类,而一个是静态的,另一个不是,C# 编译器会抛出错误; “类已经定义了一个名为 'foo' 的成员,具有相同的参数类型”。对这两种方法的调用将有所不同; 一种带有对象名称,另一种带有类名称。 因此,调用策略没有歧义。 那为什么会报错呢?

 class Example {

    public void foo() { }
    public static void foo() { }

}

class Program
{
    static void Main(string[] args)
    {

        Example e = new Example();
        e.foo(); 

    }
}

抛出错误的原因是可以从非静态方法调用静态方法,而无需指定类型名称。 在这种情况下,编译器将无法确定正在调用哪个方法。

public class Foo()
{
   public static void MyMethod() {};
   public void MyMethod() {}

   public void SomeOtherMethod()
   {
      MyMethod(); // which method we're calling static or non-static ?
   }
}

编辑

刚刚发现这个关于你的案例的SO帖子 您可能还想检查一下。

发生此错误是因为这是C# 语言规范中定义行为的方式。 任何“模棱两可”的用法(或消除这种歧义的方法)都是无关紧要的,尽管这种推理和边缘情况可能导致设计人员没有明确允许这种区分……或者它可能只是底层 .NET CLI 的 C# 编码/CLR 限制1 .

来自 C# 规范中的“3.6 签名和重载”(并与链接文档一致),格式为项目符号:

方法的签名包括

  • 方法的名称
  • 类型参数的数量,以及
  • 每个形参的类型和种类(值、引用或输出) ..

方法修饰符,包括static ,在此处被视为方法签名的一部分。

并且,从“1.6.6 方法”我们有限制和一致的总结:

方法的签名在声明该方法的类中必须是唯一的 方法的签名由方法的名称、类型参数的数量和{数量、修饰符和类型}其参数组成。

此限制适用于(并且独立于)考虑多态性的方法。

此外,作为结束语:实例方法必须是虚拟的或通过接口访问才能在 C# 中运行时多态 (方法隐藏和方法重载都可以说是编译时多态的一种形式,但那是另一个话题..)


1对此的支持仅仅是由于 .NET CLI/CLR 本身的限制,不值得绕过(即出于互操作性原因)。 来自ECMA-335 中的“I.8.6.1.5 方法签名”:

方法签名由

  • 调用约定 [CLS 规则 15:“CLS 支持的唯一调用约定是标准托管调用约定”],
  • 泛型参数的数量,如果方法是泛型的,
  • 【省略规则】
  • 零个或多个参数签名的列表——方法的每个参数一个——以及,
  • 结果值的类型签名(如果生成)。

方法签名由方法定义声明。 除了参数签名之外,只能向方法签名添加一个约束[CLS 规则 15:“可变参数约束不是CLS 的一部分”]:

  • 可以包含 vararg 约束以指示经过此点的所有参数都是可选的。 当它出现时,调用约定应该是一种支持可变参数列表的约定。

因此,C#/CLS 和 ECMA 签名组件之间的交集是方法名称、“通用参数的数量”和“零个或多个参数签名的列表”。

我觉得你的问题是“为什么标准选择禁止声明两个仅因 static 关键字不同的方法?”,因此答案“因为标准这么说”对我来说似乎不合适。

现在,问题是,可能有任何原因。 标准是法律,它可以是任意的。 没有参与语言设计的人的帮助,我们所能做的就是推测原因,试图揭示法律的精神。

这是我的猜测。 我认为这个选择的三个主要原因:

因为其他语言是这样说的。

C++ 和 Java 是 C# 的励志语言,遵循与这些语言相同的重载规则是有意义的。 至于为什么在这些语言中是这样,我不知道。 我在 SO about C++上发现了一个类似的问题,尽管没有给出为什么是这样的答案(在“标准这么说”之外)。

因为它造成了需要解决的歧义。

正如其他人和 OP 所指出的,允许除了 static 关键字之外的相同签名会强制用户以明确的方式调用方法(通过在类名或实例名前加上前缀)。 这增加了代码的复杂性。 当然,这已经可以通过字段和参数来完成。 然而,有些人不同意这种用法,而是更喜欢为字段选择不同的名称(在字段前加上 _ 或 m_)。

因为它在 OOP 中没有多大意义。

这真的是我的理解,所以我可能完全错了(至少@user2864740 认为这个论点是可疑的——见评论),但我觉得静态成员是在 OOP 中引入“函数式编程”的一种方式。 它们没有绑定到特定的实例,所以它们不会修改一个对象的内部状态(如果它们修改了另一个对象的状态,那么它们应该是另一个对象的非静态方法),在某种程度上它们是“纯”的。 因此,我不明白“纯函数”如何在语义上与常规对象方法足够接近,以便它们共享相同的名称。

同样的问题也被问到了在 C# 语言设计团队工作的Eric Gunnerson ,他的回答是:

就编译器而言,这两个函数之间确实没有歧义。 然而,对于用户而言,存在相当大的混淆可能性。 在文档中很难找到正确的方法,而且一旦找到,就很难确定调用的是正确的版本(即,当您需要实例版本时,可能会不小心调用静态版本)。

因此,不允许的原因是设计使然。

i) 问题假设 - 获得以下行为:

  1. 能够从类中调用静态方法:例如MyClass.MySpecialMethod()
  2. 还可以从同一个类的实例中调用具有相同返回类型、名称和参数的非静态方法:例如instanceOfMyClass.MySpecialMethod()

ii) 背景:

  1. 在使用依赖注入的应用程序中重现行为。

今天的大多数编程都使用 DI - 从依赖项的直接实例调用非静态方法几乎是一种反模式(该依赖项之前没有被 DI 注入)。

三)解决方案:

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 的该类的实例的非静态方法。

看看这个简单的伪代码:

class A
{
  public void B(){...}
  public static void B(){...}
}

...

A A = new A();
A.B();  // <== which one is going to be called?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM