【问题标题】:How to get a reference to a part of a list without creating a copy in C#如何在不使用 C# 创建副本的情况下获取对列表一部分的引用
【发布时间】:2017-02-17 08:00:54
【问题描述】:

我有一个从List<T> 派生的集合类,需要在该集合的一个实例上执行打包的action(即集合太大,所以我想将集合拆分为多个部分并执行@987654323 @在零件上)。请注意,action 具有类型签名,它是一些预定义的方法,需要相同类型的集合。

我知道

target.addRange(source.getRange(start, packageSize);

但这不是我想要的,因为为此我需要一个新的集合实例并将创建列表条目的副本。因为我知道该操作不会以任何方式操纵列表,所以我更愿意做类似的事情

action(source.Reference(fromIndex, toIndex);

不打算创建列表条目的副本(我知道这些只是“唯一”引用,但仍然有一个副本。在 C# 中有什么方法可以做到这一点吗?

当然,我可以将一个方法传递给action,它知道如何检索范围,但操作不需要知道我将执行细分为多个部分的意图。

【问题讨论】:

  • 感谢您的反馈。如果您能解释反对意见,我可以在下次写问题时采纳您的批评意见。
  • 我知道这些只是“唯一”参考,这正是您不必担心的原因。你会不断地复制这些参考资料,不要解决你没有的问题。
  • @HenkHolterman 感谢您的意见。我将使用我得到的答案中的建议来检查这是否有影响。该程序最终必须在资源(cpu 频率和内存)有限的设备上运行,而不是在普通 PC 上运行,因此我养成了贪婪使用任何资源的习惯。

标签: c# list collections


【解决方案1】:

您可以使用 LINQ 或简单的 C# 从列表中获取一部分,而不是将引用复制到新列表中。

使用SkipTake

var iterator = source.Skip(start).Take(end - start);

因为它是一个IEnumerable,你可以在被调用的方法中foreach(所以你可能需要改变你的方法签名):

foreach (var x in iterator)
{
    ...
}

或者创建您自己的状态机。生成的枚举也可以传递给方法:

private static IEnumerable<T> Take<T>(List<T> source, int start, int end)
{
    for (int i = start; i < end; i++)
    {
        yield return source[i];
    }
}

【讨论】:

  • 感谢您的回复,但我的印象是 Linq 语句会导致不同的类型。 action 期望给定类型作为参数。
  • 那么您的Action 期望什么?我告诉过使用IEnumerable&lt;T&gt; 可能需要更改签名。
  • 我开头提到的List&lt;T&gt;派生的集合类
  • 好的,但是为什么你的Action 需要那个?
  • 如(已编辑)问题中所述,它是给定的方法(取自库)。如果我自己编写,那么 Linq 方法会起作用,因为我可以更改它。
【解决方案2】:

只需使用 Linq:

var notAList = source.Skip(fromIndex).Take(toIndex-fromIndex);
action(notAList);

通过这种方式,您可以创建枚举器,它将枚举您的数字,而无需为所有数字实例化空间。

【讨论】:

  • source.Take(toIndex).Skip(fromIndex)会不会更容易?
  • @overactor 对我来说,您的示例似乎无效。
  • @overactor 也可以。但是Skip 现在会慢一点,因为它不能再利用源是List&lt;T&gt;
【解决方案3】:

您可以为此使用 LINQ 的 Skip()Take()

public void MyAction<T>(IEnumerable<T> range)
{
    // ...
}

然后打电话

MyAction(list.Skip(fromIndex).Take(toIndex-fromIndex));

请注意,此运算符(SkipTake)使用 延迟 执行并评估为惰性。因此,您确实可以将其视为对源列表中范围的一种引用

【讨论】:

    【解决方案4】:

    您可以创建自己的迭代器:

    public class MyPartListIterator: IEnumerator
    {
        private readonly IList list;
        private readonly int _startIdx;
        private readonly int _endIdx;
        private int _current;
    
        public MyIterator(IList list, int start, int end)
        {
            //do some validations before etc
            this._startIdx = this._current =  start;
            this._endIdx = end;
            this._list=  list;
    
        }
    
        public bool MoveNext() 
        {
           //do some checks against list was changed
    
           if (this._current >= this._endIdx) return false;
           this._current += 1;
        }
    
        public object Current => this._list[this._current];
    
        public void Reset() => this._current = this._startidx;
    }
    

    然后您可以将迭代器的实例传递给您的action

    为什么它比SkipTake 更好?它不会在开始索引之前枚举元素。

    当然你也可以准备通用版。

    凭记忆写,未经测试。使用 C# 6.0 语法糖。

    编辑:

    不可能传递与您的大 ILIst&lt;T&gt; 相同的 IList&lt;T&gt; 引用并且仅包含源列表的部分数据。

    【讨论】:

      【解决方案5】:

      您可以在这里使用扩展来获得您正在寻找的功能。

      public static class ListExtensions
      {
          public static void PerformActionOnSubset<T>(this IList<T> collection, int fromIndex,
              int toIndex, Action<IList<T>> action)
          {
              action(collection.Skip(fromIndex).Take(toIndex - fromIndex).ToList());
          }
      }
      
      myCollectionOfSomethings.PerformActionOnSubset(10, 30, myAction);
      

      【讨论】:

        猜你喜欢
        • 2013-03-12
        • 2011-02-27
        • 2017-12-28
        • 1970-01-01
        • 2019-05-12
        • 2015-03-17
        • 1970-01-01
        • 2010-09-19
        • 1970-01-01
        相关资源
        最近更新 更多