从 .NET Core 3.0 上的 C# 8.0 开始,能够在声明接口成员时定义实现。 最多见的方案是安全地将成员添加到已经由无数客户端发布并使用的接口。html
在本教程中,你将了解:git
须要将计算机设置为运行 .NET Core,包括 C# 8.0 预览版编译器。 从 Visual Studio 2019 或最新的 .NET Core 3.0 预览版 SDK 开始,可使用 C# 8.0 预览版编译器。 从 .NET Core 3.0 预览版 4 开始提供默认接口成员。github
public interface ICustomer { IEnumerable<IOrder> PreviousOrders { get; } DateTime DateJoined { get; } DateTime? LastOrder { get; } string Name { get; } IDictionary<DateTime, string> Reminders { get; } }
他们定义了表示订单的第二个接口:安全
public interface IOrder { DateTime Purchased { get; } decimal Cost { get; } }
经过这些接口,团队能够为其用户生成一个库,以便为其客户创造更好的体验。 他们的目标是与现有客户创建更深刻的关系,并改善他们与新客户的关系。测试
如今,是时候为下一版本升级库了。 其中一个请求的功能能够为拥有大量订单的客户提供忠实客户折扣。 不管客户什么时候下单,都会应用这一新的忠实客户折扣。 该特定折扣是每位客户的财产。 ICustomer 的每一个实现均可觉得忠实客户折扣设置不一样的规则。this
添加此功能的最天然方式是使用用于应用任何忠实客户折扣的方法来加强 ICustomer
接口。 此设计建议引发了经验丰富的开发人员的关注:“一旦发布,接口就是固定不变的! 这是一项突破性的变革!” C# 8.0 添加了默认接口实现 用于升级接口。 库做者能够向接口添加新成员,并为这些成员提供默认实现。spa
默认接口实现使开发人员可以升级接口,同时仍容许任何实现器替代该实现。 库的用户能够接受默认实现做为非中断性变动。 若是他们的业务规则不一样,则能够进行替代。设计
团队就最有可能的默认实现达成一致:针对客户的忠实客户折扣。code
升级应提供用于设置两个属性的功能:符合折扣条件所需的订单数量以及折扣百分比。 这使其成为用于默认接口成员的完美方案。 能够向 ICustomer 接口添加方法,并提供最有可能的实现。 全部现有的和任何新的实现均可以使用默认实现,或者提供其本身的实现。htm
首先,将新方法添加到实现中:
// Version 1: public decimal ComputeLoyaltyDiscount() { DateTime TwoYearsAgo = DateTime.Now.AddYears(-2); if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10)) { return 0.10m; } return 0; }
库做者编写了用于检查实现的第一个测试:
SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31)) { Reminders = { { new DateTime(2010, 08, 12), "childs's birthday" }, { new DateTime(1012, 11, 15), "anniversary" } } };
SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m); c.AddOrder(o); o = new SampleOrder(new DateTime(2103, 7, 4), 25m); c.AddOrder(o); // 检查折扣 ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
注意测试的如下部分:
// 检查折扣 ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
从 SampleCustomer
到 ICustomer
的强制转换是必需的。 SampleCustomer
类不须要为 ComputeLoyaltyDiscount
提供实现;这由 ICustomer
接口提供。 可是,SampleCustomer
类不会从其接口继承成员。 该规则没有更改。 若要调用在接口中声明和实现的任何方法,该变量的类型必须是接口的类型,在本示例中为 ICustomer
。
// Version 2: public static void SetLoyaltyThresholds(TimeSpan ago, int minimumOrders = 10, decimal percentageDiscount = 0.10m) { length = ago; orderCount = minimumOrders; discountPercent = percentageDiscount; }
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // 2年 private static int orderCount = 10; private static decimal discountPercent = 0.10m; public decimal ComputeLoyaltyDiscount() { DateTime start = DateTime.Now - length; if ((DateJoined < start) && (PreviousOrders.Count() > orderCount)) { return discountPercent; } return 0; }
这个小代码片断中展现了许多新的语言功能。 接口如今能够包含静态成员,其中包括字段和方法。 还启用了不一样的访问修饰符。 其余字段是专用的,新方法是公共的。 接口成员容许使用任何修饰符。
使用常规公式计算忠实客户折扣但参数有所不一样的应用程序不须要提供自定义实现;它们能够经过静态方法设置自变量。 例如,如下代码设置“客户答谢”,奖励任何成为会员超过一个月的客户:
ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m); Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
目前添加的代码提供了方便的实现,可用于用户须要相似默认实现的项目的方案,或用于提供一组不相关的规则。 对于最后一个功能,让咱们稍微重构一下代码,以实现用户可能须要基于默认实现进行生成的方案。
假设有一家想要吸引新客户的初创企业。 他们为新客户的第一笔订单提供 50% 的折扣, 而现有客户则会得到标准折扣。 库做者须要将默认实现移入 protected static
方法,以便实现此接口的任何类均可以在其实现中重用代码。 接口成员的默认实现也调用此共享方法:
public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this); protected static decimal DefaultLoyaltyDiscount(ICustomer c) { DateTime start = DateTime.Now - length; if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount)) { return discountPercent; } return 0; }
在实现此接口的类的实现中,替代能够调用静态帮助程序方法,并扩展该逻辑以提供“新客户”折扣:
public decimal ComputeLoyaltyDiscount() { if (PreviousOrders.Any() == false) return 0.50m; else return ICustomer.DefaultLoyaltyDiscount(this); }
能够在咱们位于 [GitHub 上的示例存储库]中查看整个完成的代码(能够在 GitHub 上的示例存储库中获取入门应用程序)。
这些新功能意味着,当这些新成员拥有合理的默认实现时,接口能够安全地更新。 精心设计接口,以表达可由多个类实现的单个功能概念。 这样一来,在发现针对同一功能概念的新要求时,能够更轻松地升级这些接口定义。