背景:我想“扩展” .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: Avoid empty interfaces背道而驰,表示使用空接口作为合同是一种不良的设计和代码味道(我敢肯定大多数人都同意)。我的问题是,考虑到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>可能会抛出,这会造成混乱,因为抛出的行可能没有函数(或属性)调用。

08-28 22:29