【发布时间】:2015-12-30 01:38:10
【问题描述】:
背景:我想“扩展”.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 实践更加一致?我想我不是唯一面临这个问题的人,所以希望能对此有所了解。
【问题讨论】:
-
CA1040 是一个错误的规则恕我直言。它告诉您使用属性而不是标记接口,除非这样的检查在运行时会慢几个数量级。在您的情况下,您必须向它添加一个
T Value { get; }成员无论如何。 -
@LucasTrzesniewski:如果你缓存结果,那么标记接口和属性之间的区别对于大多数用例来说是一个清洗,因为与调用数量相比,你通常使用相对较少的类型。 CA1040 无疑是较弱且更受质疑的建议之一,尽管许多人都同意,对于小型项目,标记不太灵活,但对于这些情况来说已经足够了。
-
@Guvante 当然,但是当我可以在单个表达式中使用
is运算符实现相同的事情时,为什么还要强迫我使用static ConcurrentDictionary<Type, bool>和反射调用之类的东西呢?而且它仍然比缓存查找快。 -
@LucasTrzesniewski:CA1040 说“如果您没有方法,就没有真正的界面,那么您就有了标记界面,而标记界面不是最好的主意”。老实说,标记接口并不是问题的正确解决方案,这往往是正确的而不是错误的。有些问题是一个很好的解决方案,我同意在这些情况下可以忽略 CA1040。然而,大多数想要使用标记界面的东西都想要使用多个标记界面,并且很快就会变得非常难以维护,这就是他们推荐反对它的原因。
-
@Guvante 是的,我同意在大多数情况下最好使用属性。我想我一开始就是不喜欢规则:)
标签: c# generics generic-variance