在不违反空接口规则的情况下创建协变泛型类型
Creating covariant generic types without violating empty interface rules
背景:我想 "expand" .NET Lazy<>
类型支持 Lazy<T>
和底层 T
对象之间的隐式转换,以便能够自动解包包含价值。我能够相当轻松地做到这一点:
public class ExtendedLazy<T> : Lazy<T>
{
public ExtendedLazy() : base() {}
public ExtendedLazy(bool isThreadSafe) : base(isThreadSafe) { }
public ExtendedLazy(Func<T> valueFactory) : base(valueFactory) { }
// other constructors
public static implicit operator T(ExtendedLazy<T> obj)
{
return obj.Value;
}
}
我想通过使 T
协变更进一步,这样我就可以将 ExtendedLazy<Derived>
的实例分配给 ExtendedLazy<Base>
。由于 class 定义中不允许使用方差修饰符,因此我不得不求助于一个空接口来实现这一点:
public interface IExtendedLazy<out T>
{
}
并将我的 class 定义更改为
public class ExtendedLazy<T> : Lazy<T>, IExtendedLazy<T>
这很好用,我能够利用这个协变类型:
ExtendedLazy<DerivedClass> derivedLazy = new ExtendedLazy<DerivedClass>();
IExtendedLazy<BaseClass> baseLazy = derivedLazy;
虽然编译和工作正常,但它违背了 CA1040: Avoid empty interfaces 的说法,后者说使用空接口作为契约是糟糕的设计和代码味道(我相信大多数人都同意)。我的问题是,鉴于 CLR 无法识别 class 定义中的变体泛型类型,还有什么其他方法可以使其与可接受的 OO 实践更加一致?我想我不是唯一面临这个问题的人,所以我希望能对此有所了解。
你的逻辑不会像你想象的那样有效。
ExtendedLazy<DerivedClass> derivedLazy = new ExtendedLazy<DerivedClass>();
IExtendedLazy<BaseClass> baseLazy = derivedLazy;
BaseClass v = baseLazy;
这不会编译,因为不存在从 IExtendedLazy<BaseClass>
到 BaseClass
的转换,因为转换运算符仅为 ExtendedLazy<T>
.
定义
这将迫使您在使用该界面时做一些其他事情。添加 T Value { get; }
既解决了 CA1040 的问题,又让您可以访问基础值。
顺便说一句,Lazy<T>
不提供 implicit operator T
的原因是因为底层 Func<T>
可能会抛出这会造成混淆,因为抛出的行很可能没有函数(或 属性)对其调用。
背景:我想 "expand" .NET Lazy<>
类型支持 Lazy<T>
和底层 T
对象之间的隐式转换,以便能够自动解包包含价值。我能够相当轻松地做到这一点:
public class ExtendedLazy<T> : Lazy<T>
{
public ExtendedLazy() : base() {}
public ExtendedLazy(bool isThreadSafe) : base(isThreadSafe) { }
public ExtendedLazy(Func<T> valueFactory) : base(valueFactory) { }
// other constructors
public static implicit operator T(ExtendedLazy<T> obj)
{
return obj.Value;
}
}
我想通过使 T
协变更进一步,这样我就可以将 ExtendedLazy<Derived>
的实例分配给 ExtendedLazy<Base>
。由于 class 定义中不允许使用方差修饰符,因此我不得不求助于一个空接口来实现这一点:
public interface IExtendedLazy<out T>
{
}
并将我的 class 定义更改为
public class ExtendedLazy<T> : Lazy<T>, IExtendedLazy<T>
这很好用,我能够利用这个协变类型:
ExtendedLazy<DerivedClass> derivedLazy = new ExtendedLazy<DerivedClass>();
IExtendedLazy<BaseClass> baseLazy = derivedLazy;
虽然编译和工作正常,但它违背了 CA1040: Avoid empty interfaces 的说法,后者说使用空接口作为契约是糟糕的设计和代码味道(我相信大多数人都同意)。我的问题是,鉴于 CLR 无法识别 class 定义中的变体泛型类型,还有什么其他方法可以使其与可接受的 OO 实践更加一致?我想我不是唯一面临这个问题的人,所以我希望能对此有所了解。
你的逻辑不会像你想象的那样有效。
ExtendedLazy<DerivedClass> derivedLazy = new ExtendedLazy<DerivedClass>();
IExtendedLazy<BaseClass> baseLazy = derivedLazy;
BaseClass v = baseLazy;
这不会编译,因为不存在从 IExtendedLazy<BaseClass>
到 BaseClass
的转换,因为转换运算符仅为 ExtendedLazy<T>
.
这将迫使您在使用该界面时做一些其他事情。添加 T Value { get; }
既解决了 CA1040 的问题,又让您可以访问基础值。
顺便说一句,Lazy<T>
不提供 implicit operator T
的原因是因为底层 Func<T>
可能会抛出这会造成混淆,因为抛出的行很可能没有函数(或 属性)对其调用。