为什么泛型接口中的静态方法会影响协变?
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# 功能尚未完全实现。
我有接口
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# 功能尚未完全实现。