为什么C#不允许静态方法实现接口?

时间:2008-11-03 15:42:00

标签: c# oop language-features

为什么C#以这种方式设计?

据我所知,接口只描述行为,并且用于描述实现某些行为的接口的类的合同义务。

如果班级希望在共享方法中实现这种行为,为什么不应该这样做呢?

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

// 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;
    }

    ....

 }

27 个答案:

答案 0 :(得分:211)

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

public interface IFoo {
    void Bar();
}

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

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

<小时/> 为了实现你的例子,我给Animal一个const属性,它仍然允许从静态上下文访问它,并在实现中返回该值。

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,并把它作为一个迹象表明它可能不是个好主意。

答案 1 :(得分:163)

我的(简化)技术原因是静态方法不在vtable中,并且在编译时选择了调用站点。这与您不能拥有覆盖或虚拟静态成员的原因相同。有关更多详细信息,您需要CS毕业或编译器 - 我不是。

出于政治原因,我quote Eric Lippert(谁是编译员,并拥有滑铁卢大学的数学,计算机科学和应用数学学士学位(来源:LinkedIn):< / p>

  

......静态方法的核心设计原则,给他们起名字的原则...... [是] ......在编译时,总是可以确切地确定将调用什么方法。也就是说,该方法只能通过代码的静态分析来解决。

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

  

也就是说,一个与类型(如静态)相关联的方法,它不采用不可为空的“this”参数(与实例或虚拟不同),但调用的方法取决于构造的类型T(与静态不同,它必须在编译时可以确定)。

但尚未确信其有用性。

答案 2 :(得分:88)

这里的大多数答案似乎都错过了重点。多态性不仅可以在实例之间使用,也可以在类型之间使用。当我们使用泛型时,通常需要这样做。

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

例如:

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>()

不幸的是,我只能提出“丑陋”的选择:

  • 使用反射 丑陋并击败了接口和多态的想法。

  • 创建完全独立的工厂类

    这可能会大大增加代码的复杂性。例如,如果我们尝试为域对象建模,则每个对象都需要另一个存储库类。

  • 实例化然后调用所需的界面方法

    即使我们控制用作通用参数的类的源,这也很难实现。原因是,例如,我们可能需要实例仅在众所周知的“连接到DB”状态。

示例:

public class Customer 
{
  //create new customer
  public Customer(Transaction t) { ... }

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

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

为了使用即时解决方案解决静态接口问题,我们需要做以下事情:

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...
    }
  }
}

这显然是丑陋的,并且不必要使所有其他方法的代码复杂化。显然,也不是一个优雅的解决方案!

答案 3 :(得分:18)

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

string DoSomething<T>() where T:ISomeFunction
{
  if (T.someFunction())
    ...
}

仅仅能够使用静态方法实现接口将无法达到您想要的效果;我们需要的是将静态成员作为接口的部分。我当然可以想象许多用例,特别是在能够创建东西时。我可以提供两种可能有用的方法:

  1. 创建一个静态泛型类,其类型参数将是您要传递给上面的DoSomething的类型。此类的每个变体都有一个或多个静态成员,其中包含与该类型相关的内容。可以通过让每个兴趣类调用“寄存器信息”例程,或者通过使用Reflection在运行类变体的静态构造函数时获取信息来提供此信息。我相信后者的方法被Comparer&lt; T&gt; .Default()之类的东西使用。
  2. 对于每个感兴趣的类T,定义实现IGetWhateverClassInfo&lt; T&gt;的类或结构。并满足“新”约束。该类实际上不包含任何字段,但是将具有静态属性,该属性返回具有类型信息的静态字段。将该类或结构的类型传递给相关的通用例程,该例程将能够创建实例并使用它来获取有关其他类的信息。如果为此目的使用类,则应该如上所述定义静态泛型类,以避免每次都必须构造新的描述符对象实例。如果使用结构,则实例化成本应为nil,但每种不同的结构类型都需要DoSomething例程的不同扩展。

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

