15

My situation is that I want to assign a list only part of another list. And that if possible by reference.

What I have done so far is this here:

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

Example: A list with 6 elements and start being 2 and and end being 4.

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

Now as far as I understand .ToList() it creates a copy of the original results. Thus this would be by value and not by reference. So my question is: Is there any "by reference" way to achive the result I want?

Thomas
  • 2,886
  • 3
  • 34
  • 78
  • 3
    _"if possible by reference."_ Do you want it so that if you update a shared element in one list it also updates in the other list? – James Thorpe Sep 24 '15 at 08:14
  • http://stackoverflow.com/questions/1396048/c-sharp-elegant-way-of-partitioning-a-list – Dmitry Bychenko Sep 24 '15 at 08:15
  • 2
    Depending on what you want, you could create a class that holds the list reference and the two integers. Please explain your use case better and why you think the "by reference" will help. – CodeCaster Sep 24 '15 at 08:15
  • 1
    Can you write some code demonstrating the intended use of the resulting list? – Gusdor Sep 24 '15 at 08:17
  • No way if you want to return `List` because `List` class is not virtual. However it is possible to return `IList` implementation that does that as @CodeCaster mentioned. – Ivan Stoev Sep 24 '15 at 08:20
  • 1
    Not sure, what you want exactly. In your example, if you say `oldList[3].SomeProperty = someValue;`, then `newList[1].SomeProperty == someValue` would be true if you work with reference types. The question is, do you want something like `oldList[3] = otherThing;` and then have `newList[1] == otherThing` be true. – Corak Sep 24 '15 at 08:24
  • The idea is mostly that I only need a "view" on part of the list. In my current case it is a readonly view that I need as I don't need to change things (but performance must be top so copying around the elements is bad as I need to do it a few million times in as short a time as possible). In other cases I need to change the lements within the list. Thus I decided to put the question as a general question in regards there without stating for read only or for write. – Thomas Sep 24 '15 at 08:25
  • This may be possible with unsafe code and pointers – M.kazem Akhgary Sep 24 '15 at 08:25
  • @CodeCaster - yes, I know. My question was, if that is the behaviour OP wants. – Corak Sep 24 '15 at 08:26
  • @Corak its reference type behaviour that is intended (mostly for string lists but could also be taken for other types of lists like int lists). – Thomas Sep 24 '15 at 08:30
  • have a look at [ArraySegment](http://stackoverflow.com/questions/4600024/what-is-the-use-of-arraysegmentt-class) – thumbmunkeys Sep 24 '15 at 08:32
  • @DmitryBychenko From what I saw in that one question that is a true partitioning with creating copies of it (in its answers at least) and thus not what I'm trying to see...aka that one is by value while what I'm asking is by reference – Thomas Sep 24 '15 at 08:34
  • 1
    @thumbmunkeys ArraySegment looks like it does what I want to. If I get the examples on msdn correct it uses by reference there. I think that would be a possible answer there – Thomas Sep 24 '15 at 08:37
  • @Thomas `ArraySegment` specifically requires an `T[]` array rather than a `List`. – Matthew Watson Sep 24 '15 at 09:47
  • @Thomas are you *sure* you need to avoid copying? Do you know this to be a hot spot or bottleneck in your code? Or are you just assuming that it will be - premature optimization? – stannius Sep 24 '15 at 15:12
  • It's worth digging a little deeper into your problem: *why* do you need a "view" on part of the list? If you need to provide a filtered collection to a WPF UI, for example, you'd typically use a [CollectionView](https://msdn.microsoft.com/en-us/library/system.windows.data.collectionview(v=vs.110).aspx), which might be appropriate in your case as well. – Dan J Sep 24 '15 at 16:20
  • nope its just a command line c# tool. A view is only there so that less begin here and go to there and only to there needs to be copy & pasted (and thus reducing the possibilities of doing errors.... and making changes easier as its just 1 time that it needs to be changed) – Thomas Sep 24 '15 at 17:14

3 Answers3

15

You could write your own slice class easily enough:

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

Usage:

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

You could also write a writable version, but then if you make it implement IList<T> you'll end up having to implement a LOT of methods that you'll probably not need to use.

However, if you don't mind it only implementing IReadOnlyList<T> (and by implication IEnumerable<T>) it's not so hard:

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

And to use:

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

Of course, the drawback of not implementing IList<T> is that you won't be able to pass a ListSlice<T> to a method expecting an IList<T>.

I leave the full implementation of public class ListSlice<T> : IList<T> to the proverbial "Interested Reader".


If you wanted to implement the equivalent of List<T>.FIndIndex() it's also quite simple. Just add this to either class:

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

Here's a complete compilable console app:

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
        }
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • If I get your codeexample right it does a shallow copy of the elements within start and exclusiveEnd and behaves like the original list in all things? (aka a list of strings that can have a .Where used on it, ... and also an additional slice being taken from it, so a stacked slice) – Thomas Sep 24 '15 at 08:48
  • Must it be readonly? I don't see why one shouldn't be able to add etc., probably incrementing the upper bound. (Would of course need to implement more methods of IList). Otherwise very nice piece. – Peter - Reinstate Monica Sep 24 '15 at 08:50
  • 1
    @Thomas it doesn't copy any more than the enumerator of the original list. – Peter - Reinstate Monica Sep 24 '15 at 08:52
  • Ah ok the yield return I interepreted wrongly then. How is that performance wise? (never done something like that so far) – Thomas Sep 24 '15 at 08:54
  • @Thomas The performance penalty is probably negligible in 99.9% of all use cases. – sloth Sep 24 '15 at 08:56
  • Performance: There is a single additional indirection (the enumerator needs to call the original list's indexer instead of accessing the internal array immediately). I imagine that this indirection can be eliminated at run time in many cases (when the run time compiler can prove that the underlying list does not change, which should be trivial since there is no setter). – Peter - Reinstate Monica Sep 24 '15 at 09:02
  • @PeterSchneider I made it readonly because the OP said he only needed readonly, but on closer inspection he said that sometimes he wants it to be writable, so I'll update with a read/write version. – Matthew Watson Sep 24 '15 at 09:06
  • @PeterSchneider On second thoughts, making the slice implement `IList` is not a good idea, since you end up having to implement `Clear()`, `Add()`, `CopyTo()`, `RemoveAt()` and a whole load of other things too. Needs more thought... – Matthew Watson Sep 24 '15 at 09:12
  • Btw "readonly" the compiler says "no" there when I try to assign the values inside: ReadOnlyListSlice. And "IEnumerable.GetEnumerator" the compiler says he wants the 1 type argument – Thomas Sep 24 '15 at 09:16
  • "Whole Load": True, but mostly trivial pass-throughs with a bit of local bounds attention. It seems like a good idea conceptually to view a slice as a full-fledged view on a list segment. – Peter - Reinstate Monica Sep 24 '15 at 09:18
  • ok the edit made me delete the answer :) . i did not understand the reason of readonly so i decided to change it a bit – M.kazem Akhgary Sep 24 '15 at 09:26
  • @Thomas Do you mean it won't compile? It compiles fine for me - what version of .Net are you using? – Matthew Watson Sep 24 '15 at 09:31
  • @MatthewWatson I'm using visual studio 2012 (CSharp is v4.0.3x). Yes it does not compile for me. – Thomas Sep 24 '15 at 09:36
  • @Thomas You must be missing some `using` statements. I'll post a complete compilable sample in a minute. – Matthew Watson Sep 24 '15 at 09:37
  • The condition `i <= _exclusiveEnd` in the `GetEnumerator` seems wrong. Should probably be `i < _exclusiveEnd`, otherwise it would be inclusive. – Corak Sep 24 '15 at 09:45
  • @MatthewWatson tnx must have been a copy&paste error I made somewhere. When I took it again it worked. – Thomas Sep 24 '15 at 09:47
  • 1
    @Thomas - the difference in the second class is that the indexer has a setter. – Corak Sep 24 '15 at 09:48
  • yepp just saw it (corrected my comment there as I had overread it before). Btw one last question there. Is it possible to use the splice as a normal list? From what I just saw during a test it is interpreted as an IList and thus does not have the FindIndex method that List itself has. – Thomas Sep 24 '15 at 09:50
  • @Thomas No it doesn't have `FindIndex`. It only has a small subset of `List`; to implement all the other members of `List` would be a lot more work. – Matthew Watson Sep 24 '15 at 09:56
  • @Thomas But `FindIndex` is simple to implement (I've added it to the answer) – Matthew Watson Sep 24 '15 at 10:02
  • Tnx. Almost thought it is implemented in this way in C# (almost thought it) – Thomas Sep 24 '15 at 10:07
8

It's possible using reflection and the ArraySegment class:

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

Output:

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

Note that this depends on implemention details (the private _items field in the List<> class), so it's not future proof to use. Also, this will fail if you add a couple items to the original list (the _items member will get replaced with a new array). Thanks @IvanStoev for mention it.

This wouldn't be an issue if your original collection would be a plain array.

sloth
  • 99,095
  • 21
  • 171
  • 219
2

You can simply use Where method with lambda that accepts item index as its second parameter :

    var arr = Enumerable.Range(0, 60);

    var subSequence = arr.Where((e, i) => i >= 20 && i <= 27);

    foreach (var item in subSequence) Console.Write(item + "  ");

Output: 20, 21, 22, 23, 24, 25, 26, 27

Fabjan
  • 13,506
  • 4
  • 25
  • 52
  • In your example if I also want to use it as a list would I have to use (List) arr.Where.... ? or would I have to call .ToList()? – Thomas Sep 24 '15 at 09:14
  • @Thomas Where returns an IEnumerable, ToList returns a List – sara Sep 24 '15 at 09:31
  • @Thomas kai is correct, you get `IEnumerable`, and then you can call `.ToList()` or `.ToArray()` ( for example `arr.Where((e, i) => i >= 20 && i <= 27).ToArray();` depending of what will better suit your needs – Fabjan Sep 24 '15 at 09:34
  • ToList and ToArray are out of question though as they create copies each of the ienumerable object. but as its Ienumerable am I correct that I would not need them even if I want to use .Where? – Thomas Sep 24 '15 at 09:35
  • Yeah, you can use all `LINQ` extension methods as they extends `IEnumerable` on your sub-sequence same as you'd do it on List, or Array (if it's what the question is about)... – Fabjan Sep 24 '15 at 09:38
  • Mhm tnx (although a few things like FindIndex are not useable then (IList does not have that function as it is directly a function of List)) – Thomas Sep 24 '15 at 09:43
  • 1
    `Skip()` and `Take()` returns `IEnumerable` as well - there is no need to use `Where()` instead – fubo Sep 24 '15 at 11:43
  • @fubo And what it'll return for `arr.Skip(5).Take(5)` when arr length is 4 ? – Fabjan Sep 24 '15 at 11:51
  • @Fabjan - i think the question is not about maintining the valid `start` and `take` values, it's about getting a reference to the existing list – fubo Sep 24 '15 at 11:55