默认接口成员
C# 8.0中新增了一个重要特性,就是Default Interface Members(默认接口成员)。默认接口成员允许在接口中定义默认的实现,这使得接口更加灵活,可以避免在实现该接口的类或结构体中重复编写相同的代码。本篇文章将详细介绍C# 8.0中Default Interface Members的各个方面。
# 常量
接口中可以定义常量,这些常量必须是静态的、只读的、且已知的。在C# 8.0中,接口中的常量可以定义默认值,从而成为默认接口成员。在实现该接口的类或结构体中,如果没有重写这些常量,将使用默认值。
public interface IExample
{
const int DefaultSize = 10;
const string DefaultName = "Default";
int Size { get; }
string Name { get; }
}
public class Example : IExample
{
public int Size { get; set; } = IExample.DefaultSize;
public string Name { get; set; } = IExample.DefaultName;
}
var example = new Example();
Console.WriteLine(example.Size); // 输出:10
Console.WriteLine(example.Name); // 输出:"Default"
# 运算符
接口中可以定义运算符,这些运算符可以是二元运算符或一元运算符。在C# 8.0中,接口中的运算符可以定义默认实现,从而成为默认接口成员。在实现该接口的类或结构体中,如果没有重写这些运算符,将使用默认实现。
public interface IExample
{
static IExample operator +(IExample a, IExample b) => new Example(a.Size + b.Size, a.Name + b.Name);
int Size { get; }
string Name { get; }
}
public class Example : IExample
{
public Example(int size, string name)
{
Size = size;
Name = name;
}
public int Size { get; }
public string Name { get; }
}
var example1 = new Example(10, "A");
var example2 = new Example(20, "B");
var example3 = example1 + example2;
Console.WriteLine(example3.Size); // 输出:30
Console.WriteLine(example3.Name); // 输出:"AB"
# 静态构造函数
接口中可以定义静态构造函数。在C# 8.0中,接口中的静态构造函数可以定义默认实现,从而成为默认接口成员。在实现该接口的类或结构体中,如果没有重写这个静态构造函数,将使用默认实现。
public interface IExample
{
static int MaxSize { get; }
static string DefaultName { get; }
static IExample DefaultInstance { get; }
static IExample()
{
MaxSize = 100;
DefaultName = "Default";
DefaultInstance = new Example(MaxSize, DefaultName);
}
int Size { get; }
string Name { get; }
}
public class Example
{
public int Size { get; }
public string Name { get; }
public Example(int size, string name)
{
Size = size;
Name = name;
}
}
var example1 = new Example(50, "A");
var example2 = IExample.DefaultInstance;
Console.WriteLine(example1.Size); // 输出:50
Console.WriteLine(example1.Name); // 输出:"A"
Console.WriteLine(example2.Size); // 输出:100
Console.WriteLine(example2.Name); // 输出:"Default"
# 嵌套类型
接口中可以定义嵌套类型,这些嵌套类型可以是类、结构体、枚举或接口。在C# 8.0中,嵌套类型可以定义默认接口成员,这些默认接口成员将被嵌套类型的所有实现继承。
public interface IExample
{
public class NestedClass
{
public virtual string GetName() => "Default";
}
int Size { get; }
string Name { get; }
}
public class Example : IExample
{
public int Size { get; set; }
public string Name { get; set; }
public class NestedClass : IExample.NestedClass
{
public override string GetName() => "Custom";
}
}
var example = new Example();
var nested = new Example.NestedClass();
Console.WriteLine(nested.GetName()); // 输出:"Custom"
# 静态字段、方法、属性、索引器和事件
接口中可以定义静态字段、方法、属性、索引器和事件。在C# 8.0中,这些静态成员可以定义默认接口实现,从而成为默认接口成员。在实现该接口的类或结构体中,如果没有重写这些静态成员,将使用默认实现。
public interface IExample
{
static int MaxSize { get; }
static string DefaultName { get; }
static IExample DefaultInstance { get; }
static IExample()
{
MaxSize = 100;
DefaultName = "Default";
DefaultInstance = new Example(MaxSize, DefaultName);
}
static int GetSize() => MaxSize;
static string GetName() => DefaultName;
static event EventHandler MyEvent;
int Size { get; }
string Name { get; }
}
public class Example : IExample
{
public int Size { get; }
public string Name { get; }
public Example(int size, string name)
{
Size = size;
Name = name;
}
static int GetSize() => 50;
static string GetName() => "Custom";
}
Console.WriteLine(IExample.GetSize()); // 输出:100
Console.WriteLine(IExample.GetName()); // 输出:"Default"
IExample.MyEvent += (sender, e) => Console.WriteLine("Event raised.");
IExample.MyEvent?.Invoke(null, EventArgs.Empty); // 输出:"Event raised."
# 显式接口实现
接口中可以定义显式接口实现,即为接口成员定义一个明确的实现,只能通过接口访问这个成员。在C# 8.0中,显式接口实现可以定义默认实现,从而成为默认接口成员。在实现该接口的类或结构体中,如果没有重写这些显式接口实现,将使用默认实现。
public interface IExample
{
void Print();
int Size { get; }
string Name { get; }
}
public class Example : IExample
{
public int Size { get; }
public string Name { get; }
public Example(int size, string name)
{
Size = size;
Name = name;
}
void IExample.Print() => Console.WriteLine("Default");
public void PrintCustom() => Console.WriteLine("Custom");
}
var example = new Example(10, "A");
example.Print(); // 输出:"Default"
((IExample)example).Print(); // 输出:"Default"
example.PrintCustom(); // 输出:"Custom"
# 访问修饰符
在C#中,默认接口成员的访问修饰符是public,如果需要使用其他访问修饰符,需要显式指定。
public interface IExample
{
static int MaxSize { get; }
static string DefaultName { get; }
static IExample DefaultInstance { get; }
static IExample()
{
MaxSize = 100;
DefaultName = "Default";
DefaultInstance = new Example(MaxSize, DefaultName);
}
private static int GetSize() => MaxSize;
protected static string GetName() => DefaultName;
internal static event EventHandler MyEvent;
int Size { get; }
string Name { get; }
}
public class Example : IExample
{
public int Size { get; }
public string Name { get; }
public Example(int size, string name)
{
Size = size;
Name = name;
}
protected static string GetName() => "Custom";
}
// 访问默认接口成员,使用public访问修饰符
Console.WriteLine(IExample.MaxSize); // 输出:100
Console.WriteLine(IExample.DefaultName); // 输出:"Default"
IExample.MyEvent += (sender, e) => Console.WriteLine("Event raised.");
IExample.MyEvent?.Invoke(null, EventArgs.Empty); // 输出:"Event raised."
// 访问指定访问修饰符的默认接口成员
// 使用private访问修饰符
Console.WriteLine(typeof(IExample).GetMethod("GetSize", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null)); // 输出:100
// 使用protected访问修饰符
Console.WriteLine(typeof(IExample).GetMethod("GetName", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null)); // 输出:"Default"
// 使用internal访问修饰符
Console.WriteLine(typeof(IExample).GetEvent("MyEvent", BindingFlags.NonPublic | BindingFlags.Static).AddMethod.IsAssembly); // 输出:True
# 结论
C# 8.0中的默认接口成员是一个强大的特性,它使得接口更加灵活、可扩展。通过定义默认接口成员,我们可以避免在实现接口的类或结构体中重复编写相同的代码,提高代码的可维护性和可读性。我们可以在接口中定义常量、运算符、静态构造函数、嵌套类型、静态成员和显式接口实现,并指定相应的访问修饰符。希望这篇文章能够帮助您理解C# 8.0中默认接口成员的各个方面,以便您在编写代码时更加灵活和高效地使用它们。