当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《为什么C#不允许静态方法实现接口?》经验,为你挑选了8个好方法。

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

    ....

 }

Chris Marast.. 214

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

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> Chris Marast..:

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

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


许多人都在双方权衡:从"这没有意义"到"这是一个错误,我希望你能做到." (我认为它有一些有效的用例,这就是我在这里的结果.)作为一种解决方法,实例方法可以简单地委托给静态方法.
有一种情况可能有用.例如,我希望所有实现者都实现一个带有XElement参数的GetInstance方法.我既不能将其定义为接口中的静态方法,也不能从接口请求构造函数签名.
通用仿制药会很有意义.例如,void Something ()其中T:ISomeInterface {new T().DoSomething1(); T.DoSomething2(); }
一个完美的例子!但我不确定我理解你的理由.当然编译器*可以*设计用于查看statuc成员吗?这些实例是否有一个地址表来实现theis方法?无法将静态方法包含在此表中吗?
您还可以简单地将该块作为扩展方法实现到基接口上,如:静态类中的`public static object MethodName(this IBaseClass base)`.然而,缺点是,与接口继承不同 - 这不会强制/允许单个继承者很好地覆盖方法.

2> Mark Bracket..:

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

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

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

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

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

但尚未确信其有用性.


这是正确的答案.您将界面传递给某人,他们需要知道如何调用方法.接口只是一个*虚方法表*.你的静态类没有.调用者不会知道*如何*调用方法.(在我读到这个答案之前,我认为C#只是迂腐.现在我意识到这是一个技术限制,强加于*界面**是什么***).其他人会告诉你这是一个糟糕的设计.这不是一个糟糕的设计 - 这是一个技术限制.
很好,这是我想写的答案 - 我只是不知道实现细节.
很好的答案.我想要这种'类型方法'!在很多场合都会有用(想想类型/类的元数据).
完全可以为具有关联vtable的静态类生成对象.看看Scala如何处理`object`s以及如何允许它们实现接口.
+1实际回答问题而不是迂腐和嗤之以鼻.就像Ian Boyd所说:"这不是一个糟糕的设计 - 这是一个技术限制."
vtable可以指向静态实现,就像`Func <>`那样,但它不是只包含一个指针,而是包含接口所需方法的所有指针.在静态调度中使用`static`会为了"面向对象"而人为地扰乱语言.
大多数面向对象的语言往往过于严格wrt dispatch:如果一个对象的运行时类型是已知的,那么通过vtable访问获取实际方法绝对没有意义(这会使廉价的编译器优化).另一方面,说静态调度的方法(静态`或只是非虚拟`)不能满足接口是平均无脑的,因为为了满足接口所需要的只是一个映射到的vtable实施方法.这就是所有类似C语言的OO语言出错的地方.回想起来很容易说,我想./咆哮

3> 小智..:

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

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

例如:

Repository GetRepository()
{
  //need to call T.IsQueryable, but can't!!!
  //need to call T.RowCount
  //need to call T.DoSomeStaticMath(int param)
}

...
var r = GetRepository()

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

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

创建完全独立的工厂类

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

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

即使我们控制用作通用参数的类的源,这也很难实现.原因是,例如,我们可能需要实例仅在众所周知的"连接到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...
    }
  }
}

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


+"这里的大多数答案似乎都错过了重点."; 令人难以置信的是,几乎所有的答案都躲过了问题的核心,深入研究了大多数无用的措辞......
@Chris我不同意,大规模矫枉过正.当更多架构是"最佳"解决方案时,它始终是语言缺陷的标志.还记得所有的模式,因为C#得到了泛型和匿名方法,所以没有人再讨论了吗?
@Chris这是一个特定的例子,让我再次遇到这个限制.我想在类中添加一个IResettable接口,以指示它们将某些数据缓存在可由站点管理员重置的静态变量中(例如,订单类别列表,一组增值税率,从外部API检索的类别列表)为了减少DB和外部API命中,这显然会使用静态方法重置.这使我可以自动检测哪些类可以重置.我仍然可以这样做,但该方法不会在IDE中强制执行或自动添加,而是依赖于希望.
你不能用"哪里T:IQueryable,T:IDoSomeStaticMath"或类似的?
@ ChrisMarasti-Georg:一个有点脏但有趣的方法就是这个小结构:`public abstract class DBObject 其中T:DBObject ,new()`,然后让所有DB类继承为`DBObject < T>`.然后你可以在一个抽象的超类中使用返回类型为T的一个静态函数,使该函数创建一个T的新对象,然后调用一个受保护的`String GetRetrieveByKeyQuery()`(在超类上定义为抽象) )在该对象上获取要执行的实际查询.虽然这可能会让人觉得有点不对劲

4> supercat..:

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

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

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

    创建一个静态泛型类,其类型参数将是您要传递给上面的DoSomething的类型.此类的每个变体都有一个或多个静态成员,其中包含与该类型相关的内容.这些信息可以通过让每个兴趣类调用"寄存器信息"例程,或者通过使用Reflection在运行类变体的静态构造函数时获取信息来提供.我相信后者的方法被Comparer .Default()之类的东西使用.

    对于每个感兴趣的类T,定义实现IGetWhateverClassInfo 并满足"新"约束的类或结构.该类实际上不包含任何字段,但是将具有静态属性,该属性返回具有类型信息的静态字段.将该类或结构的类型传递给相关的通用例程,该例程将能够创建实例并使用它来获取有关其他类的信息.如果为此目的使用类,则应该如上所述定义静态泛型类,以避免每次都必须构造新的描述符对象实例.如果使用结构,则实例化成本应为零,但每种不同的结构类型都需要不同的DoSomething例程扩展.

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


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

5> James Curran..:

我猜是近视.

最初设计时,接口仅用于与类的实例一起使用

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

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

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



6> John Kraft..:

接口指定对象的行为.

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


对不起..我不确定是对的!接口不指定行为.接口定义了一组命名操作.两个类可以实现接口的方法以完全不同的方式运行.因此,接口根本不指定行为.实现它的类确实如此.
嗯,是的,你会被歪曲,以定义一种与其名称不协调的方法.如果你有IAlertService.GetAssistance(),它的行为可能是闪光灯,发出警报,或用棍子捅眼睛.
接口应该指定包含行为和表示的合同.这就是为什么改变接口调用的行为是禁止的,因为两者都应该被修复.如果你有一个调用不同的接口(即IList.Add做了删除),那就不对了.

7> George..:

在接口表示"契约"的范围内,静态类实现接口似乎安静合理.

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


我完全同意这个简单而有效的答案."静态界面"的有趣之处在于它代表了一个契约.也许它不应该被称为"静态接口",但我们仍然错过了一个构造.例如,检查.NET关于ICustomMarshaler接口的官方文档.它需要实现它的类"添加一个名为GetInstance的静态方法,该方法接受String作为参数并具有返回类型ICustomMarshaler".这看起来像普通英语中的"静态界面"定义,而我更喜欢它在C#...

8> Charles Bret..:

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

你怎么称呼它?


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;  

推荐阅读
谢谢巷议
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有