接口定义协定。 实现该协定的任何 class、record 或 struct 必须提供接口中定义的成员的实现。
接口可以定义成员 的默认实现 。 它还可以定义 static 成员,以便为通用功能提供单个实现。
从 C# 11 开始,接口可以定义 static abstract 或 static virtual 成员来声明实现类型必须提供声明的成员。 通常,static virtual 方法声明实现必须定义一组重载运算符。
在以下示例中,类 ImplementationClass 必须实现一个不含参数但返回 SampleMethod 的名为 void 的方法。
interface ISampleInterface
{
void SampleMethod();
}
class ImplementationClass : ISampleInterface
{
// Explicit interface member implementation:
void ISampleInterface.SampleMethod()
{
// Method implementation.
}
static void Main()
{
// Declare an interface instance.
ISampleInterface obj = new ImplementationClass();
// Call the member.
obj.SampleMethod();
}
}
有关详细信息和示例,请参阅接口。
访问修饰符
接口可以是命名空间或类的成员。 顶级接口(在命名空间中声明但未嵌套在另一种类型的接口中)可以声明为 public 或 internal。 默认为 internal。 嵌套接口声明(在另一类型内部声明的接口)可以使用任何访问修饰符声明。
没有实现的接口成员(抽象成员)是public隐式的,不能具有任何其他访问修饰符。 如果未指定任何访问修饰符,则可以使用任何访问修饰符(、或privatepublicprivate)声明具有默认实现protected的internal
接口成员
接口声明可以包含以下成员:
- 方法。
- 属性。
- 索引器。
- 事件。
- 常量。
- 运算符。
- 静态构造函数。
- 嵌套类型。
- 静态字段、方法、属性、索引器和事件。
- 使用显式接口实现语法的成员声明。
- 显式访问修饰符(抽象方法的默认访问是
public)。
默认接口成员
成员声明通常不包含正文,但接口成员可以声明正文。 接口中的成员主体是默认实现。 具有主体的成员允许接口为不提供重写实现的类和结构提供“默认”实现。
重要
添加默认接口成员会强制实现接口的任何 ref struct 添加该成员的显式声明。
静态抽象成员和虚拟成员
从 C# 11 开始,接口可以声明 static abstract 所有成员类型(字段除外)的成员 static virtual 和成员。 接口可以声明实现类型必须定义运算符或其他静态成员。 此功能使泛型算法能够指定类似于数字的行为。 可以在 .NET 运行时的数值类型中看到示例,如 System.Numerics.INumber<TSelf>。 这些接口定义由许多数值类型实现的常见数学运算符。 编译器必须在编译时解析对 static virtual 和 static abstract 方法的调用。 接口中声明的 static virtual和static abstract方法没有类似于类中声明的 virtual 或 abstract 方法的运行时调度机制。 相反,编译器使用编译时可用的类型信息。 因此,static virtual 方法几乎完全是在泛型接口中声明的。 此外,声明 static virtual 或 static abstract 方法的大多数接口都声明了其中一个类型参数必须实现已声明的接口。 例如,INumber<T> 接口声明 T 必须实现 INumber<T>。 编译器使用类型参数解析对接口声明中声明的方法和运算符的调用。 例如,int 类型实现 INumber<int>。 当类型参数 T 表示类型参数 int 时,将调用 static 上声明的 int 成员。 或者,当 double 是类型参数时,将调用在 static 类型上声明的 double 成员。
重要
使用表达式的编译时类型解析接口中声明的 static abstract 和 static virtual 方法的方法调度。 如果表达式的运行时类型派生自不同的编译时类型,将调用基(编译时)类型的静态方法。
可以通过使用有关接口中静态抽象成员的教程来尝试此功能。
接口继承
接口不能包含实例状态。 虽然现在允许使用静态字段,但接口中不允许使用实例字段。 接口中不支持实例自动属性,因为它们将隐式声明隐藏的字段。 此规则对属性声明有细微影响。 在接口声明中,以下代码不会像在接口class声明中那样声明自动实现的属性struct。 相反,它会声明一个属性,该属性没有默认实现,而必须在该实现接口的任何类型中实现它:
public interface INamed
{
public string Name {get; set;}
}
一个接口可从一个或多个基接口继承。 当接口继承自另一个接口时,实现派生接口的类型必须实现基接口中的所有成员以及派生接口中声明的成员,如以下代码所示:
public interface I1
{
void M1();
}
public interface I2 : I1
{
void M2();
}
public class C : I2
{
// implements I1.M1
public void M1() { }
// implements I2.M2
public void M2() { }
}
当接口 重写基接口中的方法实现时,必须使用显式接口实现语法。
基类型列表包含基类和接口时,基类必须是列表中的第 1 项。
实现接口的类可以显式实现该接口的成员。 显式实现的成员不能通过类实例访问,而只能通过接口实例访问。 此外,只能通过接口实例访问默认接口成员。
有关显式接口实现的详细信息,请参阅显式接口实现。
接口实现示例
下例演示了接口实现。 在此示例中,接口包含属性声明,类包含实现。 实现 IPoint 的类的任何实例都具有整数属性 x 和 y。
interface IPoint
{
// Property signatures:
int X { get; set; }
int Y { get; set; }
double Distance { get; }
}
class Point : IPoint
{
// Constructor:
public Point(int x, int y)
{
X = x;
Y = y;
}
// Property implementation:
public int X { get; set; }
public int Y { get; set; }
// Property implementation
public double Distance =>
Math.Sqrt(X * X + Y * Y);
}
class MainClass
{
static void PrintPoint(IPoint p)
{
Console.WriteLine("x={0}, y={1}", p.X, p.Y);
}
static void Main()
{
IPoint p = new Point(2, 3);
Console.Write("My Point: ");
PrintPoint(p);
}
}
// Output: My Point: x=2, y=3
C# 语言规范
有关详细信息,请参阅 C# 语言规范的 接口 部分、C# 8 - 默认接口成员的功能规范,以及接口 C# 11 - 静态抽象成员的功能规范。