答案 4 :(得分:14)

近视,我猜。

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

的实例
IMyInterface val = GetObjectImplementingIMyInterface();
val.SomeThingDefinedinInterface();

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

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

答案 5 :(得分:14)

如果接口代表&#34; contract&#34;,静态类实现接口似乎很合理。

上述论点似乎都错过了关于合同的这一点。

答案 6 :(得分:14)

接口指定对象的行为。

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

答案 7 :(得分:9)

因为接口的目的是允许多态,能够传递任何已定义的已定义的定义类的实例以实现定义的接口...保证在您的多态调用中,代码将是能够找到你正在呼叫的方法。允许静态方法实现接口是没有意义的,

你怎么称呼它?


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;  

答案 8 :(得分:4)

关于在非泛型上下文中使用的静态方法,我同意在接口中允许它们没有多大意义,因为如果你有一个对接口的引用,你将无法调用它们。然而,在多态上下文中使用接口而不是通用接口创建的语言设计中存在一个基本漏洞。在这种情况下,接口根本不是接口,而是约束。因为C#在接口之外没有约束的概念,所以缺少实质的功能。一个很好的例子:

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

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

C#需要的是一个真正的约束类型,所有接口也都是约束,但并非所有约束都是接口,那么你可以这样做:

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;
    }
}

已经有很多关于为所有数值类型实现IA算术的讨论,但是关注效率,因为约束不是多态结构,因此CA算术约束可以解决这个问题。

答案 9 :(得分:3)

您可以将类的静态方法和非静态方法视为不同的接口。调用时,静态方法解析为单例静态类对象,非静态方法解析为您处理的类的实例。因此,如果在接口中使用静态和非静态方法,那么当我们真正希望使用接口来访问一个有凝聚力的东西时,您实际上会声明两个接口。

答案 10 :(得分:3)

举一个例子,我缺少接口方法的静态实现或Mark Brackett引入的“所谓的类型方法”:

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

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只需要每个表读取一次,实例化一个或多个实例的开销很小。但是,在这种情况下,这将是很好的。

答案 11 :(得分:3)

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

关于它是否重要,这将是无休止的争论,这是最佳实践,以及是否存在性能问题这样或那样做。通过简单地不支持C#,我们不必担心它。

符合这种愿望的编译器也可能会失去一些优化,可能会在实例和静态方法之间进行更严格的分离。

答案 12 :(得分:3)

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

答案 13 :(得分:1)

大多数人似乎忘记了在OOP中类也是对象,因此他们有消息,由于某种原因c#调用“静态方法”。 实例对象和类对象之间存在差异的事实只显示语言中的缺陷或缺点。 关于c#的乐观主义者虽然......

答案 14 :(得分:1)

C#和CLR应该像Java那样支持接口中的静态方法。 static修饰符是契约定义的一部分,并且具有含义,特别是行为和返回值不会因实例而异,尽管它可能因呼叫而异。

那就是说,我建议当你想在界面中使用静态方法而不能使用静态方法时,请使用注释。您将获得所需的功能。

答案 15 :(得分:1)

通过Microsoft在C#中使用静态元素创建类的特殊实例来实现静态类这一事实只是实现静态功能的一个奇怪之处。这不是一个理论观点。

接口应该是类接口的描述符 - 或者它如何与之交互,并且应该包括静态的交互。界面的一般定义(来自Meriam-Webster):不同事物相遇或相互沟通或相互影响的地方或区域。当你完全忽略类的静态组件或静态类时,我们忽略了这些坏男孩如何相互作用的大部分内容。

这是一个非常明显的例子,说明能够使用静态类的接口非常有用:

public interface ICrudModel<T, Tk>
{
    Boolean Create(T obj);
    T Retrieve(Tk key);
    Boolean Update(T obj);
    Boolean Delete(T obj);
}

目前,我编写了包含这些方法的静态类,没有任何检查以确保我没有忘记任何内容。就像在OOP之前编程的糟糕时光。

答案 16 :(得分:1)

