关于OOP:为什么不允许静态方法实现接口?

Why Doesn't C# Allow Static Methods to Implement an Interface?

为什么C是这样设计的?

正如我理解的那样,接口只描述行为,并用于描述实现某些行为的接口类的契约义务。

如果类希望在共享方法中实现该行为,为什么不应该呢?

以下是我所想的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// These items will be displayed in a list on the screen.
public interface IListItem {
  string ScreenName();
  ...
}

public class Animal: IListItem {
    // All animals will be called"Animal".
    public static string ScreenName() {
        return"Animal";
    }
....
}

public class Person: IListItem {

    private string name;

    // All persons will be called by their individual names.
    public string ScreenName() {
        return name;
    }

    ....

 }


假设你在问为什么你不能这样做:

1
2
3
4
5
6
7
public interface IFoo {
    void Bar();
}

public class Foo: IFoo {
    public static void Bar() {}
}

从语义上讲,这对我来说没有意义。接口上指定的方法应该存在,以指定与对象交互的约定。静态方法不允许您与对象进行交互-如果您发现自己处于可以使实现成为静态的位置,那么您可能需要问自己该方法是否真正属于接口。


为了实现您的示例,我将给animal一个const属性,它仍然允许从静态上下文访问它,并在实现中返回该值。

1
2
3
4
5
6
7
public class Animal: IListItem {
    /* Can be tough to come up with a different, yet meaningful name!
     * A different casing convention, like Java has, would help here.
     */

    public const string AnimalScreenName ="Animal";
    public string ScreenName(){ return AnimalScreenName; }
}

对于更复杂的情况,可以始终声明另一个静态方法并委托给它。在尝试想出一个例子时,我想不出任何理由,在静态和实例上下文中,您都会做一些非常重要的事情,因此我将为您留出一个foobar blob,并将其作为一个可能不是好主意的指示。


我(简化)的技术原因是静态方法不在vtable中,调用站点是在编译时选择的。这与不能具有override或虚拟静态成员的原因相同。要了解更多的细节,你需要一个CS毕业生或者编译器专家——我两者都不是。

出于政治原因,我引用埃里克·利珀特的话(他是一个编译高手,拥有滑铁卢大学的数学、计算机科学和应用数学学士学位(来源:LinkedIn):

...the core design principle of static methods, the principle that gives them their name...[is]...it can always be determined exactly, at compile time, what method will be called. That is, the method can be resolved solely by static analysis of the code.

请注意,Lippert确实为所谓的类型方法留出了空间:

That is, a method associated with a type (like a static), which does not take a non-nullable"this" argument (unlike an instance or virtual), but one where the method called would depend on the constructed type of T (unlike a static, which must be determinable at compile time).

但它的实用性还没有被证实。


这里的大多数答案似乎忽略了全部要点。多态性不仅可以在实例之间使用,还可以在类型之间使用。当我们使用泛型时,这通常是需要的。

假设我们在泛型方法中有类型参数,并且需要对它进行一些操作。我们不想实例化,因为我们不知道构造函数。

例如:

1
2
3
4
5
6
7
8
9
Repository GetRepository<T>()
{
  //need to call T.IsQueryable, but can't!!!
  //need to call T.RowCount
  //need to call T.DoSomeStaticMath(int param)
}

...
var r = GetRepository<Customer>()

不幸的是,我只能想出"丑陋"的选择:

  • 使用反射丑陋的,胜过接口和多态性的概念。

  • 创建完全独立的工厂类

    这可能会大大增加代码的复杂性。例如,如果我们试图对域对象建模,那么每个对象都需要另一个知识库类。

  • 实例化然后调用所需的接口方法

    即使我们控制类的源代码(用作泛型参数),这也很难实现。原因是,例如,我们可能需要实例仅处于众所周知的"连接到数据库"状态。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Customer
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  void SomeOtherMethod()
  {
    //do work...
  }
}

