【问题标题】:How to use ranges with List in C#?如何在 C# 中将范围与 List 一起使用?
【发布时间】:2021-06-06 05:53:51
【问题描述】:

在 C# 8 中,我们有范围来获取“子列表”。

虽然这有效:

var array = new string[] { "abc", "def", "ghi" };
var subArray = array[0..1]; // works

这不是:

var list = new List<string> { "abc", "def", "ghi" };
var subList = list[0..1]; // does not work

如何在列表中使用范围?

【问题讨论】:

    标签: c#


    【解决方案1】:

    列表本身不支持范围。但是,如果您只有一个 Range,您可以创建一个有用的扩展方法,如下所示:

    public static class ListExtensions
    {
        public static List<T> GetRange<T>(this List<T> list, Range range)
        {
            var (start, length) = range.GetOffsetAndLength(list.Count);
            return list.GetRange(start, length);
        }
    }
    

    然后你可以用它来获取你的子列表:

    var list = new List<string> { "abc", "def", "ghi" };
    var subList = list.GetRange(0..1);
    

    【讨论】:

    • 列表不支持索引?嗯?所以根据你的说法,myList[1] 应该会因为编译器错误而失败?
    • 列表支持索引。
    • 列表支持使用整数索引,但不支持System.IndexSystem.Range
    • @yaakov 那不正确。 System.Index (以某种方式)受支持。对于 Index idx = ^1; var x = new List&lt;string&gt;() { "abc", "def", "ghi" }[idx];x 的值将是 "ghi"(至少在 .NET 5 中)。
    • 是的,我忘记了编译器会尝试为可数集合解包。
    【解决方案2】:

    您可以使用ToArray()方法解决问题:

    var list = new List<string> { "abc", "def", "ghi" };
    var subList = list.ToArray()[0..1]; // works
    

    但请注意,List&lt;T&gt; 具有执行相同操作的 GetRange 方法:

    var list = new List<string> { "abc", "def", "ghi" };
    var subList = list.GetRange(0,1);//abc
    

    【讨论】:

    【解决方案3】:

    为了完整起见,我知道这并不是 OP 所要求的。

    List&lt;T&gt; 有一个GetRange-方法。

    https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.getrange?view=net-5.0

    List<?> range = list.GetRange(0,5);
    

    它与新的语言功能没有太大关系,但它确实做到了。

    请不要只使用ToArray 来使用那个花哨的新语言功能。这不值得开销。

    【讨论】:

      【解决方案4】:

      很遗憾,你不能。来自语言规范:

      例如,以下 .NET 类型同时支持索引和范围:String、Span 和 ReadOnlySpan。 List 支持索引,但不支持范围。

      Type Support for ranges and indices.

      所以唯一的方法就是这样,有点笨拙:

      var subList = list.ToArray()[0..1];
      

      编辑:我将把我的其余答案留作记录,但我认为使用Range 的答案更优雅,并且会明显更快,尤其是对于大切片。

      基准和扩展方法

      下面演示了使用ToArray() 可以减慢多少(使用 LINQPad - F.Rnd.Str 是我自己生成随机字符串的辅助方法):

      var list = new List<string>();
      for (int i = 0; i < 100000; i++)
      {
          list.Add(F.Rnd.Str);
      }
      
      // Using ToArray()
      var timer = new Stopwatch();
      timer.Start();
      var subList0 = list.ToArray()[42..142];
      timer.Stop();
      timer.ElapsedTicks.Dump("ToArray()");
      
      // Using Indices
      timer.Reset();
      timer.Start();
      var subList1 = new List<string>();
      for (int i = 42; i < 142; i++)
      {
          subList1.Add(list[i]);
      }
      timer.Stop();
      timer.ElapsedTicks.Dump("Index");
      
      // ToArray()
      // 3136
      // Index
      // 28
      

      因此,这样的扩展方法会很快:

      public static class ListExtensions
      {
          public static List<T> GetSlice<T>(this List<T> @this, int first, int last)
          {
              var slice = new List<T>();
              for (int i = first; i < last; i++)
              {
                  slice.Add(@this[i]);
              }
      
              return slice;
          }
      }
      

      然后您可以将其添加到基准测试中:

      // Using Extension Method
      timer.Reset();
      timer.Start();
      var subList2 = list.GetSlice(42, 142);
      timer.Stop();
      timer.ElapsedTicks.Dump("Extension");
      

      基准测试结果:

      // ToArray()
      // 2021
      // Index
      // 16
      // Extension
      // 15
      

      范围

      其中一个答案使用Range 来实现相同的目的,所以为了比较:

      timer.Reset();
      timer.Start();
      var range = new Range(42, 142);
      var (start, length) = range.GetOffsetAndLength(list.Count);
      var subList3 = list.GetRange(start, length);
      timer.Stop();
      timer.ElapsedTicks.Dump("Range");
      

      基准测试结果:

      // ToArray()
      // 2056
      // Index
      // 21
      // Extension
      // 16
      // Range
      // 17
      

      您可以看到底部三种方法在速度方面基本相同(因为我多次运行它,它们都在 15-21 滴答之间变化)。

      【讨论】:

      • 你的GetSLice-方法比List&lt;T&gt;.GetRange效率低很多。虽然基本上具有完全相同的功能。
      • 其实它在运行速度方面几乎相同,我将它添加到我的基准测试中。
      • 如果你用有意义的容量初始化新列表,并像原来一样使用 Array.Copy。你的 perfoamcnetest 不是一个很好的测试。您的扩展大大优于原始扩展。结帐referencesource.microsoft.com/#mscorlib/system/collections/… 使用计时器并且只执行一项操作并不是衡量性能的好方法。
      猜你喜欢
      • 2012-02-06
      • 2013-06-11
      • 1970-01-01
      • 2019-04-12
      • 2011-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多