C#8.0中有趣的新功能之一是对默认接口方法(也称为虚拟扩展方法)的支持。 本文介绍了默认接口方法以及如何在C#8.0中使用它们的讨论。
在C#8.0之前,C#中的接口不能包含方法定义。 您只能在接口中包括方法声明,并且默认情况下,接口的成员是公共的和抽象的。 此外,在C#8.0之前,接口不能包含字段,也不能具有私有成员,受保护成员或内部成员。 如果在接口中引入了新成员(即方法声明),则必须对实现该接口的所有类进行更新。
使用C#8.0,您现在可以在接口中具有方法的默认实现。 接口成员也可以是私有的,受保护的和静态的。 不能在扩展接口的类中访问接口的受保护成员。 而是只能在派生接口中访问它们。 接口成员也可以是虚拟的和抽象的。 但是,接口的虚拟成员可以被派生的接口覆盖,但不能被扩展接口的类覆盖。 请注意,您仍然不能在接口中包含实例成员。
为什么要使用默认接口方法?
默认接口方法是接口中包含具体实现的方法。 如果扩展接口的类未实现该方法,则将从接口使用该方法的默认实现。 这是一个有用的功能,因为它可以帮助开发人员在不破坏现有功能(即该接口的现有实现)的情况下将方法添加到接口的未来版本中。
考虑以下接口ILogger。
public interface ILogger
{
public void Log(string message);
}
以下两个类扩展了ILogger接口并实现Log()方法。
public class FileLogger : ILogger
{
public void Log(string message)
{
//Some code
}
}
public class DbLogger : ILogger
{
public void Log(string message)
{
//Some code
}
}
到目前为止,一切都很好。 现在,假设您要在ILogger接口中引入一个新方法,该方法将接受两个参数,即要记录的文本消息和日志级别。 以下代码段显示了日志级别枚举。
public enum LogLevel
{
Info, Debug, Warning, Error
}
现在是ILogger界面的外观。
public interface ILogger
{
public void Log(string message);
public void Log(string message, LogLevel logLevel);
}
现在,这是问题所在:您将不得不在扩展ILogger接口的所有类中实现此新方法。 如果您不这样做,编译器将标记一个错误,指出尚未实现新方法。 您的界面可能已在其他几个库中使用,甚至在各个团队中也使用过。 因此,实施此更改将是一项艰巨而艰巨的任务。
默认接口方法示例
这是抢救默认接口方法的地方。 您可以为新的Log方法提供默认实现,如下面的代码段所示。
public interface ILogger
{
public void Log(string message);
public void Log(string message, LogLevel logLevel)
{
Console.WriteLine("Log method of ILogger called.");
Console.WriteLine("Log Level: "+ logLevel.ToString());
Console.WriteLine(message);
}
}
扩展ILogger接口的类不再需要实现新的Log方法。 因此,以下代码有效-编译器不会抛出错误。
public class FileLogger : ILogger
{
public void Log(string message)
{
//Some code
}
}
public class DbLogger : ILogger
{
public void Log(string message)
{
//Some code
}
}
默认接口方法不继承
现在,如下面的代码片段所示,创建FileLogger类的实例,并调用我们新添加到ILogger接口的Log方法。
FileLogger fileLogger = new FileLogger();
fileLogger.Log("This is a test message.", LogLevel.Debug);
因此,这证明了扩展类没有继承默认接口方法,即,扩展接口的类不了解默认接口方法。
默认界面方法和钻石问题
现在的百万美元问题是,默认接口方法如何避免“菱形问题”,即它如何使用接口解决多重继承问题? 我们来看一个例子。 考虑下面的代码清单。
public interface A
{
public void Display();
}
public interface B : A
{
public void Display()
{
Console.WriteLine("Interface B.");
}
}
public interface C : A
{
public void Display()
{
Console.WriteLine("Interface C.");
}
}
public class MyClass : B, C
{
}
当您编译上述程序时,将出现一个编译错误,指出MyClass类未实现接口成员A.Display(),正确的是。 因为Display方法在接口A中缺少默认实现,所以我们必须在MyClass类中提供一个实现才能满足编译器的要求。
来做吧。 这是更新的代码供您参考。
public interface A
{
public void Display();
}
public interface B : A
{
public void Display()
{
Console.WriteLine("Interface B.");
}
}
public interface C : A
{
public void Display()
{
Console.WriteLine("Interface C.");
}
}
public class MyClass : B, C
{
public void Display()
{
Console.WriteLine("MyClass.");
}
}
现在,创建类MyClass的实例,并调用Display方法,如下面的代码片段所示。
static void Main(string[] args)
{
A obj = new MyClass();
obj.Display();
Console.Read();
}
这次将调用哪种显示方法? 为避免歧义,将应用最具体的覆盖规则。 因此,将调用类MyClass的Display方法。
C#8.0中的抽象类与接口
C#8.0中的抽象类和接口是否相同? 并不是的。 尽管抽象类和接口现在看起来不止一种相似,但两者之间还是存在细微差别。 您仍然不能从一个以上的类中扩展一个类。 并且与抽象类不同,接口不能具有实例成员。 而且,您仍然可以实现多个接口。
默认接口方法使开发人员可以利用traits编程技术,该技术使您可以重用与不相关类型有关的方法。 假设您已经建立了一个可供许多开发人员使用的库,并且您想发布该库的新版本。 如果您的接口添加了新成员,则可以定义其实现,以便可以继续利用库中的类型,而无需更改代码库。
From: https://www.infoworld.com/article/3455239/how-to-use-default-interface-methods-in-csharp-8.html