C#8 接口默认值:如何以良好且可用的方式实现默认属性
C#8 interface defaults: How to implement default properties in a nice and usable way
我真的很喜欢 C#8 中接口默认实现的想法。但试过之后,失望很大...
所以这是一个简单的例子,我已经在 中找到了答案的一部分,原因是:
public interface IHasFirstNames
{
string? FirstName => FirstNames.FirstOrDefault();
List<string> FirstNames { get; }
}
public class Monster : IHasFirstNames
{
public List<string> FirstNames { get; } = new();
public Monster(params string[] firstNames)
{
FirstNames.AddRange(firstNames);
}
}
public static class Program
{
public static void Main()
{
var sally = new Monster("James", "Patrick");
var error = sally.FirstName; // Cannot resolve symbol FirstName
var works = ((IHasFirstNames)sally).FirstName;
}
}
但是,如果您总是不得不将其转换成丑陋的形式,那么在接口中使用 属性 的默认实现有什么意义呢?!
所以根据上面的铸造解决方案我已经试过了:
public interface IHasFirstNames
{
string? FirstName => FirstNames.FirstOrDefault();
List<string> FirstNames { get; }
}
public class Monster : IHasFirstNames
{
// Not ideal to declare the property here
// But at least the implementation is still in the interface
public string? FirstName => ((IHasFirstNames)this).FirstName;
public List<string> FirstNames { get; } = new();
public Monster(params string[] firstNames)
{
FirstNames.AddRange(firstNames);
}
}
public static class Program
{
public static void Main()
{
var sally = new Monster("James", "Patrick");
var error = sally.FirstName; // Whosebug!
}
}
但与预期相反,这会导致堆栈溢出,因为转换为 IHasFirstName
并没有真正调用接口的默认实现。
即使当我使用 IHasFirstName
类型的专用变量实现完整的 getter 时,它也会导致堆栈溢出。
我想出的唯一丑陋的解决方案是使用专用 getter 方法:
public interface IHasFirstNames
{
// Default implementation of a property is no use to me!
string? FirstName { get; }
// So I have to implement a getter method as default
public string? FirstNameGetter() => FirstNames.FirstOrDefault();
List<string> FirstNames { get; }
}
public class Monster : IHasFirstNames
{
public string? FirstName => ((IHasFirstNames)this).FirstNameGetter();
public List<string> FirstNames { get; } = new();
public Monster(params string[] firstNames)
{
FirstNames.AddRange(firstNames);
}
}
public static class Program
{
public static void Main()
{
var sally = new Monster("James", "Patrick");
var works= sally.FirstName;
}
}
不一定是方法。显然,如果它是一个具有不同名称的 属性 也可以。一旦界面中的 属性 和 class 中的名称应该相同,它就会变得丑陋。
真的没有更好的解决方案吗?
谢谢
正如其他人所指出的,这并不是默认接口方法的实际用途。正如文档所述:
The most common scenario is to safely add members to an interface
already released and used by innumerable clients.
对于您想要的使用方式,还有另一种机制可用:静态扩展方法。您可能已经知道,这种机制在 CLR 中被广泛用于 IEnumerable<T>
扩展方法。
对于您的情况,您可以在界面旁边包含以下静态扩展 class:
public static class HasFirstNamesExt
{
public static string? FirstName(this IHasFirstNames self)
{
return self.FirstNames.FirstOrDefault();
}
}
如果您使用它而不是接口本身中的声明,您的代码将按预期工作。
但是,这当然会遇到实施 classes 无法更改实施的主要缺点!如果你想支持它,你需要使用 abstract base classes 代替:
public static class Program
{
public static void Main()
{
var sally = new Monster("James", "Patrick");
Console.WriteLine(sally.FirstName); // "James"
}
}
public interface IHasFirstNames
{
List<string> FirstNames { get; }
string? FirstName { get; }
}
public abstract class HasFirstNamesBase : IHasFirstNames
{
public abstract List<string> FirstNames { get; }
public virtual string? FirstName => FirstNames.FirstOrDefault();
}
public class Monster : HasFirstNamesBase
{
public sealed override List<string> FirstNames { get; } = new();
public Monster(params string[] firstNames)
{
FirstNames.AddRange(firstNames);
}
}
请注意,在此使用模式中,您的实现 classes 始终派生自 HasFirstNamesBase
以使用默认实现。
派生的 classes 可以覆盖实现:
public class DifferentFirstNameImplementation: Monster
{
public DifferentFirstNameImplementation(params string[] firstNames)
:base (firstNames)
{
}
public override string? FirstName => FirstNames.LastOrDefault();
}
然后:
public static void Main()
{
var sally = new DifferentFirstNameImplementation("James", "Patrick");
Console.WriteLine(sally.FirstName); // "Patrick"
}
我真的很喜欢 C#8 中接口默认实现的想法。但试过之后,失望很大...
所以这是一个简单的例子,我已经在
public interface IHasFirstNames
{
string? FirstName => FirstNames.FirstOrDefault();
List<string> FirstNames { get; }
}
public class Monster : IHasFirstNames
{
public List<string> FirstNames { get; } = new();
public Monster(params string[] firstNames)
{
FirstNames.AddRange(firstNames);
}
}
public static class Program
{
public static void Main()
{
var sally = new Monster("James", "Patrick");
var error = sally.FirstName; // Cannot resolve symbol FirstName
var works = ((IHasFirstNames)sally).FirstName;
}
}
但是,如果您总是不得不将其转换成丑陋的形式,那么在接口中使用 属性 的默认实现有什么意义呢?!
所以根据上面的铸造解决方案我已经试过了:
public interface IHasFirstNames
{
string? FirstName => FirstNames.FirstOrDefault();
List<string> FirstNames { get; }
}
public class Monster : IHasFirstNames
{
// Not ideal to declare the property here
// But at least the implementation is still in the interface
public string? FirstName => ((IHasFirstNames)this).FirstName;
public List<string> FirstNames { get; } = new();
public Monster(params string[] firstNames)
{
FirstNames.AddRange(firstNames);
}
}
public static class Program
{
public static void Main()
{
var sally = new Monster("James", "Patrick");
var error = sally.FirstName; // Whosebug!
}
}
但与预期相反,这会导致堆栈溢出,因为转换为 IHasFirstName
并没有真正调用接口的默认实现。
即使当我使用 IHasFirstName
类型的专用变量实现完整的 getter 时,它也会导致堆栈溢出。
我想出的唯一丑陋的解决方案是使用专用 getter 方法:
public interface IHasFirstNames
{
// Default implementation of a property is no use to me!
string? FirstName { get; }
// So I have to implement a getter method as default
public string? FirstNameGetter() => FirstNames.FirstOrDefault();
List<string> FirstNames { get; }
}
public class Monster : IHasFirstNames
{
public string? FirstName => ((IHasFirstNames)this).FirstNameGetter();
public List<string> FirstNames { get; } = new();
public Monster(params string[] firstNames)
{
FirstNames.AddRange(firstNames);
}
}
public static class Program
{
public static void Main()
{
var sally = new Monster("James", "Patrick");
var works= sally.FirstName;
}
}
不一定是方法。显然,如果它是一个具有不同名称的 属性 也可以。一旦界面中的 属性 和 class 中的名称应该相同,它就会变得丑陋。
真的没有更好的解决方案吗?
谢谢
正如其他人所指出的,这并不是默认接口方法的实际用途。正如文档所述:
The most common scenario is to safely add members to an interface already released and used by innumerable clients.
对于您想要的使用方式,还有另一种机制可用:静态扩展方法。您可能已经知道,这种机制在 CLR 中被广泛用于 IEnumerable<T>
扩展方法。
对于您的情况,您可以在界面旁边包含以下静态扩展 class:
public static class HasFirstNamesExt
{
public static string? FirstName(this IHasFirstNames self)
{
return self.FirstNames.FirstOrDefault();
}
}
如果您使用它而不是接口本身中的声明,您的代码将按预期工作。
但是,这当然会遇到实施 classes 无法更改实施的主要缺点!如果你想支持它,你需要使用 abstract base classes 代替:
public static class Program
{
public static void Main()
{
var sally = new Monster("James", "Patrick");
Console.WriteLine(sally.FirstName); // "James"
}
}
public interface IHasFirstNames
{
List<string> FirstNames { get; }
string? FirstName { get; }
}
public abstract class HasFirstNamesBase : IHasFirstNames
{
public abstract List<string> FirstNames { get; }
public virtual string? FirstName => FirstNames.FirstOrDefault();
}
public class Monster : HasFirstNamesBase
{
public sealed override List<string> FirstNames { get; } = new();
public Monster(params string[] firstNames)
{
FirstNames.AddRange(firstNames);
}
}
请注意,在此使用模式中,您的实现 classes 始终派生自 HasFirstNamesBase
以使用默认实现。
派生的 classes 可以覆盖实现:
public class DifferentFirstNameImplementation: Monster
{
public DifferentFirstNameImplementation(params string[] firstNames)
:base (firstNames)
{
}
public override string? FirstName => FirstNames.LastOrDefault();
}
然后:
public static void Main()
{
var sally = new DifferentFirstNameImplementation("James", "Patrick");
Console.WriteLine(sally.FirstName); // "Patrick"
}