为了使用实例化来解决静态接口问题,我们需要执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Customer: IDoSomeStaticMath
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  //dummy instance
  public Customer() { IsDummy = true; }

  int DoSomeStaticMath(int a) { }

  void SomeOtherMethod()
  {
    if(!IsDummy)
    {
      //do work...
    }
  }
}

这显然很难看,而且不必要,这会使所有其他方法的代码变得复杂。显然,也不是一个优雅的解决方案!


我知道这是个老问题,但很有趣。这个例子不是最好的。我认为如果你展示一个使用案例会更清楚:

1
2
3
4
5
string DoSomething<T>() where T:ISomeFunction
{
  if (T.someFunction())
    ...
}

仅仅能够让静态方法实现接口并不能实现您想要的;需要的是将静态成员作为接口的一部分。我当然可以想象许多这样的使用案例,特别是当涉及到能够创建事物时。我可以提供两种可能有帮助的方法:

  • 创建一个静态泛型类,该类的类型参数将是您要传递给上面dosomething的类型。这个类的每个变体都将有一个或多个静态成员持有与该类型相关的内容。这些信息可以通过让每个感兴趣的类调用"注册信息"例程来提供,也可以在运行类变体的静态构造函数时使用反射来获取信息。我相信后一种方法被比较器.default()之类的东西使用。
  • 对于每个感兴趣的T类,定义一个实现igetHateVerClassInfo并满足"new"约束的类或结构。类实际上不包含任何字段,但将具有一个静态属性,该属性返回带有类型信息的静态字段。将该类或结构的类型传递给相关的泛型例程,该例程将能够创建一个实例并使用该实例获取有关另一个类的信息。如果您为此目的使用一个类,那么您可能应该如上面所示定义一个静态泛型类,以避免每次都必须构造一个新的描述符对象实例。如果使用结构,实例化成本应为零,但每种不同的结构类型都需要对dosomething例程进行不同的扩展。

    这些方法都不是真正有吸引力的。另一方面,我希望如果clr中存在的机制能够干净地提供这类功能,.net将允许您指定参数化的"新"约束(因为知道类是否具有具有具有特定签名的构造函数似乎难以与知道它是否具有带particular的静态方法相比签名)。


    接口指定对象的行为。

    静态方法不指定对象的行为,而是以某种方式影响对象的行为。


    我想是近视吧。

    最初设计时,接口仅用于类的实例

    1
    2
    IMyInterface val = GetObjectImplementingIMyInterface();
    val.SomeThingDefinedinInterface();

    只有在引入接口作为泛型的约束时,向接口添加静态方法才有实际的用途。

    (回应评论):我认为现在更改它需要更改clr,这将导致与现有程序集不兼容。


    在接口表示"契约"的程度上,静态类实现接口似乎是合理的。

    以上的论点似乎都忽略了关于合同的这一点。


    因为接口的目的是允许多态性,所以能够传递任意数量的已定义类的实例,这些类都已定义为实现已定义的接口…确保在多态调用中,代码能够找到您正在调用的方法。允许静态方法实现接口是没有意义的,

    你怎么称呼它??

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public interface MyInterface { void MyMethod(); }
    public class MyClass: MyInterface
    {
        public static void MyMethod() { //Do Something; }
    }

     // inside of some other class ...  
     // How would you call the method on the interface ???
        MyClass.MyMethod();  // this calls the method normally
                             // not through the interface...

        // This next fails you can't cast a classname to a different type...
        // Only instances can be Cast to a different type...
        MyInterface myItf = MyClass as MyInterface;


    对于在非通用上下文中使用的静态方法,我同意允许它们出现在接口中没有多大意义,因为如果您无论如何都引用了接口,就无法调用它们。然而,在语言设计中存在着一个基本的漏洞,它不是在多态上下文中使用接口,而是在通用上下文中使用接口。在这种情况下,接口根本不是接口,而是约束。因为C没有接口外部约束的概念,所以它缺少实质性的功能。例证:

    1
    2
    3
    4
    5
    6
    7
    T SumElements<T>(T initVal, T[] values)
    {
        foreach (var v in values)
        {
            initVal += v;
        }
    }

    这里没有多态性,generic使用对象的实际类型并调用+=操作符,但是这失败了,因为它不能确定该操作符是否存在。简单的解决方案是在约束中指定它;简单的解决方案是不可能的,因为运算符是静态的,静态方法不能在接口中,并且(这里是问题)约束表示为接口。

    C需要的是真正的约束类型,所有接口也都是约束,但不是所有约束都是接口,那么您可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    constraint CHasPlusEquals
    {
        static CHasPlusEquals operator + (CHasPlusEquals a, CHasPlusEquals b);
    }

    T SumElements<T>(T initVal, T[] values) where T : CHasPlusEquals
    {
        foreach (var v in values)
        {
            initVal += v;
        }
    }

    已经有很多人在讨论如何为所有数字类型实现IArithmetic,但是人们关心的是效率,因为约束不是多态构造,所以创建一个Carithmetic约束可以解决这个问题。


    您似乎希望允许通过类型或该类型的任何实例调用静态方法。这至少会导致歧义,这不是一个可取的特征。

    关于它是否重要,这是最佳实践,以及是否存在以某种方式执行它的性能问题,将会有无休止的争论。不支持它,我们就不用担心了。

    同样,符合这种需求的编译器也可能会失去一些优化,而这些优化可能会伴随着实例和静态方法之间更严格的分离。


    可以将类的静态方法和非静态方法视为不同的接口。调用时,静态方法解析为singleton静态类对象,而非静态方法解析为处理的类的实例。所以,如果您在一个接口中使用静态和非静态方法,那么当我们真的希望接口用于访问一个内聚性的东西时,实际上您将声明两个接口。


    因为接口在继承结构中,静态方法不能很好地继承。


    举个例子,我缺少接口方法的静态实现,或者MarkBrackett介绍的"所谓的类型方法"是什么:

    当从数据库存储中读取数据时,我们有一个通用的数据表类来处理从任何结构的表中读取的数据。所有特定于表的信息都放在每个表的一个类中,该表还保存来自数据库的一行数据,并且必须实现IDataRow接口。IDataRow中包含对要从数据库中读取的表结构的描述。在从数据库读取数据之前,数据表必须从IDataRow请求数据结构。目前的情况如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    interface IDataRow {
      string GetDataSTructre();  // How to read data from the DB
      void Read(IDBDataRow);     // How to populate this datarow from DB data
    }

    public class DataTable<T> : List<T> where T : IDataRow {

      public string GetDataStructure()
        // Desired: Static or Type method:
        // return (T.GetDataStructure());
        // Required: Instantiate a new class:
        return (new T().GetDataStructure());
      }

    }

    每个表只需要读取一次getdatastructure,实例化多个实例的开销最小。不过,在这种情况下会很好。


    大多数人似乎忘记了在OOP类中也是对象,所以他们有消息,出于某种原因C称之为"静态方法"。实例对象和类对象之间存在差异的事实只显示了语言中的缺陷或缺点。尽管如此,对C的乐观主义者…


    仅供参考:通过为接口创建扩展方法,您可以获得与您想要的类似的行为。扩展方法将是共享的、不可重写的静态行为。然而,不幸的是,这种静态方法不会成为合同的一部分。


    接口是定义的可用功能的抽象集。

    该接口中的方法是否表现为静态的,这是一个应该隐藏在接口后面的实现细节。将接口方法定义为静态方法是错误的,因为您将不必要地强制以某种方式实现该方法。

    如果方法被定义为静态的,那么实现接口的类就不会像它可能的那样被封装。在面向对象的设计中,封装是一件很好的事情(我不想谈为什么,你可以在这里阅读:http://en.wikipedia.org/wiki/object-oriented)。因此,接口中不允许使用静态方法。


    一个静态类是由微软在C语言中实现的,用静态元素创建一个特殊的类实例,这只是实现静态功能的一个奇怪之处。这不是理论上的观点。

    接口应该是类接口的描述符——或者它是如何与之交互的,并且应该包括静态的交互。界面的一般定义(来自MeriamWebster):不同事物相遇、交流或相互影响的地方或区域。当您完全忽略类或静态类的静态组件时,我们将忽略这些坏男孩如何交互的很大一部分。

    下面是一个非常清楚的例子,说明在哪里能够使用静态类的接口非常有用:

    1
    2
    3
    4
    5
    6
    7
    public interface ICrudModel<T, Tk>
    {
        Boolean Create(T obj);
        T Retrieve(Tk key);
        Boolean Update(T obj);
        Boolean Delete(T obj);
    }

    目前,我编写包含这些方法的静态类,而不进行任何检查,以确保我没有忘记任何东西。就像以前糟糕的编程时代。


    C1和CLR应该支持Java中的接口中的静态方法。静态修饰符是契约定义的一部分,并且具有一定的含义,特别是行为和返回值不会因实例而变化,尽管它在调用之间可能仍然会有所不同。

    也就是说,我建议当您希望在接口中使用静态方法而不能使用时,使用注释。您将获得所需的功能。


    静态类应该能够做到这一点,这样它们就可以被一般地使用。相反,我必须实现一个单例来实现期望的结果。

    我有许多静态业务层类,它们为每个实体类型(如"用户"、"团队"等)实现了CRUD方法,如"创建"、"读取"、"更新"、"删除"。然后,我为实现CRUD方法的业务层类创建了一个具有抽象属性的基控件。这允许我从基类中自动执行"创建"、"读取"、"更新"、"删除"操作。由于静态限制,我不得不使用单例。


    当类实现接口时,它正在为接口成员创建实例。虽然静态类型没有实例,但在接口中没有静态签名的意义。


    我认为简短的回答是"因为它毫无用处"。要调用接口方法,需要类型的实例。从实例方法中,可以调用任何想要的静态方法。


    这里有一个需要"类型方法"的例子。我正在基于某个源XML创建一组类中的一个。所以我有一个

    1
      static public bool IsHandled(XElement xml)

    在每个类上依次调用的函数。

    该函数应该是静态的,否则我们会浪费时间创建不合适的对象。正如@ian boyde所指出的,它可以在工厂类中完成,但这只是增加了复杂性。

    最好将它添加到接口中以强制类实现者实现它。这不会造成很大的开销-这只是编译/链接时间检查,不会影响vtable。

    不过,这也是一个相当小的改进。由于方法是静态的,所以作为调用方,我必须显式调用它,因此如果不实现它,就必须立即得到编译错误。允许在接口上指定它意味着这个错误在开发周期中稍早出现,但与其他中断的接口问题相比,这是微不足道的。

    所以,这是一个微小的潜在特征,总的来说,可能是最好被忽略的。


    我认为问题在于,对于这种情况,C需要另一个关键字。您需要一个方法,其返回值仅取决于调用该方法的类型。如果所说的类型未知,则不能称之为"静态"。但是一旦类型被知道,它就会变成静态的。""未解决的静态"是一个概念——它还不是静态的,但是一旦我们知道了接收类型,它就会是静态的。这是一个非常好的概念,这也是程序员不断要求它的原因。但它并不完全符合设计师对语言的思考方式。

    由于它不可用,所以我采用了下面所示的非静态方法。不太理想,但我看不出有什么更合理的方法,至少对我来说没有。

    1
    2
    3
    4
    5
    6
    7
    public interface IZeroWrapper<TNumber> {
      TNumber Zero {get;}
    }

    public class DoubleWrapper: IZeroWrapper<double> {
      public double Zero { get { return 0; } }
    }

    As per Object oriented concept Interface implemented by classes and
    have contract to access these implemented function(or methods) using
    object.

    因此,如果要访问接口契约方法,必须创建对象。对于静态方法,总是不允许这样做。静态类、方法和变量在不创建该区域(或类)的对象的情况下从不需要对象并加载到内存中,或者可以说不需要创建对象。