C#接口的默认方法实现?

63

在C#中是否可能定义一个具有默认实现的接口?(这样我们就可以定义实现该接口的类,而不必实现特定的默认方法)。

我知道扩展方法(例如在此链接中解释)。 但这 不是 我的答案,因为像以下的方法扩展一样,编译器仍会抱怨在MyClass中实现MyMethod:

public interface IMyInterface
{
    string MyMethod();
}

public static class IMyInterfaceExtens
{
    public static string MyMethod(this IMyInterface someObj)
    {
        return "Default method!";
    }
}

public class MyClass: IMyInterface
{
// I want to have a default implementation of "MyMethod" 
// so that I can skip implementing it here
}

我问这个问题是因为(按照我的理解)在Java中可以这样做(请看这里)。

PS:拥有一个带有一些方法的抽象基类不是我的答案,因为在C#中我们没有多重继承,并且与为接口提供默认实现(如果可能的话!)不同。


3
Java最近通过在接口中定义实现来重新引入了多继承,正如你所说。这真是讽刺,因为Java最初推出接口就是为了应对多重继承的问题。然而,C#并没有这样做,因此单继承(如果你一定要使用继承)仍然是C#所支持的全部。 - David Arno
11
在 C# 8 中可能会发生改变,请参阅 GitHub 上的讨论。 - Swimburger
1
此功能已经在 C# 的预览版本中发布 - https://devblogs.microsoft.com/dotnet/default-implementations-in-interfaces/ - Mauricio Gracia Gutierrez
1
@DavidArno,“经典”多重继承的大部分问题都源于共享状态。默认接口实现不会引入任何新状态。 - Luaan
7个回答

51

C# v8及以上版本允许在接口中实现具体方法,而之前只允许在抽象类中实现。

这个变化将会保护我们的具体类不受更改接口后产生的副作用的影响,例如在已经发布到生产环境的DLL中添加新的接口约定。因此,即使在正在实现的接口中添加了新的方法签名,我们的类仍然可以正确编译。

这是可能的,因为我们还可以为引入的新接口提供默认实现。因此,我们不必更改任何使用此接口的旧类或现有类(请参考代码片段)。

interface IA
{
    void NotImplementedMethod(); //method having only declaration
    void M() 
    { 
        WriteLine("IA.M"); 
    }//method with declaration + definition
}

原则上,我们可以说C#接口现在开始表现得像抽象类。

参考资料:


1
考虑到C# > v8,您是否考虑更新此答案。一个可能有用的参考资料:https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#default-interface-methods - Paul Masri-Stone
感谢您的好建议。已完成翻译。 - RBT
谢谢...虽然它仍然说“将在下一个语言版本中可能”,并且参考了过时的Github问题。我更倾向于更新措辞和链接。 - Paul Masri-Stone

42

我开发游戏,因此经常希望为接口的所有实现定义公共功能,同时允许每个实现也可以做自己的事情,就像子类的虚拟/重写方法一样。

这是我的做法:

public class Example
{
    void Start()
    {
        WallE wallE = new WallE();
        Robocop robocop = new Robocop();

        // Calling Move() (from IRobotHelper)
        // First it will execute the shared functionality, as specified in IRobotHelper
        // Then it will execute any implementation-specific functionality,
        // depending on which class called it. In this case, WallE's OnMove().
        wallE.Move(1);

        // Now if we call the same Move function on a different implementation of IRobot
        // It will again begin by executing the shared functionality, as specified in IRobotHlper's Move function
        // And then it will proceed to executing Robocop's OnMove(), for Robocop-specific functionality.
        robocop.Move(1);

        // The whole concept is similar to inheritence, but for interfaces.
        // This structure offers an - admittedly dirty - way of having some of the benefits of a multiple inheritence scheme in C#, using interfaces.
    }

}

public interface IRobot
{
    // Fields
    float speed { get; }
    float position { get; set; }

    // Implementation specific functions.
    // Similar to an override function.
    void OnMove(float direction);
}

public static class IRobotHelper
{
    // Common code for all IRobot implementations. 
    // Similar to the body of a virtual function, only it always gets called.
    public static void Move(this IRobot iRobot, float direction)
    {
        // All robots move based on their speed.
        iRobot.position += iRobot.speed * direction;

        // Call the ImplementationSpecific function
        iRobot.OnMove(direction);
    }
}

// Pro-Guns robot.
public class Robocop : IRobot
{
    public float position { get; set; }

    public float speed { get; set;}

    private void Shoot(float direction) { }

    // Robocop also shoots when he moves
    public void OnMove(float direction)
    {
        Shoot(direction);
    }
}

// Hippie robot.
public class WallE : IRobot
{
    public float position { get; set; }

