我真的不明白接口存在的原因。据我所知,这是c#中不存在的多继承的一种工作(至少我是这么被告知的)。

我所看到的是,您预定义了一些成员和函数,然后必须在类中再次重新定义它们。从而使接口成为冗余。它只是感觉像句法……嗯,垃圾对我来说(请没有冒犯的意思。Junk是指无用的东西)。

在下面的例子中,我将创建一个名为Pizza的基类,而不是一个接口。

简单示例(取自不同的堆栈溢出贡献)

public interface IPizza
{
    public void Order();
}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}

当前回答

class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

让我从另一个角度来描述一下。让我们根据上面的例子来创建一个故事;

程序、机器和IMachine是我们故事的演员。程序想要运行,但它没有这个能力,而机器知道如何运行。机器和IMachine是最好的朋友,但程序和机器没有说话的关系。于是程序和IMachine做了一个交易,决定IMachine通过观察机器(就像一个反射器)来告诉程序如何运行。

Program在IMachine的帮助下学习如何运行。

接口提供通信和开发松散耦合的项目。

PS:我把具体类的方法作为私有。我在这里的目标是通过防止访问具体的类属性和方法来实现松耦合,只允许通过接口访问它们。(所以我明确地定义了接口的方法)。

其他回答

接口定义了某种功能的提供者和相应的使用者之间的契约。它将实现与契约(接口)解耦。您应该了解一下面向对象的体系结构和设计。你可能想从维基百科开始:http://en.wikipedia.org/wiki/Interface_(computing)

考虑一下不控制或不拥有基类的情况。

以可视化控件为例,在。net for Winforms中,它们都继承自。net框架中完全定义的基类Control。

让我们假设您从事创建自定义控件的业务。你想要建立新的按钮,文本框,列表视图,网格,等等,你希望他们都有特定的功能独特的控件集。

例如,你可能想要一种通用的方法来处理主题,或者一种通用的方法来处理本地化。

在这种情况下,你不能“只创建一个基类”,因为如果你这样做,你必须重新实现所有与控件相关的东西。

相反,您将从按钮,TextBox, ListView, GridView等下降,并添加您的代码。

但这就产生了一个问题,你现在如何识别哪些控件是“你的”,你如何构建一些代码来表明“对于窗体上所有属于我的控件,将主题设置为X”。

输入接口。

接口是一种查看对象、确定对象是否遵守某种约定的方法。

您可以创建“YourButton”,从Button向下延伸,并添加对所需的所有接口的支持。

这将允许您编写如下代码:

foreach (Control ctrl in Controls)
{
    if (ctrl is IMyThemableControl)
        ((IMyThemableControl)ctrl).SetTheme(newTheme);
}

如果没有接口,这是不可能的,相反,你必须写这样的代码:

foreach (Control ctrl in Controls)
{
    if (ctrl is MyThemableButton)
        ((MyThemableButton)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableTextBox)
        ((MyThemableTextBox)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableGridView)
        ((MyThemableGridView)ctrl).SetTheme(newTheme);
    else ....
}

接口用于应用不同类之间的连接。例如,你有一个关于汽车和树的类;

public class Car { ... }

public class Tree { ... }

您希望为这两个类添加一个可燃功能。但是每个职业都有自己的燃烧方式。所以你只要做;

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}

我在这个页面上搜索了“合成”这个词,但一次也没看到。这个答案是对前面提到的答案的补充。

在面向对象项目中使用接口的一个绝对重要的原因是,它们允许你更倾向于组合而不是继承。通过实现接口,您可以将您的实现与应用于它们的各种算法解耦。

德里克·巴纳斯(Derek Banas)的“装饰图案”教程(有趣的是,它也以披萨为例)是一个有价值的插图:

https://www.youtube.com/watch?v=j40kRwSm4VE

没有人真正清楚地解释过接口是如何有用的,所以我打算尝试一下(并从Shamim的回答中窃取一点想法)。

让我们以披萨订购服务为例。您可以有多种类型的披萨,每个披萨的共同操作是在系统中准备订单。每个披萨都要准备,但每个披萨的准备方式不同。例如,当点菜时,系统可能需要验证餐厅是否有特定的食材,并将不需要做深盘披萨的食材放在一边。

当用代码写这个的时候,技术上你可以这样做

public class Pizza
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;
                
            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;
                
            //.... etc.
        }
    }
}

然而,深盘披萨(在c#术语中)可能需要在Prepare()方法中设置与填充披萨皮不同的属性,因此最终会有许多可选属性,并且类不能很好地伸缩(如果添加新的披萨类型会怎样)。

解决这个问题的正确方法是使用接口。界面声明所有披萨都可以准备,但每个披萨可以准备不同。如果你有以下接口:

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

现在,您的订单处理代码不需要确切地知道订购了什么类型的披萨来处理配料。它只有:

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

尽管每种类型的披萨都是不同的,但这部分代码并不需要关心我们处理的是哪种类型的披萨,它只知道它正在为披萨调用,因此每次调用Prepare都会根据其类型自动正确地准备每个披萨,即使集合有多种类型的披萨。