好的,这是一个需要'类型方法'的例子。我正在基于一些源XML创建一组类中的一个。所以我有一个

  static public bool IsHandled(XElement xml)

在每个班级依次调用的函数。

该函数应该是静态的,否则我们会浪费时间创建不适当的对象。 至于@Ian Boyde指出它可以在工厂类中完成,但这只会增加复杂性。

将它添加到接口以强制类实现者实现它会很好。这不会导致显着的开销 - 它只是一个编译/链接时间检查,不会影响vtable。

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

所以这是一个次要的潜在特征,总的来说可能最好省略。

答案 17 :(得分:1)

仅供参考:您可以通过为界面创建扩展方法来获得与您想要的类似的行为。扩展方法将是共享的,不可覆盖的静态行为。然而,遗憾的是,这种静态方法不属于合同的一部分。

答案 18 :(得分:1)

静态类应该能够这样做,因此可以一般地使用它们。我不得不实施一个Singleton来达到预期的效果。

我有一堆静态业务层类,为每个实体类型(如“用户”,“团队”等)实现CRUD方法,如“创建”,“读取”,“更新”,“删除”等。然后我创建了一个基本控件,该控件具有实现CRUD方法的Business Layer类的抽象属性。这使我能够从基类自动执行“创建”,“读取”,“更新”,“删除”操作。由于静态限制,我不得不使用Singleton。

答案 19 :(得分:1)

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

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

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

答案 20 :(得分:0)

我认为问题在于C#需要另一个关键字,正是因为这种情况。您需要一个方法,其返回值仅取决于调用它的类型。如果所述类型未知,则不能将其称为“静态”。但是一旦知道这种类型,它就会变得静止。 “未解决的静态”是这个想法 - 它还不是静态的,但是一旦我们知道了接收类型,它就会是。这是一个非常好的概念,这就是程序员不断要求它的原因。但它并不完全符合设计师对语言的看法。

由于它不可用,我已采用下面显示的方式使用非静态方法。不完全理想,但我看不出任何更有意义的方法,至少不适合我。

public interface IZeroWrapper<TNumber> {
  TNumber Zero {get;}
}

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

答案 21 :(得分:0)

我认为简短的回答是“因为它没有用处”。 要调用接口方法,您需要一个类型的实例。从实例方法中,您可以调用任何您想要的静态方法。

答案 22 :(得分:0)

  

根据面向对象的概念接口由类和   有权使用访问这些实现的功能(或方法)   对象

因此,如果要访问Interface Contract方法,则必须创建对象。在静态方法的情况下,始终必须使用它。静态类,方法和变量从不需要对象并在内存中加载而无需创建该区域(或类)的对象,或者您可以说不需要创建对象。

答案 23 :(得分:0)

从概念上讲 ,没有理由为什么接口无法定义包含静态方法的协定。

对于当前的C#语言实现,此限制是由于允许继承基类和接口。如果“ class SomeBaseClass”实现“ interface ISomeInterface”并且“ class SomeDerivedClass:SomeBaseClass,ISomeInterface”也实现该接口,则用于实现接口方法的静态方法将无法编译,因为静态方法不能具有与实例方法相同的签名(出现在基类中以实现该接口。

静态类在功能上与单例相同,并且具有与使用更简洁语法的单例相同的目的。由于单例可以实现接口,因此静态实现接口在概念上是有效的。

因此,它简单地归结为C#名称冲突的局限性,例如在继承过程中实例和具有相同名称的静态方法。没有理由为什么不能“升级” C#以支持静态方法协定(接口)。

答案 24 :(得分:0)

请勿这样做,请尝试使用抽象类

public abstract class ExampleBase
{
    /// <summary>
    /// Do it
    /// </summary>
    public virtual abstract static void DoIt();
}

答案 25 :(得分:0)

从 c# 9 开始允许接口内的静态方法(参见 https://www.dotnetcurry.com/csharp/simpler-code-with-csharp-9)。

答案 26 :(得分:-1)

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

相关问题