为什么 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