我正在尝试创建一个在通用
ReadOnlySpan<T>
上运行的方法,以便我可以传递数组或堆栈分配的缓冲区,而无需更改我的代码。我还想将类型 T
限制为仅是值类型。
方法如下:
void Test1<T>(ReadOnlySpan<T> foo) where T : struct
{
}
但是,如果不传递类型参数,编译器无法推断类型:
Test1(new[] {42}); // Doesn't compile
Test1<int>(new[] {42}); // Compiles
我也尝试了不同的方法,发现了一些有趣的行为:
void Test1<T>(ReadOnlySpan<T> foo) where T : struct
{
}
void Test2<T>(T[] foo) where T : struct
{
}
void Test3<T>(T[] foo)
{
}
void Test4<T>(ReadOnlySpan<T> foo)
{
}
void Test5<T>(List<T> foo) where T : struct
{
}
void Test6<T>(List<T> foo)
{
}
Test1(new[] {42}); // NO
Test2(new[] {42}); // OK
Test3(new[] {42}); // OK
Test4(new[] {42}); // NO
Test5(new List<int> {42}); // OK
Test6(new List<int> {42}); // OK
Test1<int>(new[] {42}); // OK
Test4<int>(new[] {42}); // OK
似乎类型推断仅适用于
*Span
无法正常工作(数组和 List
可以正常工作),我猜 ReadOnlySpan
是 ref struct
的事实可能是导致问题。
造成这种不同行为的真正原因是什么?
有没有办法帮助编译器推断
ReadOnlySpan
方法中 Test1()
的数据类型?
更新:
我尝试显式声明参数的类型:
ReadOnlySpan<int> testSpan = new[] {42};
Test1(testSpan); // OK!
Span<int> test = new[] {42};
Test1(test); // NO
看来这里真正的问题是隐式转换。
谁能更好地解释一下吗?
让我们跟随类型推理过程!
在第一阶段,此案例符合:
如果 Eᵢ 具有类型 U 并且相应的参数是值参数 (§15.6.2.2),则从 U 到 Tᵢ 进行下限推断。
将其应用到这种情况,那就是“
new[] {42}
具有类型int[]
,然后从int[]
到ReadOnlySpan<T>
进行下限推断”。
从类型 U 到类型 V 的下界推断如下:这里
U
是
int[]
,V
是ReadOnlySpan<T>
,而这一步正是出错的地方。通过这些案例,ReadOnlySpan<T>
不是T
,它也不能为空,所以我们可以:
否则,通过检查是否适用以下任何情况来确定集合 U₁...Uₑ 和 V₁...Vₑ:这是它做出如下推论的地方:
给定一个
List<Foo>
参数和
参数,那么IEnumerable<T>
必须是T
或其某个超类(也称为“下界”)。 在上述情况下,U₁...Uₑ 将是Foo
Foo
,V₁...Vₑ 将是
T
。不幸的是,这些情况都不适用,也没有做出任何推论。这些情况实际上考虑了很多事情 - 它处理数组类型(例如 int[]
到
T[]
),
V 是数组类型 V₁[...],U 是相同秩的数组类型 U₁[...]从数组类型到集合接口的隐式转换(例如
int[]
到
IEnumerable<T>
),
V 是 IEnumerable<V₁>
、
、ICollection<V₁>
、IReadOnlyList<V₁>
或IReadOnlyCollection<V₁>
之一,U 是单维数组类型 U₁[] 以及相互继承/实现的泛型类型的类型参数(例如IList<V₁>
List<int>
到
IList<T>
)。
V 是构造类、结构、接口或委托类型 C,并且存在唯一类型 C但是,它不会检查任何任意隐式转换。,使得 U(或者,如果 U 是类型参数,则其有效基类或其有效接口集的任何成员)是与 C
. 相同、继承自(直接或间接)或实现(直接或间接)C。
也就是说,该语言可以被设计为在上面的第二种情况中添加一个特殊情况。除了
ReadOnlySpan<T>
等之外,还将
Span<T>
/
IEnumerable<T>
添加到界面列表中。让我们希望这在未来能够实现。