如何在接口上实现静态方法?

164

我有一个从C#调用的第三方C ++ DLL。这些方法是静态的。

我想将其抽象出来进行一些单元测试,因此我创建了一个包含这些静态方法的接口,但现在我的程序出错了:

修改符“static”对此项无效

MyMethod cannot be accessed with an instance reference; qualify it with a type name instead

如何实现这种抽象?

我的代码看起来像这样

private IInterfaceWithStaticMethods MyInterface;

public MyClass(IInterfaceWithStaticMethods myInterface)
{
  this.MyInterface = myInterface;
}

public void MyMethod()
{
  MyInterface.StaticMethod();
}

5
也许你可以使用扩展方法来实现:https://dev59.com/-nM_5IYBdhLWcg3ww18Q - hcb
2
接口方法应该被实现为实例方法。如果你想要将静态API的方法复制到一个接口中,你可以创建一个实现该接口的类,只需将所有调用委托给真正的静态API即可。 - Jone Polvora
12个回答

165

接口不能有静态成员,静态方法也不能用作接口方法的实现。

你可以使用显式接口实现:

public interface IMyInterface
{
    void MyMethod();
}

public class MyClass : IMyInterface
{
    static void MyMethod()
    {
    }

    void IMyInterface.MyMethod()
    {
        MyClass.MyMethod();
    }
}

或者,您可以直接使用非静态方法,即使它们不访问任何特定于实例的成员。


