【问题标题】:How to simplify the initialization of List<List<Int32>> as IEnumerable<IEnumerable<Int32>>?如何将 List<List<Int32>> 的初始化简化为 IEnumerable<IEnumerable<Int32>>?
【发布时间】:2021-08-03 16:38:22
【问题描述】:

我正在做以下集合初始化:

Int32[,] v1 = new Int32[2, 2] { { 1, 2 }, { 3, 4 } };

IEnumerable<IEnumerable<Int32>> v2 = new List<List<Int32>> { { 2, 3 }, { 3, 4 } };

在第二行我得到错误:

No overload for method 'Add' takes 2 arguments

有没有办法使用最新的 C# 版本创建一个 IEnumerable&lt;IEnumerable&lt;Int32&gt;&gt; 而无需为主集合中的每个项目添加 new List&lt;Int32&gt;

IEnumerable<IEnumerable<Int32>> v2 = new List<List<Int32>> { 
  new List<Int32> { 2, 3 }, 
  new List<Int32> { 3, 4 } 
};

【问题讨论】:

标签: c# list ienumerable new-operator nested-lists


【解决方案1】:

来自Object and Collection Initializers article in the C# Programming Guide

集合初始化器允许您在初始化实现IEnumerable 并具有Add 具有适当签名作为实例方法或扩展方法的集合类型时指定一个或多个元素初始化器。

因此,这里的问题是 List&lt;T&gt;.Add() 没有适当的重载采用可变数量的参数。

如果您定义了一个自定义集合类型,您可以添加一个采用params 参数的Add() 方法:

class NestedList<T> : IEnumerable<IEnumerable<T>>
{
    List<List<T>> _list;

    public NestedList()
    {
        _list = new List<List<T>>();
    }

    public void Add(params T[] innerItems)
    {
        _list.Add(new List<T>(innerItems));
    }

    public IEnumerator<IEnumerable<TI>> GetEnumerator()
    {
        return _list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

现在我们有了一个接受可变数量参数的Add() 方法,您可以使用嵌套初始化器:

IEnumerable<IEnumerable<int>> v2 = new NestedList<int> { { 1, 2 }, { 3, 4 } };

【讨论】:

    【解决方案2】:

    无需为 List&lt;List&lt;T&gt;&gt; 编写包装器!

    集合初始值设定项只是调用对象声明的名为Add 的方法的语法糖,而List&lt;T&gt; 不声明采用2 个参数(或1 个以上的任意数量的参数)的Add 方法。

    然而,集合初始化器还将调用一个名为Add 的扩展方法。这意味着我们可以这样写:

    public static class ListExtensions
    {
         public static void Add<T>(this List<List<T>> list, params T[] items)
         {
             list.Add(new List<T>(items));
         }
    }
    

    这让我们接着写:

    IEnumerable<IEnumerable<int>> collection = new List<List<int>>()
    {
        { 1, 2 },
        { 3, 4 },
    };
    

    See it working here.


    值得注意的是,这种Add 方法只能作为扩展方法添加——List&lt;T&gt; 不可能自己声明它。这样做的原因是只有当列表包含其他列表时才有意义,并且无法定义仅适用于某些类型列表的实例方法。但是,您可以在扩展方法上添加各种类似的限制。您可以在 List&lt;T&gt; 上编写扩展方法,该方法仅适用于 T 是一个类时,或者实际上仅适用于 T 是另一个列表时!

    【讨论】:

    • 出于好奇,您将Add() 方法编写为extension 方法,用于List&lt;List&lt;T&gt;&gt;。编译器如何区分扩展方法和List&lt;T&gt; 的预定义Add 函数。我之所以这样问是因为 如果扩展方法与类型中定义的方法具有相同的签名,则永远不会调用它。 from MSDN
    • @PrasadTelkikar 扩展方法与实例方法没有相同的签名 - 它明显不同。如果你去掉扩展方法并写var list = new List&lt;List&lt;int&gt;&gt;(); list.Add(1, 2),那显然是一个编译器错误:List&lt;T&gt; 上没有方法,它需要 2 个参数。因此,扩展方法和实例方法之间没有冲突。即使你写了list.Add(1),这仍然是一个编译器错误,因为List&lt;List&lt;int&gt;&gt; 上的Add 方法只接受List&lt;int&gt;,而不接受int
    • 是的,你是对的。 Add() 方法采用单个参数存储到列表中,但在您的情况下,您存储的是 T[],这使得这两个 Add() 方法彼此不同。我是正确的还是遗漏了什么?
    • 比这更简单:List&lt;List&lt;int&gt;&gt;.Add 需要一个List&lt;int&gt;,所以调用它的唯一方法是使用list.Add(new List&lt;int&gt;() { .. })。我的扩展方法需要单独的ints,所以你可以称它为list.Add(1, 2, 3, ...)。它们有不同的参数类型。
    • @PrasadTelkikar 见这里:dotnetfiddle.net/onVyT4。当您传递单个整数或整数数组时,它会选择 Add(params int[] values) 扩展方法。当您传递List&lt;int&gt; 时,它会选择实例Add(List&lt;T&gt; item) 重载。
    猜你喜欢
    • 1970-01-01
    • 2021-04-05
    • 1970-01-01
    • 2012-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多