CodeGym /课程 /C# SELF /接口的概念和语法

接口的概念和语法

C# SELF
第 23 级 , 课程 0
可用

1. 什么是接口?

如果说抽象类是带点实现的“半成品”模板,那接口就是一堆要求清单:某个东西(类)必须会啥,才能在某个抽象场景下被用。

在编程里,接口就像是个“合同”或者“要求清单”,规定了对象要有啥行为。它描述了一组公开的方法、属性、索引器和事件,实现这个接口的类必须提供这些东西。接口就像在说:“你要想叫IDriveable(能开车的),那你就得有Drive()Stop()方法。”

你可以把接口想象成员工要求清单:比如你要招“厨师”,那接口里就写着:“会做饭、会上菜、会洗手”。具体厨师怎么做这些事——那是他自己的事。只要外面看起来都按合同来就行。

注意:接口只描述要有啥,不管怎么做

用OOP术语简单说

  • 接口描述了对象的外部“样子”(API):能干啥。
  • 没有状态(没有字段)。传统理解下接口也没有实现,但现在C#新版本有例外(比如默认方法),后面会细说。
  • 一个类可以实现任意多个接口(不像类继承只能一个!)。

生活中的类比

USB接口——大家都知道可以插鼠标、键盘、U盘、耳机,甚至咖啡机(真的有!)。“鼠标”里面啥样不重要,只要有USB口、支持协议就行。这就是接口!

接口有啥用?

  • 降低耦合。代码只和接口打交道,不管具体类。我们“面向接口编程”。
  • 能实现多继承行为:一个类能实现好几个接口。
  • 标准化:可以做通用机制:比如所有能比较的对象都实现IComparable接口。
  • 方便测试:测试时可以用“假实现”替换真实现。
  • 插件扩展:能加新“模块”,不用改老代码。

2. 接口的声明语法

来点代码!在C#里,接口用关键字interface声明,名字一般都用I开头:


// 声明接口
public interface IPrintable
{
    // 抽象方法——合同
    void Print();

    // 也可以声明属性
    string Name { get; set; }
}

记住:接口的方法和属性没有实现(没有方法体,只有签名——和抽象方法一样)。

接口的关键点

  • 不能在接口里声明字段(成员变量)。
  • 所有方法、属性、事件和索引器默认都是public(实现时也必须是)。
  • 接口不能有构造函数(因为没状态)。
  • 接口不管实现它的类是抽象的还是具体的。

接口实战

来,把接口加到我们的学习小项目里。假设现在有个“可打印”接口(IPrintable),被Report类实现,还有个新类Invoice也实现它。

public interface IPrintable
{
    void Print();
    string Name { get; set; }
}

现在定义个实现这个接口的类:

public class Report : IPrintable
{
    public string Name { get; set; }

    public Report(string name)
    {
        Name = name;
    }

    // 实现接口的方法
    public void Print()
    {
        Console.WriteLine($"打印报告: {Name}");
    }
}

再来一个完全不同的类,但接口一样:

public class Invoice : IPrintable
{
    public string Name { get; set; }

    public Invoice(string name)
    {
        Name = name;
    }

    public void Print()
    {
        Console.WriteLine($"打印发票: {Name}");
    }
}

现在我们可以写个方法,能处理任何可打印的东西:

public static void PrintAnything(IPrintable printable)
{
    printable.Print(); // 就这!不管是报告还是发票——只要能打印就行。
}

用法示例:

var report = new Report("月度报告");
var invoice = new Invoice("发票 #12345");

PrintAnything(report);  // 打印报告: 月度报告
PrintAnything(invoice); // 打印发票: 发票 #12345

这样接口就让你写出通用、可扩展又优雅的代码啦。

3. 接口的实现

类实现接口

类用冒号实现接口(对,和继承一样):

public class Ticket : IPrintable
{
    public string Name { get; set; }
    public void Print()
    {
        Console.WriteLine($"打印票据: {Name}");
    }
}

