代码之家  ›  专栏  ›  技术社区  ›  Kramii

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

  •  475
  • Kramii  · 技术社区  · 17 年前

    为什么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;
        }
    
        ....
    
     }
    
    28 回复  |  直到 14 年前
        1
  •  208
  •   Chris Marasti-Georg Scott Weinstein    17 年前

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

    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,并将其视为一个可能不是好主意的迹象。

        2
  •  159
  •   yoyo    11 年前

    我的(简化的)技术原因是静态方法不在 vtable ,并且在编译时选择调用站点。这也是你不能有覆盖或虚拟静态成员的原因。要了解更多细节,你需要一个计算机科学毕业生或编译器专家——我两者都不是。

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

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

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

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

    但尚未确信其有用性。

        3
  •  86
  •   Ian Boyd    14 年前

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

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

    例如:

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

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

        4
  •  18
  •   supercat    14 年前

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

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

    仅仅能够拥有静态方法 实施 一个界面不会达到你想要的效果;需要的是拥有静态成员 部分 一个接口。我当然可以想象很多用例,尤其是在能够创造东西的时候。我可以提供两种可能有所帮助的方法:

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

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

        5
  •  14
  •   John Kraft    17 年前

    我猜是近视。

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

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

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

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

        6
  •  13
  •   James Curran    11 年前

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

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

        7
  •  12
  •   George    14 年前

    接口指定对象的行为。

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

        8
  •  8
  •   Charles Bretana    16 年前

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

    你怎么称呼它??


    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;  
    
        9
  •  4
  •   Jeremy Sorensen    13 年前

    事实上,确实如此。

    截至2022年中期,当前版本的C#完全支持所谓的 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;
    
        ....
    }
    

    请注意,这取决于您的Visual Studio版本和安装的版本。NET SDK,您必须至少更新其中一个(或两者都更新),或者必须启用预览功能(请参阅 Use preview features & preview language in Visual Studio ).

    查看更多:

        10
  •  3
  •   Joel Coehoorn    17 年前

    关于在非泛型上下文中使用的静态方法,我同意在接口中允许它们没有多大意义,因为如果你有对接口的引用,你就无法调用它们。然而,在语言设计中存在一个根本性的漏洞,即不是在多态上下文中使用接口,而是在通用上下文中使用。在这种情况下,接口根本不是接口,而是约束。因为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约束可以解决这个问题。

        11
  •  3
  •   AnthonyWJones    17 年前

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

        12
  •  3
  •   Scott Langham    17 年前

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

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

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

        13
  •  3
  •   Jesper Grooss    17 年前

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

        14
  •  1
  •   Daniel Auger    17 年前

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

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

    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,实例化多个实例的开销很小。然而,在这种情况下,这将是一件好事。

        15
  •  1
  •   Jeff Yates    17 年前

    从c#9开始,允许在接口中使用静态方法(参见 https://www.dotnetcurry.com/csharp/simpler-code-with-csharp-9 ).

        16
  •  1
  •   Louis Rebolloso    15 年前

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

        17
  •  1
  •   Mar Bar    13 年前

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

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

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

        18
  •  1
  •   Thomas Phaneuf    11 年前

    静态类应该能够做到这一点,这样它们就可以通用。我不得不实施Singleton来实现预期的结果。

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

        19
  •  1
  •   Daniel Barbalace    10 年前

    大多数人似乎忘记了,在面向对象编程中,类也是对象,因此它们有消息,出于某种原因,c#调用了“静态方法”。 实例对象和类对象之间存在差异的事实只表明了语言的缺陷或不足。 对c#持乐观态度。..

        20
  •  0
  •   mackenir    17 年前

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

      static public bool IsHandled(XElement xml)
    

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

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

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

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

    因此,这是一个次要的潜在特征,总的来说,最好忽略不计。

        21
  •  0
  •   John Saunders    13 年前

    微软在C#中创建了一个带有静态元素的类的特殊实例来实现静态类,这只是实现静态功能的一个奇怪之处。这不是一个理论问题。

    接口应该是类接口的描述符,或者它是如何与之交互的,并且应该包括静态的交互。界面的一般定义(来自梅里亚姆·韦伯斯特):不同事物相遇、交流或相互影响的地方或区域。当你完全省略一个类或静态类的静态组件时,我们忽略了这些坏男孩如何互动的大部分内容。

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

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

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

        22
  •  0
  •   William Jockusch    11 年前

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

    也就是说,我建议当你想在接口中使用静态方法但无法使用时,使用注释代替。您将获得所需的功能。

        23
  •  0
  •   Sanjeev Saraswat    9 年前

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

        24
  •  0
  •   Vinay Chanumolu    8 年前

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

    由于它不可用,我开始使用非静态方法,如下所示。不太理想,但我看不出任何更有意义的方法,至少对我来说没有。

    public interface IZeroWrapper<TNumber> {
      TNumber Zero {get;}
    }
    
    public class DoubleWrapper: IZeroWrapper<double> {
      public double Zero { get { return 0; } }
    }
    
    推荐文章