465

为什么 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;
    }

    ....

 }
4

27 回答 27

227

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

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

于 2008-11-03T15:57:41.350 回答
179

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

出于政治原因,我将引用 Eric Lippert(他是一名编译器专家,拥有滑铁卢大学的数学、计算机科学和应用数学学士学位(来源:LinkedIn):

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

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

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

但尚未确信其有用性。

于 2008-11-03T17:37:31.947 回答
103

这里的大多数答案似乎都没有抓住重点。多态不仅可以在实例之间使用,也可以在类型之间使用。当我们使用泛型时,这通常是需要的。

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

例如:

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

不幸的是,我只能提出“丑陋”的替代方案:

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

  • 创建完全独立的工厂类

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

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

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

例子:

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

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

于 2011-10-30T13:35:25.200 回答
20

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

字符串 DoSomething<T>() 其中 T:ISomeFunction
{
  if (T.someFunction())
    ...
}

仅仅能够让静态方法实现接口并不能实现您想要的;所需要的是将静态成员作为接口的一部分。我当然可以想象很多用例,尤其是在能够创造事物的时候。我可以提供两种可能有用的方法:

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

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

于 2011-11-17T17:05:45.480 回答
16

我猜是短视。

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

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

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

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

于 2008-11-03T15:46:44.743 回答
15

就接口代表“契约”的程度而言,静态类实现接口似乎是合理的。

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

于 2012-08-22T03:47:47.043 回答
14

接口指定对象的行为。

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

于 2008-11-03T15:47:57.643 回答
10

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

你会怎么称呼它?


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;  
于 2008-11-03T17:51:07.987 回答
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;
    }
}

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

于 2013-08-13T17:26:04.293 回答
3

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

于 2008-11-03T15:43:44.220 回答
3

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

关于它是否重要、哪种是最佳实践以及以这种方式或另一种方式是否存在性能问题,将会有无休止的争论。通过简单地不支持它,C# 让我们不必担心它。

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

于 2008-11-03T16:33:18.050 回答
3

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

于 2008-11-03T16:49:06.147 回答
3

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

从数据库存储中读取时,我们有一个通用的 DataTable 类来处理从任何结构的表中读取。所有表特定信息都放在每个表的一个类中,该类还保存数据库中一行的数据,并且必须实现 IDataRow 接口。IDataRow 中包含对要从数据库中读取的表结构的描述。DataTable 必须在从 DB 读取之前从 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,实例化一个更多实例的开销是最小的。但是,在这种情况下会很好。

于 2009-02-26T07:16:06.917 回答
1

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

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

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

于 2008-11-03T16:34:26.380 回答
1

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

于 2008-11-03T16:46:39.583 回答
1

静态类应该能够做到这一点,因此它们可以被普遍使用。我不得不实现一个 Singleton 来实现预期的结果。

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

于 2011-01-19T17:18:36.203 回答
1

大多数人似乎忘记了在 OOP 中类也是对象,因此它们有消息,出于某种原因,c# 将其称为“静态方法”。实例对象和类对象之间存在差异这一事实仅表明该语言存在缺陷或缺点。虽然对 c# 持乐观态度...

于 2013-07-15T18:55:42.133 回答
1

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

  static public bool IsHandled(XElement xml)

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

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

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

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

因此,这是一个潜在的小功能,总的来说最好不要考虑。

于 2013-07-29T13:20:01.833 回答
1

静态类是由微软在 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 之前编程的糟糕时光。

于 2015-07-26T00:22:33.547 回答
1

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

也就是说,我建议当您想在接口中使用静态方法而不能使用时,请改用注解。您将获得您正在寻找的功能。

于 2016-01-18T22:33:03.703 回答
1

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

于 2021-07-14T08:46:04.283 回答
0

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

于 2008-11-03T17:29:33.320 回答
0

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

由于它不可用,我已采用如下所示的方式使用非静态方法。不完全理想,但我看不出任何更有意义的方法,至少对我来说不是。

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

public class DoubleWrapper: IZeroWrapper<double> {
  public double Zero { get { return 0; } }
}
于 2015-03-19T16:25:00.390 回答
0

根据由类实现的面向对象的概念接口,并具有使用对象访问这些实现的功能(或方法)的合同。

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

于 2017-07-02T13:31:49.717 回答
0

从概念上讲,接口没有理由不能定义包含静态方法的协定。

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

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

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

于 2019-05-14T19:07:02.693 回答
0

实际上,它确实(或至少,它会)

自 2022 年 1 月起,即将推出的 C# 和 .NET 版本将全面支持所谓的static abstract成员:

interface INumber<T>
{
    static abstract T Zero { get; }
}

struct Fraction : INumber<Fraction>
{
    public static Fraction Zero { get; } = new Fraction();

    public long Numerator;
    public ulong Denominator;

    ....
}
于 2022-01-30T09:23:54.200 回答
-1

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

于 2018-06-05T12:47:41.053 回答