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

Posted

技术标签:

【中文标题】如何在接口上实现静态方法?【英文标题】:How can I implement static methods on an interface? 【发布时间】:2012-03-13 23:43:39 【问题描述】:

我有一个从 C# 调用的第 3 方 C++ DLL。

方法是静态的。

我想把它抽象出来做一些单元测试,所以我创建了一个包含静态方法的接口,但现在我的程序错误:

修饰符“静态”对此项目无效

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

【问题讨论】:

也许你可以用扩展方法做到这一点:***.com/questions/1243921/… 【参考方案1】:

您不能在 C# 中的接口上定义静态成员。接口是实例的合约

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

一旦你定义了这两个类,你就可以为你的环境创建一个你需要的类,并将它传递给MyClass的构造函数。

【讨论】:

-1 表示An interface is a contract, not an implementation. - 这是真的,但在这里完全不相关(non sequitur),因为静态方法不是实现本身的一部分 i> - 根据定义,实现是基于 data 的,而静态成员又是 inaccessible 的。 An interface type definition can define and implement static methods (see §8.4.3) since static methods are associated with the interface type itself rather than with any value of the type. - 请记住,static 成员通常是实用程序方法 我理解并同意你的说法,我觉得你的评论也是重要的背景。虽然。在设计接口时,应该将其视为一种契约,这意味着静态方法不适用。我想我应该把它留在那里以帮助一些人理解界面的目的。社区是否认为应该删除它? 我部分同意An interface is a contract, not an implementation 是无用的,有时有点上下文化确实有帮助。我完全同意static method is not a part of implementation itself ,静态方法一个实现,它们只有在另一个方法的实现中用作实现时才成为实现的一部分。但是,据我所知,我的字典是基于所学知识的,术语确实也因编程语言而异。静态方法不能是接口,因为无论如何只能有 1 个实现。 @vaxquis - 恕我直言,如果句子被改写,“它的合同”将是相关的:`接口是合同for instance。静态成员是类型的一部分;这个改写的句子(正确地)说它们在实例合同中没有意义。所以我认为问题只是措辞不准确,而不是不合逻辑。 感谢您的建议,我按照您的建议更新了答案【参考方案2】:

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

您可以做的是使用显式接口实现:

public interface IMyInterface

    void MyMethod();


public class MyClass : IMyInterface

    static void MyMethod()
    
    

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

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

【讨论】:

对于任何想知道为什么有人想要这样做的人来说,它在为实现静态方法的遗留代码编写单元/集成测试时特别有用。 这种技术非常适用于实现需要持久化数据但不能使用数据库的快速 RESTful API。该实现仅使用内存中的 C# 对象,因此没有地方存储数据,但使用静态属性减轻了对使用 EF Core 或 SQLite 的内存数据库的需求。 @gware 不是我建议解决此问题的方式 - 尝试将内存数据结构注入到非静态类的构造函数中。此外,您可以让常规属性访问静态字段(也不推荐)。【参考方案3】:

至于为什么不能在接口上使用静态方法:Why Doesn't C# Allow Static Methods to Implement an Interface?

但是,我建议删除静态方法以支持实例方法。如果这是不可能的,那么您可以将静态方法调用包装在一个实例方法中,然后您可以为此创建一个接口并从中运行您的单元测试。

public static class MyStaticClass

    public static void MyStaticMethod()
    ...


public interface IStaticWrapper

    void MyMethod();


public class MyClass : IStaticWrapper

    public void MyMethod()
    
        MyStaticClass.MyStaticMethod();
    

【讨论】:

使用静态类的接口比只使用接口有什么优势? 如果实现只是业务逻辑并且没有状态,静态有助于表明这一点。【参考方案4】:

你可以用反射来调用它:

MyInterface.GetType().InvokeMember("StaticMethod", BindingFlags.Static | BindingFlags.Public | BindingFlags.InvokeMethod, null, null, null);

【讨论】:

如果您没有 MyInterface 的实例,您可以使用“typeOf(MyInterface)”而不是“myInterface.GetType()”。 当时似乎是个好主意,我可能会继续通过反射来做,但有一点警告:如果程序被混淆以致于方法 StaticMethod 被重命名,它确实会变得更加成问题。 @RenniePet:您可以改用 nameof(StaticMethod) 来部分处理 StaticMethod 重命名。它可能有助于混淆器,具体取决于它的重命名方式。如果你这样做,你至少会看到一个编译时错误。 反射对于这种情况来说太极端了【参考方案5】:

静态成员在 CLR 中是完全合法的,只是在 C# 中不合法。

您可以在 IL 中实现一些胶水来链接实现细节。

不确定 C# 编译器是否允许调用它们?

参见:8.9.4 接口类型定义 ECMA-335。

接口类型必然是不完整的,因为它们什么也没说 关于接口类型的值的表示。为了这 原因,接口类型定义不应提供字段 接口类型值的定义(即实例字段), 尽管它可以声明静态字段(参见 §8.4.3)。

同样,接口类型定义不应提供 对其类型的值的任何方法的实现。然而,一个 接口类型定义可以——而且通常确实——定义方法契约 (方法名称和方法签名)应由 支持类型。接口类型定义可以定义和 实现静态方法(参见 §8.4.3),因为静态方法是 与接口类型本身相关联,而不是与任何值相关联 类型。

【讨论】:

作为参考,CLS Rule 19: CLS-compliant interfaces shall not define static methods, nor shall they define fields. 继续说符合 CLS 的消费者可以拒绝这些类型的接口。大约一年前,我尝试在接口上调用静态方法,但 C# 编译器无法编译它。 @ChristopherCurrens 关于 CLS 的进一步说明:Common Language Specification (CLS) is a set of basic language features that .Net Languages needed.... When there is a situation to communicate Objects written in different .Net Complaint languages , those objects must expose the features that are common to all the languages. 如果 CLS 是关于跨不同 .NET 语言的互操作性,并且 C# 不允许在接口上使用静态成员,那么CLS 也会禁止它们,以确保可以从 C# 调用其他 .NET 语言的库。【参考方案6】:

可以在 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# 8 中。 作为一个接口实现的观点,你的权利。这是没用的。但是这样至少你肯定在每个使用这个接口的类上都有一个实现的方法。 (这是接口的一种可选实现)【参考方案7】:

他们正在考虑在名为 C#“Ten”的未来 C# 版本中添加其中一些功能。这些理论特性可能允许接口上的静态成员以及角色。这将是向前迈出的一大步,它也将允许泛型运算符重载,而无需使用任何反射。这是一个示例 sn-p 计划如何工作,使用经典的幺半群示例,这只是说“可以添加的东西”的行话。直接取自Mads Torgersen: C# into the Future:

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 interface static members

编辑

这个帖子原说C# 8.0中会添加接口静态成员,这不是真的,我曲解了视频中Mads Torgersen的话。 The official C# 8.0 guide 还没有谈论静态接口成员,但很明显他们已经为此工作了很长时间。

【讨论】:

在您链接到的视频中,Mads 说通用接口、角色和类型扩展是 他们只是在考虑的东西 - 他没有说已经确认.我认为您应该将“C#“十”将允许接口上的静态成员”更改为不那么自信。【参考方案8】:

C# 8 允许接口上的静态成员

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

interface (C# Reference)

例如

public interface IGetSomething

    public static string Something = "something";


var something = IGetSomething.Something;

【讨论】:

【参考方案9】:

除了编译器认为我不应该这样做之外,我没有看到任何问题。 C# 不能从多个基类继承,当你习惯了能够做到这一点时真的很糟糕,正面你可以做几个接口,所以我滥用它来偷偷添加我需要的功能;-)

您应该检查 null 等,但这里是一个简化版本,它实现 Parse 以从 Web 服务或数据库获取类

/// <summary>
/// Implements parse
/// </summary>
/// <typeparam name="T">the type to parse</typeparam>
public interface IParse<T>
 
    /// <summary>
    /// implements parse from string to type
    /// </summary>
    /// <param name="text">value to parse</param>
    /// <returns></returns>
    static T Parse(string text)=>JsonConvert.DeserializeObject<T>(text, settings:new JsonSerializerSettings()  ConstructorHandling= ConstructorHandling.AllowNonPublicDefaultConstructor );

    /// <summary>
    /// implements parse from string to type
    /// </summary>
    /// <param name="text">value to parse</param>
    /// <param name="settings">the settings to us</param>
    /// <returns></returns>
    static T Parse(string text, JsonSerializerSettings settings) =>JsonConvert.DeserializeObject<T>(text, settings);

那我只调用接口 在以 List 作为返回值的代码中。

这是我从数据库中读取 JSON 并将其填充到已实现 Json 的类型的片段

//some plugging code

using (var reader = cmd.ExecuteReader(behavior: CommandBehavior.CloseConnection | CommandBehavior.SingleResult))

    if (reader.HasRows)
    
        while (reader.Read())
        
            rows++;
            try
            
                var json = reader.GetString(0);

                result.Add(IParse<T>.Parse(json));
            
            catch
            
                failed++;
            
        
    

//other plugging code

在 >ver 版本 8 中,您拥有默认实现,因此“潘多拉魔盒已打开”

【讨论】:

【参考方案10】:

你可以简单地使用:

     public interface MyInterface<T>
     
         T Method();
     
     
     public class MyClass<T> : MyInterface<T>
     
         public T Method() => //Your implementation   
     

所以你使用:

    var result = new MyClass<T>().Method();

静态类是不需要实例化的类。但是 C# 允许这种对我来说简单且有效的事情(最重要)。 当然,您需要避免在 Method() 中实现任何会更改类中信息的内容,因为在此调用之后,该类将消失(我希望)。

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。

以上是关于如何在接口上实现静态方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何强制继承类在 C# 中实现静态方法?

2017-04-16抽象类接口构造函数重载静态成员和方法

在静态页面中“包装”Flutter Web

为啥在 Eloquent 模型中调用方法时出现“不应静态调用非静态方法”?

Java静态方法获取所属类的信息

如何从静态函数返回函数接口?