【问题标题】:Why does C# (4.0) not allow co- and contravariance in generic class types?为什么 C# (4.0) 不允许泛型类类型中的协变和逆变?
【发布时间】:2011-02-02 05:51:30
【问题描述】:

造成这种限制的真正原因是什么?只是必须完成的工作吗?概念上很难吗?不可能吗?

当然,不能在字段中使用类型参数,因为它们始终是可读写的。但这不可能是答案,不是吗?

这个问题的原因是我正在写一篇关于 C# 4 中的方差支持的文章,我觉得我应该解释一下为什么它仅限于委托和接口。只是为了颠倒举证责任。

更新: 埃里克问了一个例子。

这个怎么样(不知道这是否有意义,但是:-))

public class Lookup<out T> where T : Animal {
  public T Find(string name) {
    Animal a = _cache.FindAnimalByName(name);
    return a as T;
  }
}

var findReptiles = new Lookup<Reptile>();
Lookup<Animal> findAnimals = findReptiles;

将它放在一个类中的原因可能是类本身中保存的缓存。并且请不要将您不同类型的宠物命名相同!

顺便说一句,这将我带到optional type parameters in C# 5.0 :-)

更新 2: 我没有声称 CLR 和 C# 应该允许这样做。只是试图了解导致它没有的原因。

【问题讨论】:

  • 当然,这是一个合理的例子,但你没有展示任何不能用接口完成的事情。只需制作接口 ILookup 并让 Lookup 实现它。您的类差异方案添加了哪些与接口差异相比引人注目的额外好处?
  • 实际上没有。除此之外,它的代码更少。让我把举证责任倒过来。我们如何解释为什么不支持它。实际上,我并不是要求实施它。 “一直都是这样”不算数! :-)
  • @Eric Lippert:我当然可以想象协变结构的用例。 KeyValuePair 怎么样?可以定义一个 IKeyValPair,并有一个结构 KeyValPair 来实现它,但这在很多使用场景中都需要非常可怕的装箱。
  • @supercat:这是可以使协变的类型的一个很好的例子。这里的关键是数据类型是逻辑上不可变的,因此您不必担心在构造函数中设置字段后会更改其值。如果我们有 struct 或 class 差异,那就是我想要开始的地方。
  • @Eric Lippert:我知道你讨厌可变结构(顺便说一句,我刚刚打开了一个关于这个主题的聊天室)但是对于结构的协变正确性来说,不变性是必要的吗?传递给期望 IEnumerable 的例程的 List 仍然是 List,但传递给期望 KeyValuePair 的例程的 KeyValuePair 将 成为 KeyValuePair。假设 Key 是可变的,那么将 Key 设置为 Dog 会造成什么危害?一种是将狗存放在动物中,而不是猫中。

标签: c# generics c#-4.0 covariance contravariance


【解决方案1】:

如果它们被允许,则可以定义有用的 100% 类型安全(无内部类型转换)类或结构,如果它们的构造函数接受一个或多个 T 或 T 供应商,则它们的类型 T 是协变的。如果它们的构造函数接受一个或多个 T 消费者,则可以定义有用的 100% 类型安全的类或结构,这些类或结构相对于 T 是逆变的。除了使用“新”而不是使用静态工厂方法(很可能来自名称与接口名称相似的类)之外,我不确定类比接口有多大优势,但我可以当然可以看到使用不可变结构支持协方差的用例。

【讨论】:

    【解决方案2】:

    首先,正如 Tomas 所说,CLR 不支持它。

    其次,这将如何运作?假设你有

    class C<out T>
    { ... how are you planning on using T in here? ... }
    

    T 只能用于输出位置。正如您所注意到的,该类不能有任何类型为 T 的字段,因为可以写入该字段。该类不能有任何采用 T 的方法,因为这些是逻辑写入的。假设您有此功能 - 您将如何利用它?

    这对于不可变类将很有用,例如,如果我们可以使拥有 T 类型的只读字段合法;这样我们就大大减少了它被不正确写入的可能性。但是很难想出其他允许以类型安全方式变化的场景。

    如果你有这样的场景,我很乐意看到它。这将有助于有朝一日在 CLR 中实现这一点。

    更新:见

    Why isn't there generic variance for classes in C# 4.0?

    关于这个问题的更多信息。

    【讨论】:

    • 好问题 :) 我记得去年在 Lang.NET 上 Anders 和一些 Java 编译器极客(抱歉)之间的讨论,Java 专家要求这个特性。他似乎知道他为什么要问。但我不记得了。我想到了没有状态的类。我会尝试在问题中弥补一些东西。
    • 这对于不可变数据类型(例如Tuple&lt;T1, T2&gt;)来说绝对是一个不错的功能,但我同意这对于更改 CLR 来说不够令人信服:-)。
    • 另一个用例是幻像类型,其中 T 仅用于类型安全,而不用于类的实现。
    • Lazy 和 Task 类不适合在 T 上协变吗?他们仅在其公共 API 的外部位置使用 T。
    • 很多例子。建设者呢?当然,这些都是协变和逆变泛型的候选者。一个类的状态阻止它从通用方差中受益的概念似乎非常站不住脚。它仅在 state 对应于类型 var 时才成立,该类型完全依赖于手头的类。换句话说,不难想象一个类会产生 Ts 或消费 Ts,但不能两者兼而有之。而且因为您可以分解出一个接口,所以在我看来,这并不意味着不应该允许类有差异。 耸耸肩
    【解决方案3】:

    据我所知,CLR 不支持此功能,因此添加此功能也需要 CLR 方面的大量工作。我相信 CLR 在 4.0 版之前实际上支持接口和委托的协变和逆变,因此这是一个相对简单的实现扩展。

    (不过,为类支持此功能肯定很有用!)

    【讨论】:

    • 你是对的。接口和委托的泛型类型参数的变化来自 CLR 2.0 - 只是在 C# 中没有
    猜你喜欢
    • 2013-11-29
    • 1970-01-01
    • 1970-01-01
    • 2018-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多