38
为了帮助写遗留代码实现静态方法的单元测试/集成测试,有人可能想知道为什么要这样做。 - Dezzamondo
1
这种技术非常适用于实现需要持久化数据但不能使用数据库的快速RESTful API。该实现仅使用内存中的C#对象,因此没有存储数据的地方,但使用静态属性减轻了使用EF Core或SQLite的内存数据库的需求。 - gware
2
@gware 这不是我推荐的解决方法 - 请尝试将内存中的数据结构注入到非静态类的构造函数中。此外,您可以让常规属性访问静态字段(也不建议这样做)。 - Danny Varod
就记录而言,因为这个答案(目前)显示在最前面:它已经过时,从这个意义上说不再正确(对于更新的C#版本)。请查看周围的其他答案。 - undefined

145

C# 8中,您可以定义静态方法,但必须为其声明一个默认主体。

public interface IMyInterface
{
      static string GetHello() =>  "Default Hello from interface" ;
      static void WriteWorld() => Console.WriteLine("Writing World from interface");
}

或者如果您不想有任何默认的主体内容,只需抛出异常:

public interface IMyInterface
{
      static string GetHello() =>  throw new NotImplementedException() ;
      static void WriteWorld() => throw new NotImplementedException();
}

更新:

此外,我应该在C# 11中指出,您也可以拥有static abstract成员,而抽象成员不需要默认实现。

public interface IMyInterface
{
    static abstract string GetHello();
    static abstract void WriteWorld();
}

关于静态抽象的更多信息


5
在接口中,静态成员似乎相当无用,因为你不能通过接口实例来访问它们。至少在C# 8中是这样。 - Pavel Sapehin
9
从接口实现的角度来看,你是正确的。这样做似乎没有什么用处。但是这样做至少可以确保每个使用该接口的类都有一个已实现的方法。(这是接口的可选实现方式) - AliReza Sabouri
这些似乎没有指定“静态接口”,而是属于接口的静态成员。 - Dave Cousineau

72

在C#中,您不能为接口定义静态成员。接口是针对实例的契约。

建议按照您目前的方式创建接口,但不要使用静态关键字。然后创建一个类StaticIInterface,它实现了该接口并调用了静态C++方法。为了进行单元测试,创建另一个类FakeIInterface,它也实现了接口,但执行您需要处理单元测试的操作。

一旦定义了这两个类,您就可以创建适合您环境的类,并将其传递给MyClass的构造函数。


79
-1表示“一个接口是一个契约,而不是实现。” 这是正确的,但在这里完全无关紧要(non sequitur),因为静态方法不属于实现本身。按照定义,实现基于数据,而数据又对于静态成员是无法访问的。 "接口类型定义可以定义和实现静态方法(参见§8.4.3),因为静态方法与接口类型本身相关,而不是与类型的任何值相关。" - 请记住,static成员通常是实用方法 - user719662
3
我理解并同意你的陈述,我觉得你的评论也是重要的背景。然而,在设计接口时,应该将其视为合同,这意味着静态方法不适用。我认为我应该把它留在那里,以帮助一些人理解接口的目的。社区是否认为应该将其删除? - davisoa
4
我部分同意“接口是一份契约而非实现”的说法有时候有点无用,有时候加一些上下文解释确实会更有帮助。我完全同意“静态方法不是实现本身的一部分”,静态方法确实有一个实现,只有当它作为另一个方法实现的一部分被使用时才成为实现的一部分。然而我的字典基于所学的知识,据我所知,术语的确因编程语言而异。静态方法不能成为接口,因为只能有一个实现方式。 - CoffeDeveloper
2
@vaxquis - 在我看来,“它是一个契约”只有在句子重新表述为“接口是实例的契约”时才相关。静态成员是类型的一部分;这个重新表述的句子表明(正确地)它们在实例契约中没有意义。因此,我认为问题仅仅是措辞不精确,而不是非正常推论。 - ToolmakerSteve
1
谢谢您的建议,我已按照您的建议更新了答案。 - davisoa
显示剩余2条评论

20

静态成员在CLR中是完全合法的,只是在C#中不允许。您可以使用IL实现一些粘合剂来链接实现细节。

不确定C#编译器是否允许调用它们?请参见:8.9.4接口类型定义ECMA-335。

接口类型必须是不完整的,因为它们对接口类型的值的表示没有任何说明。因此,接口类型定义不得提供接口类型值(即实例字段)的字段定义,尽管它可以声明静态字段(请参见§8.4.3)。

同样,接口类型定义不应为其类型的值的任何方法提供实现。但是,接口类型定义可以 - 通常会 - 定义方法契约(方法名称和方法签名),这些方法契约将由支持类型实现。接口类型定义可以定义和实现静态方法(请参见§8.4.3),因为静态方法与接口类型本身相关联,而不是与任何类型的值相关联。


10
作为参考,“CLS规则19:符合CLS的接口不应定义静态方法,也不应定义字段。”继续说道,对于符合CLS的消费者拒绝这些类型的接口是可以接受的。大约一年前我尝试调用一个接口上的静态方法,但C#编译器无法编译它。 - Christopher Currens
关于CLS的注释,公共语言规范(CLS)是一组基本语言功能,.Net语言需要...当需要通信不同的.Net Complaint语言编写的对象时,这些对象必须公开所有语言都通用的功能。如果CLS是关于跨不同.NET语言的互操作性,并且C#不允许在接口上使用静态成员,则CLS也会禁止它们,以确保其他.NET语言中的库可以从C#调用。 - Simon Elms

15

C# 8 允许接口中定义静态成员

从C# 8.0开始,接口可以为其成员定义默认实现。它还可以定义静态成员,以提供通用功能的单一实现。

interface (C# 参考文献)

例如:

public interface IGetSomething
{
    public static string Something = "something";
}

var something = IGetSomething.Something;

1
这些是静态成员,为了方便而存在于接口中(用于默认接口实现的帮助),但它们不是实际“接口”的一部分,也不适用于接口的实现。 - Dave Cousineau

8

C# 11和.NET 7起,可以在接口中定义静态成员而无需提供默认实现。

以下是定义接口的方法。

interface IDoSomething
{
    static abstract void DoSomething();
}

以下是如何使一个类实现该接口的方法。

class DoerImplementation : IDoSomething
{
    public static void DoSomething()
    {
        // Method implementation goes here
    }
}

你可能会使用一个泛型类型参数来指定要使用的实现方式,就像这样。

class MyClass
{
    void MyMethod<T>() where T : IDoSomething
    {
        T.DoSomething();
    }

    void MyOtherMethod()
    {
        MyMethod<DoerImplementation>();
    }
}

有关接口中静态虚拟成员的更多信息,请参阅此Microsoft Learn文章


7

这篇文章虽然有点旧,但自最近相关文章以来C#已经发生了变化。

C#8之前

静态方法不存在。

C#10/11之前

可以定义静态方法/属性,但必须实现:

public interface MyInterfaceWithStaticMethod 
{
    public static String HelloWorld() => "Hello world";
}

C#10/11

目前得知C#将会引入接口静态成员特性,但是版本号尚不清楚。

根据这篇文章,你可以使用C#10 .NET6的预览版来尝试“接口中的静态抽象成员”。

另一方面,根据这篇文章,该特性将只在C#11中发布。


虽然文档写得有点混乱,但我对上述文档(截至2022年5月26日)的理解是,这是C#10中的预览功能,但目前计划在C#11中正式发布。在Mads Torgersen关于C#11功能的Build演讲中,它也被作为C#11功能呈现。 - ADBailey

7

他们正在考虑在未来的C#版本中添加一些这些特性,称为C# "Ten"。这些理论特性可能允许接口上的静态成员,并且还支持角色。这将是一个巨大的进步,它将允许使用通用运算符重载,而无需使用反射。以下是计划如何工作的示例片段,使用经典的单子例子,这只是说“可以添加的东西”的行话。直接摘自 Mads Torgersen:C#未来展望

interface IMonoid<T>
{
    static T Zero { get; }
    static T operator +(T t1, T t2);
}

public static T AddAll<T>(T[] ts) where T : IMonoid<T>
{
    T result = T.Zero;
    foreach (T t in ts) { result += t; }
    return result;
}

role IntAddMonoid extends int : IMonoid<int>
{
    public static int Zero => 0;
}

IntAddMonoid[] values = new int[] {1, 2, 4, 8, 16, 32};
int sixtyThree = AddAll<IntAddMonoid>(values); // == 63

附加资源:

Jeremy Bytes: C# 8接口静态成员

编辑

本文最初错误地声明了接口静态成员将在中添加,这是不正确的,我误解了Mads Torgersen在视频中的话。 官方C# 8.0指南还没有讨论静态接口成员,但显然他们已经在长时间的工作中了。


在你提供的视频中,Mads说通用接口、角色和类型扩展是他们正在考虑的事情,他并没有说这已经得到确认。我认为你应该减少肯定性地说“C# 'Ten'将允许接口上的静态成员”。 - Dai

5
您可以通过反射来调用它:
MyInterface.GetType().InvokeMember("StaticMethod", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, null, null);

5
如果您没有MyInterface的实例,您可以使用"typeOf(MyInterface)"代替"myInterface.GetType()"。 - RenniePet
当时似乎是个好主意,我可能会继续通过反射来实现它,但有一个小警告:如果程序被混淆以重命名StaticMethod方法,则问题会变得更加棘手。 - RenniePet
1
@RenniePet:你可以使用nameof(StaticMethod)来部分处理StaticMethod重命名。这可能有助于混淆器,具体取决于它如何重命名。如果您以这种方式操作,至少会看到编译时错误。 - Brent Rittenhouse
1
反射对于这种情况来说过于极端。 - Stepagrus

2
关于为什么接口不能有静态方法: 为什么 C# 不允许使用静态方法实现接口? 然而,我建议使用实例方法代替静态方法。如果不可能,请将静态方法调用封装在实例方法中,然后创建一个接口,并从该接口运行单元测试。
例如:
public static class MyStaticClass
{
    public static void MyStaticMethod()
    {...}
}

public interface IStaticWrapper
{
    void MyMethod();
}

public class MyClass : IStaticWrapper
{
    public void MyMethod()
    {
        MyStaticClass.MyStaticMethod();
    }
}

使用带有静态类的接口相比仅使用接口的优势是什么? - Selen
1
如果实现仅涉及业务逻辑且没有状态,静态变量可以帮助表明这一点。 - fcrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接