还不了解 C# 默认接口?
C# 8.0 默认接口实现
Intro
C# 8.0 开始引入了默认接口实现,也就是可以在接口里写方法实现。
在之前的版本中接口上是没有办法定义实现的,方法也都是 public
的,除了接口和属性之外是不能定义其他数据的,这也意味着,接口从一开始就要设计得比较好,否则在已有接口里增加新方法的时候其实现就必须要修改,否则就会编译失败,默认接口实现使得可以不造成破坏性变更的前提下在接口中新增加方法,只需要在接口中提供一个默认的实现即可。
Sample
下面我们来看一个示例吧:
internal interface IFly
{
string Name { get; }
}
internal class Superman : IFly
{
public string Name => nameof(Superman);
}
internal class MonkeyKing : IFly
{
public string Name => nameof(MonkeyKing);
}
这是一个基本的接口定义,并提供了两个实现,紧接着我们来为接口新增一个方法,
internal interface IFly
{
string Name { get; }
void Fly() => Console.WriteLine($"{Name.GetValueOrDefault((DefaultName))} is flying");
}
internal class Superman : IFly
{
public string Name => nameof(Superman);
public void Test()
{
((IFly) this).Fly();
Console.WriteLine(Name);
}
}
internal class MonkeyKing : IFly
{
public string Name => nameof(MonkeyKing);
public void Fly()
{
Console.WriteLine($"I'm {Name}, I'm flying");
}
}
我们在接口里增加了一个 Fly
方法,并提供了一个默认实现,在其中一个实现中进行了重写,我们来写一段代码测试一下吧
// Cannot resolve symbol 'Fly'
// new Superman().Fly();
IFly fly = new Superman();
fly.Fly();
fly = new MonkeyKing();
fly.Fly();
输出结果如下:
Superman is flying
I'm MonkeyKing, I'm flying
IFly
上面的示例中 Superman
没有定义 Fly
这个方法,是不能直接调用 Fly
方法的,需要先转成 IFly
接口然后再调用,此时方法实现是在接口里定义的逻辑,而 MonkeyKing
实现了 Fly
方法,所以会使用它自己的 Fly
实现,如上面所示。
除了上面的基本用法之外,现在可以在接口里定义静态字段静态方法来实现更好的方法复用,我们在上面的示例里演示一下,修改后的示例如下:
internal interface IFly
{
private const string DefaultName = nameof(IFly);
protected static string GetDefaultName() => DefaultName;
public static string GetPublicName() => DefaultName;
// Interface cannot contain instance fields
// private string name = "";
string Name { get; }
void Fly() => Console.WriteLine($"{Name.GetValueOrDefault((DefaultName))} is flying");
}
internal class MonkeyKing : IFly
{
public string Name => nameof(MonkeyKing);
public void Fly()
{
Console.WriteLine($"I'm {Name}, I'm flying, DefaultName:{IFly.GetDefaultName()}");
}
}
如果定义了 protected static
的方法或字段,则在实现接口的类中就可以通过 IFly.GetDefaultName()
来调用接口中的方法了,如果是 protected
就只能在实现它的类型中使用,如果要在没有实现接口的类型中调用可以声明为 public
就可以了,下面是在没有实现接口的类型中调用的示例:
// Cannot access protected method 'GetDefaultName' here
// IFly.GetDefaultName().Dump();
IFly.GetPublicName().Dump();
More
虽然现在可以这样用,但我个人还是推荐沿用之前的接口用法,不要轻易使用这个特性,提前设计提前规划才是正道,不要想着事后补偿,感觉这个特性比较合适的一个使用场景是现在基于接口的扩展方法,扩展方法作为一个接口的默认实现,具体类可以重写这个实现,使用示例如下:
Task<bool> SaveProperties(int id, Dictionary<string, object> properties)
{
if (properties is null || properties.Count == 0) return Task.FromResult(false);
var json = JsonConvert.SerializeObject(properties.Select(p => new PropertyModel()
{
PropertyName = p.Key,
PropertyValue = p.Value?.ToString()
}));
return SaveProperties(id, json);
}
Task<bool> SaveProperties(int id, string properties);
在之前的版本中,我一般都是把上面的方法作为一个扩展方法来用,有个默认接口实现之后也可以考虑加一个默认实现(仅限于业务代码中,针对类库代码,感觉还是越干净越好)
References
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/default-interface-methods-versions https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#default-interface-methods https://github.com/WeihanLi/SamplesInPractice/blob/master/CSharp9Sample/DefaultInterfaceImplement.cs
【限时删】刘*55页ppt大瓜,比项*醒的还要精彩!
又来一个神奇的网站!