2015-09-24 3 views
15

Моя ситуация в том, что я хочу назначить список только частью другого списка. И это возможно по ссылке.Возможно ли вернуть часть списка по ссылке?

То, что я сделал до сих пор это здесь:

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() создает копию исходных результатов. Таким образом, это будет по стоимости, а не по ссылке. Поэтому мой вопрос: есть ли способ «по ссылке» достичь результата, который я хочу?

+3

_ «если возможно по ссылке». _ Вы хотите, чтобы, если вы обновляете общий элемент в одном списке, он также обновляется в другом списке? –

+0

http://stackoverflow.com/questions/1396048/c-sharp-elegant-way-of-partitioning-a-list –

+2

В зависимости от того, что вы хотите, вы можете создать класс, содержащий ссылку на список и два целых числа. Пожалуйста, объясните свой прецедент лучше и почему вы думаете, что «по ссылке» поможет. – CodeCaster

ответ

8

Это возможно с помощью отражения и 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 

Обратите внимание, что это зависит от деталей РЕАЛИЗАЦИИ (частного _items поля в List<> классе), так что это не будущее доказательство использования. Кроме того, это не удастся, если вы добавите пару элементов в исходный список (член _items будет заменен новым массивом). Спасибо @IvanStoev за это.

Это не будет проблемой, если ваша первоначальная коллекция будет простым массивом.

15

Вы можете написать свой собственный класс ломтика достаточно легко:

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<T> вас В конце концов вам придется внедрить множество методов, которые вам, вероятно, не понадобится использовать.

Однако, если вы не возражаете, это только осуществление IReadOnlyList<T> (и косвенно IEnumerable<T>) это не так уж трудно:

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<T> заключается в том, что вы не сможете передать ListSlice<T> методу, ожидающему IList<T>.

Я оставляю полную реализацию public class ListSlice<T> : IList<T> пресловутому «Заинтересованному читателю».


Если вы хотите реализовать эквивалент List<T>.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 
     } 
    } 
} 
+0

Если я получу свой код codeexample правильно, он делает мелкую копию элементов в начале и exclusiveEnd и ведет себя как исходный список во всех вещах?(aka список строк, которые могут иметь .Where используется на нем, ... а также дополнительный срез, взятый из него, так что уложенный фрагмент) – Thomas

+0

Должно быть, это только для чтения? Я не понимаю, почему нельзя добавлять и т. Д., Вероятно, увеличивая верхнюю границу. (Разумеется, необходимо будет внедрить больше методов IList). В противном случае очень приятная штука. –

+1

@Thomas он не копирует больше, чем перечислитель исходного списка. –

2

Вы можете просто использовать Where метод с лямбдой, который принимает индекс элемента в качестве второго параметра:

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

+0

В вашем примере, если я также хочу использовать его в качестве списка, я должен использовать (List ) arr.Where ....? или мне нужно позвонить. ToList()? – Thomas

+0

@Thomas Где возвращается IEnumerable , ToList возвращает список kai

+0

@Thomas kai верен, вы получаете 'IEnumerable ', а затем вы можете вызывать '.ToList()' или '.ToArray()' (например, ' arr.Where ((e, i) => i> = 20 && i <= 27) .ToArray(); 'зависит от того, что лучше подходит вашим потребностям – Fabjan

Смежные вопросы