注意:类必须实现接口的所有成员。而且实现的成员必须是public

如果漏实现一个成员:

public class BrokenTicket : IPrintable
{
    // 漏了Print()的实现
    public string Name { get; set; }
}
// 编译错误: 'BrokenTicket' 没有实现接口成员 'IPrintable.Print()'

多个接口

类可以用逗号实现多个接口:

public interface IStorable
{
    void Store();
}

public class MultiPurposeDoc : IPrintable, IStorable
{
    public string Name { get; set; }
    public void Print()
    {
        Console.WriteLine("打印文档");
    }

    public void Store()
    {
        Console.WriteLine("保存文档");
    }
}

4. 接口到底有啥用?

你可能现在心里想:“好吧,语法我懂了。可这玩意儿现实里到底有啥用,除了让我这课更难?”别急!接口其实是专业开发里最常用的工具之一。

职责分离和低耦合(Decoupling / Loose Coupling):

  • 比如你要写个音乐播放器。它不关心音乐从哪来——本地文件、网络、CD都行。它只关心音乐源能不能给它音频流。
  • 你可以定义个IAudioSource接口,里面有GetAudioStream()方法。
  • 然后你有FileAudioSourceInternetAudioSourceCDAudioSource这些类,都实现这个接口。
  • 播放器只和IAudioSource打交道,不知道具体类型。明天你要加个BluetoothAudioSource,播放器代码都不用改!只要写个新类实现IAudioSource就行。这样系统就超级灵活、好扩展。这就是低耦合——组件只依赖抽象(接口),不依赖具体实现。

多态和统一处理:

就像前面PrintAnything的例子,你可以有一堆不同类型的对象,但它们有共同的行为(接口描述的)。你可以对所有这些对象调用同一个方法(Print()),不用管它到底是报告、发票还是票据。这样代码又简洁又通用。

单元测试(Unit Testing):

这大概是接口最重要的用途之一。你测某个组件时,它经常要依赖别的组件(比如保存数据的类要用数据库类)。

你不用真的传DatabaseSaver(那得真有数据库才能测!),你可以传个“假货”(或者叫“mock”),只要它实现IDataSaver接口就行。这个“mock”对象只模拟保存行为,不会真去数据库。这样你就能隔离、快速、无依赖地测你的组件。

开发API和框架:

你写库或框架时,肯定想给开发者留“扩展点”。接口就特别适合。你可以说:“你要让你的组件和我系统配合,就实现这个接口。” .NET标准库里接口一大堆(比如IEnumerable<T>IDisposableIComparable<T>)——它们为常见场景定了合同。

直接面向接口编程(Programming to an Interface):

老司机常说:“面向接口编程,不要面向实现。”意思是你定义变量类型或方法参数时,别用具体类(Car),用接口(IDriveable)。这样代码更灵活,不依赖细节,实现换起来也方便。

5. 用接口时常见的坑

坑1:想new接口的实例。
你可以写Cat murzik = new Cat("Мурзик", 3);,因为Cat是具体类。但你不能ITalkable talker = new ITalkable();。接口只是合同、模板,没有实现,不能直接创建。这就像图纸,不是房子。

坑2:忘了实现接口的所有成员。
你说你类实现了接口,比如IMyInterface,那你必须实现它所有方法。漏一个都会编译报错:MyClass没实现IMyInterface.TheMissingMethod()

坑3:实现时访问修饰符写错。
接口方法默认public,实现时也必须public。你要是写成privateprotected,编译器直接报错。承诺了就得公开实现。

坑4:想在接口里加字段或构造函数。
接口描述的是行为,不是状态。所以不能加字段或构造函数。你要加就编译报错。只能有属性,而且只是getter/setter的描述。

坑5:把override和接口实现搞混。
override是重写基类方法用的,实现接口不用它——你只要写public方法,签名对就行。这点很容易搞混。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION