【问题标题】:Is it possible to return part of a list by reference?是否可以通过引用返回列表的一部分?
【发布时间】:2015-09-24 08:12:51
【问题描述】:

我的情况是我只想分配另一个列表的一部分。如果可能,请参考。

到目前为止我所做的是这里:

List<string> partialList = originalList.Skip(start).Take(end-start).ToList();

示例:一个包含 6 个元素的列表,开始为 2,结束为 4。

In New List   Element
N             0
N             1
Y             2
Y             3
Y             4
N             5

现在据我了解 .ToList() 它会创建原始结果的副本。因此,这将是价值而不是参考。所以我的问题是:是否有任何“通过引用”的方式来实现我想要的结果?

【问题讨论】:

  • "如果可能的话,请参考。"您是否希望这样,如果您更新一个列表中的共享元素,它也会在另一个列表中更新?
  • 根据您的需要,您可以创建一个包含列表引用和两个整数的类。请更好地解释您的用例以及为什么您认为“通过引用”会有所帮助。
  • 你能写一些代码来演示结果列表的预期用途吗?
  • 不确定,您到底想要什么。在您的示例中,如果您说oldList[3].SomeProperty = someValue;,那么如果您使用引用类型,newList[1].SomeProperty == someValue 将是正确的。问题是,你想要oldList[3] = otherThing; 这样的东西,然后让newList[1] == otherThing 为真。
  • @thumbmunkeys ArraySegment 看起来像我想做的那样。如果我在 msdn 上得到正确的示例,它会在那里通过引用使用。我认为那将是一个可能的答案

标签: c# list


【解决方案1】:

你可以很容易地编写自己的切片类:

public class ReadOnlyListSlice<T> : IReadOnlyList<T>
{
    private readonly IReadOnlyList<T> _list;
    private readonly int _start;
    private readonly int _exclusiveEnd;

    public ReadOnlyListSlice(IReadOnlyList<T> list, int start, int exclusiveEnd)
    {
        _list = list;
        _start = start;
        _exclusiveEnd = exclusiveEnd;
    }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = _start; i <= _exclusiveEnd; ++i)
            yield return _list[i];
    }

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

    public int Count
    {
        get { return _exclusiveEnd - _start; }
    }

    public T this[int index]
    {
        get { return _list[index+_start]; }
    }
}

用法:

List<int> ints = Enumerable.Range(1, 10).ToList();
var test = new ReadOnlyListSlice<int>(ints, 4, 7);

foreach (var i in test)
    Console.WriteLine(i); // 5, 6, 7, 8

Console.WriteLine();

for (int i = 1; i < 3; ++i)
    Console.WriteLine(test[i]); // 6, 7

你也可以写一个可写的版本,但是如果你让它实现IList&lt;T&gt;,你最终将不得不实现很多你可能不需要使用的方法。

但是,如果您不介意它只实现 IReadOnlyList&lt;T&gt;(并且暗示 IEnumerable&lt;T&gt;),那并不是那么难:

public class ListSlice<T> : IReadOnlyList<T>
{
    private readonly List<T> _list;
    private readonly int _start;
    private readonly int _exclusiveEnd;

    public ListSlice(List<T> list, int start, int exclusiveEnd)
    {
        _list = list;
        _start = start;
        _exclusiveEnd = exclusiveEnd;
    }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = _start; i <= _exclusiveEnd; ++i)
            yield return _list[i];
    }

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

    public int Count
    {
        get { return _exclusiveEnd - _start; }
    }

    public T this[int index]
    {
        get { return _list[index+_start]; }
        set { _list[index+_start] = value; }
    }
}

并使用:

List<int> ints = Enumerable.Range(1, 10).ToList();
var test = new ListSlice<int>(ints, 4, 7);

foreach (var i in test)
    Console.WriteLine(i); // 5, 6, 7, 8

Console.WriteLine();

test[2] = -1;

for (int i = 1; i < 4; ++i)
    Console.WriteLine(test[i]); // 6, -1, 8

当然,不实现IList&lt;T&gt; 的缺点是您无法将ListSlice&lt;T&gt; 传递给期望IList&lt;T&gt; 的方法。

我将public class ListSlice&lt;T&gt; : IList&lt;T&gt; 的完整实现留给众所周知的“感兴趣的读者”。


如果你想实现List&lt;T&gt;.FIndIndex() 的等价物,它也很简单。只需将其添加到任一类:

public int FindIndex(int startIndex, int count, Predicate<T> match)
{
    for (int i = startIndex; i < startIndex + count; ++i)
        if (match(this[i]))
            return i;

    return -1;
}

