接口 - 定义多种类型的行为

接口包含非抽象 classstruct 必须实现的一组相关功能的定义。 接口可以定义 static 方法。 接口可以为成员定义默认实现。 接口无法声明实例数据,例如字段、自动实现的属性或类似属性的事件。

例如,使用接口可以在类中包括来自多个源的行为。 该功能在 C# 中十分重要,因为该语言不支持类的多重继承。 此外,如果要模拟结构的继承,也必须使用接口,因为它们无法实际从另一个结构或类继承。

可使用 interface 关键字定义接口,如以下示例所示。

interface IEquatable<T>
{
    bool Equals(T obj);
}

接口名称必须是有效的 C# 标识符名称。 按照约定,接口名称以大写字母 I 开头。

实现 IEquatable<T> 接口的任何类或结构都必须包含与该接口指定的签名匹配的 Equals 方法的定义。 因此,可以依靠实现 T 的类型 IEquatable<T> 的类来包含 Equals 方法,类的实例可以通过该方法确定它是否等于相同类的另一个实例。

IEquatable<T> 的定义不为 Equals 提供实现。 类或结构可以实现多个接口,但是类只能从单个类继承。

有关抽象类的详细信息,请参阅抽象类、密封类及类成员

接口可以包含实例方法、属性、事件、索引器或这四种成员类型的任意组合。 接口可以包含静态构造函数、字段、常量或运算符。 从 C# 11 开始,不是字段的接口成员可以是 static abstract。 接口不能包含实例字段、实例构造函数或终结器。 接口成员默认是公共的,可以显式指定可访问性修饰符(如 publicprotectedinternalprivateprotected internalprivate protected)。 private 成员必须有默认实现。

若要使用隐式实现实现接口成员,实现类的对应成员必须是公共的、非静态的,并且具有与接口成员相同的名称和签名。 但是,当接口只是内部或在其签名中使用内部类型时,可以改用显式接口实现,这不需要实现成员是公共的。

注意

当接口声明静态成员时,实现该接口的类型也可能声明具有相同签名的静态成员。 它们是不同的,并且由声明成员的类型唯一标识。 在类型中声明的静态成员不会覆盖接口中声明的静态成员。

实现接口的类或结构必须为所有已声明的成员提供实现,而非接口提供的默认实现。 但是,如果基类实现接口,则从基类派生的任何类都继承该实现。

下面的示例演示 IEquatable<T> 接口的实现。 实现类 Car 必须提供 Equals 方法的实现。

public class Car : IEquatable<Car>
{
    public string? Make { get; set; }
    public string? Model { get; set; }
    public string? Year { get; set; }

    // Implementation of IEquatable<T> interface
    public bool Equals(Car? car)
    {
        return (this.Make, this.Model, this.Year) ==
            (car?.Make, car?.Model, car?.Year);
    }
}

类的属性和索引器可以为接口中定义的属性或索引器定义额外的访问器。 例如,接口可能会声明包含 get 取值函数的属性。 实现此接口的类可以声明包含 getget 取值函数的同一属性。 但是,如果属性或索引器使用显式实现,则访问器必须匹配。 有关显式实现的详细信息,请参阅显式接口实现接口属性

接口可从一个或多个接口继承。 派生接口从其基接口继承成员。 实现派生接口的类必须实现派生接口中的所有成员,包括派生接口的基接口的所有成员。 该类可以隐式转换为派生接口或其任何基接口。 类可能通过它继承的基类或通过其他接口继承的接口来多次包含某个接口。 但是,类只能提供接口的实现一次,并且仅当类将接口作为类定义的一部分 (class ClassName : InterfaceName) 进行声明时才能提供。 如果由于继承实现接口的基类而继承了接口,则基类会提供接口的成员的实现。 但是,派生类可以重新实现任何虚拟接口成员,而不是使用继承的实现。 当接口声明方法的默认实现时,实现该接口的任何类都会继承该实现(你需要将类实例强制转换为接口类型,才能访问接口成员上的默认实现)。

基类还可以使用虚拟成员实现接口成员。 在这种情况下,派生类可以通过重写虚拟成员来更改接口行为。 有关虚拟成员的详细信息,请参阅多态性

使用内部接口

只要可以公开访问接口签名中的所有类型,就可以使用具有公共成员的隐式实现来实现内部接口。 但是,当接口在其成员签名中使用内部类型时,隐式实现变得不可能,因为实现类成员需要在公开内部类型时公开。 在这种情况下,必须使用显式接口实现。

以下示例演示这两种方案:

// Internal type that cannot be exposed publicly
internal class InternalConfiguration
{
    public string Setting { get; set; } = "";
}

// Internal interface that CAN be implemented with public members
// because it only uses public types in its signature
internal interface ILoggable
{
    void Log(string message); // string is public, so this works with implicit implementation
}

// Interface with internal accessibility using internal types
internal interface IConfigurable
{
    void Configure(InternalConfiguration config); // Internal type prevents implicit implementation
}

// This class shows both implicit and explicit interface implementation
public class ServiceImplementation : ILoggable, IConfigurable
{
    // Implicit implementation works for ILoggable because string is public
    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }

    // Explicit implementation required for IConfigurable because it uses internal types
    void IConfigurable.Configure(InternalConfiguration config)
    {
        // Implementation here
        Console.WriteLine($"Configured with: {config.Setting}");
    }
    
    // If we tried implicit implementation for IConfigurable, this wouldn't compile:
    // public void Configure(InternalConfiguration config) // Error: cannot expose internal type
}

在前面的示例中, IConfigurable 接口在其方法签名中使用内部类型 InternalConfiguration 。 该 ServiceImplementation 类无法使用隐式实现,因为这样会要求将 Configure 方法设为公共方法,而如果方法签名中包含内部类型,这是不允许的。 而是使用显式接口实现,该实现没有访问修饰符,只能通过接口类型进行访问。

相比之下, ILoggable 可以通过公共成员隐式实现接口,因为其签名(string)中的所有类型都可以公开访问,即使接口本身是内部接口。

有关显式接口实现的详细信息,请参阅 显式接口实现

接口摘要

接口具有以下属性:

  • 在 8.0 以前的 C# 版本中,接口类似于只有抽象成员的抽象基类。 实现接口的类或结构必须实现其所有成员。
  • 从 C# 8.0 开始,接口可以为部分或全部成员定义默认实现。 实现接口的类或结构不一定要实现具有默认实现的成员。 有关详细信息,请参阅默认接口方法
  • 接口无法直接进行实例化。 其成员由实现接口的任何类或结构来实现。
  • 一个类或结构可以实现多个接口。 一个类可以继承一个基类,还可实现一个或多个接口。