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?
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
}
}
}
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With