【问题标题】:Why is Enumerable.OfType<TResult>(IEnumerable) doesn't work as expected with ValueTuple?为什么 Enumerable.OfType<TResult>(IEnumerable) 不能按预期使用 ValueTuple?
【发布时间】:2021-04-06 12:46:11
【问题描述】:

行为

所以我有一个如下所示的元组集合:

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)

返回真,甚至

 this.ResultCollection[0] is (ElementRowViewModel parent, TElement element)

它也返回 true。

我是不是误会了什么?或者当使用元组作为类型参数时,Enumerable.OfType(IEnumerable) 是否存在限制或已知错误?

【问题讨论】:

    标签: .net linq generics type-parameter valuetuple


    【解决方案1】:

    ValueTuple&lt;ElementRowViewModel, SpecificElement&gt; 值不是 ValueTuple&lt;ElementRowViewModel, ElementBase&gt;。有一个从一个到另一个的隐式转换,但这是由 C# 语言定义的(基本上它执行元素转换)而不是由 OfType 关心的 CLR 类型系统定义。

    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;
        }
    }
    

    返回 false 的第二个 is 测试实际上是 OfType 方法正在执行的操作,因此没有产生您的值是有道理的。

    【讨论】:

      【解决方案2】:

      如果您查看Enumerable.OfType 的源代码,您会发现它在内部调用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 指令),这将返回 false(因为 z 的类型是 ValueTuple&lt;ElementRowViewModel, ElementBase&gt;,不等于 ValueTuple&lt;ElementRowViewModel, SpecificElement&gt;)。

      非泛型检查 (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

      【讨论】:

        猜你喜欢
        • 2021-10-30
        • 1970-01-01
        • 1970-01-01
        • 2021-05-30
        • 2020-03-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多