为什么 Enumerable.OfType<TResult>(IEnumerable) 对于 ValueTuple 不能按预期工作?
Why is Enumerable.OfType<TResult>(IEnumerable) doesn't work as expected with ValueTuple?
行为
所以我有一个元组集合,如下所示:
public List<(ElementRowViewModel Parent, ElementBase Element)> ResultCollection { get; private set; } = new List<(ElementRowViewModel Parent, ElementBase Element)>();
当我尝试执行以下操作时
private async Task RegisterAndCommitElements<TElement>() where TElement : ElementBase
{
var specificElements = this.ResultCollection.OfType<(ElementRowViewModel parent, TElement element)>();
}
它什么也没产生。
但是当我这样做时
this.ResultCollection[0] is (ElementRowViewModel parent, SpecificElement element)
它returns正确,甚至
this.ResultCollection[0] is (ElementRowViewModel parent, TElement element)
它也 returns 正确。
我是不是误会了什么?或者在使用元组作为类型参数时,Enumerable.OfType(IEnumerable) 是否存在限制或已知错误?
ValueTuple<ElementRowViewModel, SpecificElement>
值不是 ValueTuple<ElementRowViewModel, ElementBase>
。存在从一个到另一个的隐式转换,但它是由 C# 语言定义的(基本上它执行逐元素转换)而不是由 CLR 类型系统定义,这是 OfType
关心的。
using System;
public class Test
{
public static void Main(string[] args)
{
var strings = ("hello", "there");
object boxed = strings;
Console.WriteLine(boxed is ValueTuple<string, string>); // True
Console.WriteLine(boxed is ValueTuple<object, object>); // False
// This is still valid though: element-wise implicit conversions.
(object, object) objects = strings;
}
}
第二个 is
测试 returns false 实际上是 OfType
方法所做的,所以你的值没有产生是有道理的。
如果您查看 Enumerable.OfType
you will see that internally it calls OfTypeIterator
的源代码,它看起来像这样:
private static IEnumerable<TResult> OfTypeIterator<TResult>(IEnumerable source)
{
foreach (object? obj in source)
{
if (obj is TResult result)
{
yield return result;
}
}
}
要重现此行为,您不需要枚举,只需要一个带有 is
检查内部的通用方法就足够了:
static bool IS<T>(object o) => o is T;
(ElementRowViewModel Parent, ElementBase Element) y = (new ElementRowViewModel(), new SpecificElement());
var z = (object) y;
Console.WriteLine(y is (ElementRowViewModel, SpecificElement)); // True
Console.WriteLine(IS<(ElementRowViewModel, SpecificElement)>(z)); // False
Console.WriteLine(z is (ElementRowViewModel, SpecificElement)); // True
你可以通过sharplab看到第二个和第三个语句之间的区别。
通用方法只执行“简单”类型测试(使用 IL isinst
指令),这将 return 为假(因为 z
的类型是 ValueTuple<ElementRowViewModel, ElementBase>
而不是等于 ValueTuple<ElementRowViewModel, SpecificElement>
).
非泛型检查(z is (ElementRowViewModel, SpecificElement)
)实际上被编译器变成了这样的东西:
ITuple tuple = obj as ITuple;
Console.WriteLine(tuple != null && tuple.Length == 2 && tuple[0] is ElementRowViewModel && tuple[1] is SpecificElement);
它对每个元组元素执行类型检查,结果为 true
。
行为
所以我有一个元组集合,如下所示:
public List<(ElementRowViewModel Parent, ElementBase Element)> ResultCollection { get; private set; } = new List<(ElementRowViewModel Parent, ElementBase Element)>();
当我尝试执行以下操作时
private async Task RegisterAndCommitElements<TElement>() where TElement : ElementBase
{
var specificElements = this.ResultCollection.OfType<(ElementRowViewModel parent, TElement element)>();
}
它什么也没产生。 但是当我这样做时
this.ResultCollection[0] is (ElementRowViewModel parent, SpecificElement element)
它returns正确,甚至
this.ResultCollection[0] is (ElementRowViewModel parent, TElement element)
它也 returns 正确。
我是不是误会了什么?或者在使用元组作为类型参数时,Enumerable.OfType(IEnumerable) 是否存在限制或已知错误?
ValueTuple<ElementRowViewModel, SpecificElement>
值不是 ValueTuple<ElementRowViewModel, ElementBase>
。存在从一个到另一个的隐式转换,但它是由 C# 语言定义的(基本上它执行逐元素转换)而不是由 CLR 类型系统定义,这是 OfType
关心的。
using System;
public class Test
{
public static void Main(string[] args)
{
var strings = ("hello", "there");
object boxed = strings;
Console.WriteLine(boxed is ValueTuple<string, string>); // True
Console.WriteLine(boxed is ValueTuple<object, object>); // False
// This is still valid though: element-wise implicit conversions.
(object, object) objects = strings;
}
}
第二个 is
测试 returns false 实际上是 OfType
方法所做的,所以你的值没有产生是有道理的。
如果您查看 Enumerable.OfType
you will see that internally it calls OfTypeIterator
的源代码,它看起来像这样:
private static IEnumerable<TResult> OfTypeIterator<TResult>(IEnumerable source)
{
foreach (object? obj in source)
{
if (obj is TResult result)
{
yield return result;
}
}
}
要重现此行为,您不需要枚举,只需要一个带有 is
检查内部的通用方法就足够了:
static bool IS<T>(object o) => o is T;
(ElementRowViewModel Parent, ElementBase Element) y = (new ElementRowViewModel(), new SpecificElement());
var z = (object) y;
Console.WriteLine(y is (ElementRowViewModel, SpecificElement)); // True
Console.WriteLine(IS<(ElementRowViewModel, SpecificElement)>(z)); // False
Console.WriteLine(z is (ElementRowViewModel, SpecificElement)); // True
你可以通过sharplab看到第二个和第三个语句之间的区别。
通用方法只执行“简单”类型测试(使用 IL isinst
指令),这将 return 为假(因为 z
的类型是 ValueTuple<ElementRowViewModel, ElementBase>
而不是等于 ValueTuple<ElementRowViewModel, SpecificElement>
).
非泛型检查(z is (ElementRowViewModel, SpecificElement)
)实际上被编译器变成了这样的东西:
ITuple tuple = obj as ITuple;
Console.WriteLine(tuple != null && tuple.Length == 2 && tuple[0] is ElementRowViewModel && tuple[1] is SpecificElement);
它对每个元组元素执行类型检查,结果为 true
。