为什么泛型接口中的静态方法会影响协变?

Why does the static method in the generic interface affect covariance?

我有接口

interface IContainer<out T>
{
    T Unpack();
}

它是协变的。如果我添加一个使用 T 作为输入的方法 - 协方差将被破坏。

interface IContainer<out T> // compilation ERROR!!!
{
    T Unpack();
    void RepackWith(T value);
}

可以理解,因为下面的方法

void UseContainer(IContainer<Base> container)
{
    container.RepackWith(new Base());
}

调用错误

IContainer<Derived> d = ...
UseConatiner(d);

RepackWith(new Base()) 不包含对 Derived 实例的期望。

但是为什么我把static方法加到界面里面还是一样呢?

interface IContainer<out T> // compilation ERROR!!!
{
    T Unpack();
    static IContainer<T> Pack(T value) { ... }
}

至于我,它不应该影响协方差,因为使用静态和非静态方法的上下文不同。

void UseContainer<T>(IContainer<T> container)
{
    // Everything is OK, the container can be 
    // IContainer<Derived> for UseContainer<Base> call. 
    // container.Unpack() returns a Derived instance 
    // that is fully compatible with Base instance expectations.
    var content  = container.Unpack();

    // Everything is OK with the following too.
    // The static call has its own context IContainer<T>, 
    // and it can't break any expectation of type.
    var newContainer = IContainer<T>.Pack(content);
    var newContent = newContainer.Unpack();
}

此限制的原因是什么? 我错过了哪个代码示例以了解它如何与协方差不兼容?

我找不到任何技术原因,它不在文档、规范草案或我在 GitHub 上可以找到的任何内容中。然而,这很可能(并且令人不满意)归结为,它就是这样,只是遵循相同的方差规则更简单,或者他们还没有实现它。

我个人不使用静态默认接口方法。但是,如果你真的想把东西藏在难以找到的地方。你可以做这样的事情。虽然,我真的不确定你为什么会这样。

public interface IContainer
{
   public static IContainer<T> Pack<T>(T value)
   {
      return null;
   }
}

public interface IContainer<out T> : IContainer
{
   T Unpack();
}

此外,您最好不要继承它,因为它会导致您变得荒谬,例如:

IContainer<Bob>.Pack<int>(4);

您可以做的另一件事就是制作一个扩展方法并继续。

这将在未来的 C# 版本中得到修复。事实上,对于 .net 5.0,如果您将语言版本指定为“预览版”,则此问题已得到修复。

例如,考虑您的示例代码:

interface IContainer<out T>
{
    T Unpack();
    static IContainer<T> Pack(T value) { return default; }
}

在项目文件中使用 <LangVersion>latest</LangVersion>(对于 .net 5.0 目标)这会产生以下错误:

error CS8904: Invalid variance: The type parameter 'T' must be contravariantly valid on 'IContainer.Pack(T)' unless language version 'preview' or greater is used. 'T' is covariant.

请注意,错误消息明确告诉您可以使用“预览”。

所以将语言版本更改为 <LangVersion>preview</LangVersion> 允许编译。

似乎缺乏对此的支持是因为 C# 功能尚未完全实现。