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

时间:2015-10-01 21:20:53

标签: c# generics generic-variance

背景:我想“扩展”.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实践更加一致?我想我不是唯一一个面临这个问题的人,所以我希望对此有所了解。

1 个答案:

答案 0 :(得分:2)

你的逻辑不会像你想象的那样有效。

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

这不会编译,因为不存在从IExtendedLazy<BaseClass>BaseClass的转换,因为转换运算符仅定义为ExtendedLazy<T>

这会强制您在使用界面时执行其他操作。添加T Value { get; }解决了CA1040的问题,并允许您访问基础值。

BTW Lazy<T>没有提供implicit operator T的原因是因为潜在的Func<T>可能会引起混淆,因为抛出的行很可能没有函数(或者属性)调用它。