这是一个完整的可编译控制台应用程序:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Demo
{
    public class ReadOnlyListSlice<T> : IReadOnlyList<T>
    {
        private readonly IReadOnlyList<T> _list;
        private readonly int _start;
        private readonly int _exclusiveEnd;

        public ReadOnlyListSlice(IReadOnlyList<T> list, int start, int exclusiveEnd)
        {
            _list = list;
            _start = start;
            _exclusiveEnd = exclusiveEnd;
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = _start; i <= _exclusiveEnd; ++i)
                yield return _list[i];
        }

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

        public int Count
        {
            get { return _exclusiveEnd - _start; }
        }

        public T this[int index]
        {
            get { return _list[index + _start]; }
        }
    }

    public class ListSlice<T> : IReadOnlyList<T>
    {
        private readonly IList<T> _list;
        private readonly int _start;
        private readonly int _exclusiveEnd;

        public ListSlice(IList<T> list, int start, int exclusiveEnd)
        {
            _list = list;
            _start = start;
            _exclusiveEnd = exclusiveEnd;
        }

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = _start; i <= _exclusiveEnd; ++i)
                yield return _list[i];
        }

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

        public int Count
        {
            get { return _exclusiveEnd - _start; }
        }

        public T this[int index]
        {
            get { return _list[index+_start]; }
            set { _list[index+_start] = value; }
        }
    }

    internal class Program
    {
        private static void Main()
        {
            var ints = Enumerable.Range(1, 10).ToList();

            Console.WriteLine("Readonly Demo\n");
            demoReadOnlySlice(ints);

            Console.WriteLine("\nWriteable Demo\n");
            demoWriteableSlice(ints);
        }

        private static void demoReadOnlySlice(List<int> ints)
        {
            var test = new ReadOnlyListSlice<int>(ints, 4, 7);

            foreach (var i in test)
                Console.WriteLine(i); // 5, 6, 7, 8

            Console.WriteLine();

            for (int i = 1; i < 4; ++i)
                Console.WriteLine(test[i]); // 6, 7, 8
        }

        private static void demoWriteableSlice(List<int> ints)
        {
            var test = new ListSlice<int>(ints, 4, 7);

            foreach (var i in test)
                Console.WriteLine(i); // 5, 6, 7, 8

            Console.WriteLine();

            test[2] = -1;

            for (int i = 1; i < 4; ++i)
                Console.WriteLine(test[i]); // 6, -1, 8
        }
    }
}

【讨论】:

  • 如果我的代码示例正确,它会对 start 和 ExclusiveEnd 中的元素进行浅表复制,并且在所有方面都表现得像原始列表? (也就是一个字符串列表,可以有一个 .Where 使用在它上面,...还有一个额外的切片从中取出,所以是一个堆叠的切片)
  • 必须是只读的吗?我不明白为什么不能添加等,可能会增加上限。 (当然需要实现更多的 IList 方法)。否则非常好。
  • @Thomas 它只复制原始列表的枚举数。
  • 啊,好吧,我当时理解错误的收益回报。这种表现如何? (到目前为止从未做过类似的事情)
  • @Thomas - 第二类的区别在于索引器有一个设置器。
【解决方案2】:

可以使用反射和ArraySegment 类:

var originalList = Enumerable.Range(0, 6).ToList();

var innerArray = (int[])originalList.GetType().GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(originalList);
var partialList = (IList<int>)new ArraySegment<int>(innerArray, 2, 3);

partialList[0] = -99;
partialList[1] = 100;
partialList[2] = 123;

Console.WriteLine(String.Join(", ", originalList));

输出:

0, 1, -99, 100, 123, 5

请注意,这取决于实现细节(List&lt;&gt; 类中的私有 _items 字段),因此它不是未来使用的证明。此外,如果您将几个项目添加到原始列表中,这将失败(_items 成员将被替换为新数组)。感谢@IvanStoev 提及。

如果您的原始集合是普通数组,这将不是问题。

【讨论】:

    【解决方案3】:

    您可以简单地将Where 方法与接受项目索引作为其第二个参数的 lambda 结合使用:

        var arr = Enumerable.Range(0, 60);
    
        var subSequence = arr.Where((e, i) => i >= 20 && i <= 27);
    
        foreach (var item in subSequence) Console.Write(item + "  ");
    

    输出:20、21、22、23、24、25、26、27

    【讨论】:

    • 在您的示例中,如果我还想将其用作列表,我是否必须使用 (List) arr.Where....?还是我必须调用 .ToList()?
    • @Thomas Where 返回一个 IEnumerable,ToList 返回一个 List
    • @Thomas kai 是正确的,你得到IEnumerable&lt;T&gt;,然后你可以打电话给.ToList().ToArray()(例如arr.Where((e, i) =&gt; i &gt;= 20 &amp;&amp; i &lt;= 27).ToArray(); 取决于哪个更适合你的需要
    • ToList 和 ToArray 是毫无疑问的,因为它们创建了每个可枚举对象的副本。但作为它的 Ienumerable 我是否正确,即使我想使用 .Where 也不需要它们?
    • Skip()Take() 也返回 IEnumerable - 无需使用 Where() 代替
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-19
    • 2021-11-08
    • 2023-02-08
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    • 2012-03-27
    相关资源
    最近更新 更多