    public float speed { get; set; }

    // Wall-E is happy just moving around
    public void OnMove(float direction) { }
}

8
在你的例子中,我没有看到 IRobotHelper 的任何用处。它的目的是什么? - Jazimov
11
所提出的架构提供了在接口实现中具有共同功能的能力。IRobotHelper 包含公共部分,因此每个实现只需要实现特定于该实现的部分。例如,如果你有类型为 Robocop、WallE 或任何其他实现 IRobot 接口的类的对象 r,并调用 r.Move(1),它将首先调用 IRobotHelper 的共享函数 Move(this IRobot),然后调用相应实现的 IRobot 的实现特定 OnMove(1)。你能理解吗? - Konstantinos Vasileiadis
8
如果你像我一样好奇,不明白为什么IRobot上会调用Move()方法,那是因为它被称为扩展方法 - Naomak
1
好的,现在我明白你所做的了。在静态方法Move中使用this关键字会影响所有机器人。IRobotHelper是一个带有扩展方法的静态类。让我感到困惑的是 IRobotHelper 中的 I。因为这个类不是接口,所以它可能不应该包含前缀 I - Mark Entingh
5
标准的命名应该是RobotExtensions。当然,这并不太重要。 - Luaan
显示剩余6条评论

18

简短回答:

不,你不能在接口中编写方法的实现。

描述:

接口就像合同一样,继承自它的类型必须定义实现。如果你需要一个带有默认实现方法的场景,那么可以将你的类抽象化,并为所需的方法定义默认实现。

例如:

public abstract class MyType
{
    public string MyMethod()
    {
      // some implementation
    }

    public abstract string SomeMethodWhichDerivedTypeWillImplement();
}

现在在派生类中:

public class DerivedType : MyType
{
  // now use the default implemented method here
}

更新(C# 8将支持此功能):

C# 8允许在接口中具有默认实现


8

不能直接实现,但您可以为接口定义一个扩展方法,然后像这样实现它:

public interface ITestUser
{
    int id { get; set; }
    string firstName { get; set; }
    string lastName { get; set; }

    string FormattedName();
}

static class ITestUserHelpers
{
    public static string FormattedNameDefault(this ITestUser user)
    {
        return user.lastName + ", " + user.firstName;
    }
}

public class TestUser : ITestUser
{
    public int id { get; set; }
    public string firstName { get; set; }
    public string lastName { get; set; }

    public string FormattedName()
    {
        return this.FormattedNameDefault();
    }
}

编辑* 重要的是,扩展方法和您正在实现的方法的名称不同,否则可能会出现堆栈溢出。


使用扩展方法会使“表面”接口变得混乱。如果您希望允许消费者选择“默认”或“标准”实现,则这是一个好处。普通的静态方法可以避免这种影响。但是,它们也会使编辑器插件/“代码片段”变得更加复杂,因为它们需要接口/助手类的名称。例如 return <InterfaceName>Defaults.<MethodName>(this);return this.<MethodName>Default(); - SensorSmith

7

C# 8.0中是可行的。您可以添加具有默认实现的方法。为使用此功能,必须将目标框架版本更改为最新版本。


1

C# 11 特性 - 现已正式发布:

接口中的静态虚成员

文档表示:

C# 11 和 .NET 7 包括接口中的静态虚成员。

此特性使您能够定义包含重载运算符或其他静态成员的接口。 一旦您定义了带有静态成员的接口,就可以使用这些接口作为约束来创建使用运算符或其他静态方法的泛型类型。

因此,您可以:

定义具有静态成员的接口。

使用接口定义实现了定义了运算符的接口的类。

创建依赖于静态接口方法的通用算法。

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/static-virtual-interface-members

前提条件

您需要设置您的计算机以运行支持C# 11的.NET 7。


0
作为一名新手C#程序员,我正在阅读这个主题,并想知道以下代码示例是否有任何帮助(我甚至不知道这是否是正确的方法)。对我来说,它允许我在接口后面编写默认行为。请注意,我使用了通用类型规范来定义一个(抽象)类。
namespace InterfaceExample
{
    public interface IDef
    {
        void FDef();
    }

    public interface IImp
    {
        void FImp();
    }

    public class AbstractImplementation<T> where T : IImp
    {
        // This class implements default behavior for interface IDef
        public void FAbs(IImp implementation)
        {
            implementation.FImp();
        }
    }

    public class MyImplementation : AbstractImplementation<MyImplementation>, IImp, IDef
    {
        public void FDef()
        {
            FAbs(this);
        }
        public void FImp()
        {
            // Called by AbstractImplementation
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyImplementation MyInstance = new MyImplementation();

           MyInstance.FDef();
        }
    }
}

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