在不违反空接口规则的情况下创建协变泛型

本文关键字:创建 情况下 泛型 规则 接口 | 更新日期: 2023-09-27 18:15:48

背景:我想"扩展". 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>。由于在类定义中不允许使用方差修饰符,因此我不得不使用空接口来实现这一点:

public interface IExtendedLazy<out T>
{
}
并将我的类定义更改为

public class ExtendedLazy<T> : Lazy<T>, IExtendedLazy<T>

这工作得很好,我能够利用这个协变类型:

ExtendedLazy<DerivedClass> derivedLazy = new ExtendedLazy<DerivedClass>();
IExtendedLazy<BaseClass> baseLazy = derivedLazy;

虽然这个编译和工作得很好,但它违反了CA1040:避免空接口,它说使用空接口作为契约是一个糟糕的设计和代码气味(我相信大多数人都同意)。我的问题是,鉴于CLR无法识别类定义中的可变泛型类型,还有什么其他方法可以使它与可接受的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>可能会抛出,这会令人困惑,因为抛出的那行很可能没有函数(或属性)调用。