2016-12-29 2 views
1

У меня есть библиотека, которая принимает только тип неизменяемой коллекции. Я хотел бы иметь функцию, которая принимает одну из этих коллекций и выполняет некоторые изменения в этой коллекции, возвращая новую коллекцию, содержащую внесенные изменения.Заменить, Вставить, Удалить операции над IEnumerable

Я бы хотел использовать синтаксис LINQ вместо копирования этой коллекции в список и обратно.

Добавить операции легко для меня: concat перечислимый с другим. Но что делать с заменой (при заданном индексе возвращаем значение, указанное вместо значения IEnumerable), Insert (при заданном индексе, возвращайте заданное значение, а затем продолжайте итерацию по IEnumerable) или Delete (при заданном индексе пропустите IEnumerable стоимость)?

Существуют ли функции, подобные этой, в среде .NET или в другой библиотеке? Если нет, то как я могу реализовать эти функции?

ответ

6

Вы можете сделать свои собственные расширения для этих операций:

  • Добавить

    public static IEnumerable<T> Add<T>(this IEnumerable<T> enumerable, T value) 
    { 
        foreach (var item in enumerable) 
         yield return item; 
    
        yield return value; 
    } 
    

    или:

    public static IEnumerable<T> Add<T>(this IEnumerable<T> enumerable, T value) 
    { 
        return enumerable.Concat(new T[] { value }); 
    } 
    
  • Вставка

    public static IEnumerable<T> Insert<T>(this IEnumerable<T> enumerable, int index, T value) 
    { 
        int current = 0; 
        foreach (var item in enumerable) 
        { 
         if (current == index) 
          yield return value; 
    
         yield return item; 
         current++; 
        } 
    } 
    

    или

    public static IEnumerable<T> Insert<T>(this IEnumerable<T> enumerable, int index, T value) 
    { 
        return enumerable.SelectMany((x, i) => index == i ? new T[] { value, x } : new T[] { x }); 
    } 
    
  • Заменить

    public static IEnumerable<T> Replace<T>(this IEnumerable<T> enumerable, int index, T value) 
    { 
        int current = 0; 
        foreach (var item in enumerable) 
        { 
         yield return current == index ? value : item; 
         current++; 
        } 
    } 
    

    или

    public static IEnumerable<T> Replace<T>(this IEnumerable<T> enumerable, int index, T value) 
    { 
        return enumerable.Select((x, i) => index == i ? value : x); 
    } 
    
  • Удалить

    public static IEnumerable<T> Remove<T>(this IEnumerable<T> enumerable, int index) 
    { 
        int current = 0; 
        foreach (var item in enumerable) 
        { 
         if (current != index) 
          yield return item; 
    
         current++; 
        } 
    } 
    

    или

    public static IEnumerable<T> Remove<T>(this IEnumerable<T> enumerable, int index) 
    { 
        return enumerable.Where((x, i) => index != i); 
    } 
    

Тогда вы можете совершать звонки, как это:

IEnumerable<int> collection = new int[] { 1, 2, 3, 4, 5 }; 

var added = collection.Add(6);    // 1, 2, 3, 4, 5, 6 
var inserted = collection.Insert(0, 0);  // 0, 1, 2, 3, 4, 5 
var replaced = collection.Replace(1, 22); // 1, 22, 3, 4, 5 
var removed = collection.Remove(2);   // 1, 2, 4, 5 
0

Вопрос немного широк, поэтому я продемонстрирую возможность для метода Replace. В этой области нет методов, чтобы заменил что-то в IEnumerable, так как IEnumerable должен представлять неизменяемую последовательность.

Так наивный способ вернуть новый IEnumerable с замещаемыми элементами:

public static class Extensions 
{ 
    public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, T oldValue, T newValue) 
    { 
     return source.Select(element => element == oldValue ? newValue : element); 
    } 
} 

Это будет перебирать исходную последовательность и возвращает элементы источника для тех, кто EqualoldValue исключения. Обратите внимание, что это использует оператор == и как это работает, зависит от аргумента типа для T.

Также обратите внимание, что это использует отложенное исполнение. Исходная последовательность только перечисляется, когда вы начинаете перечислять полученный результат IEnumerable. Поэтому, если вы измените исходную последовательность после вызова на Replace, полученная последовательность также даст это изменение.

Реализации для Insert и Delete также являются прямыми, хотя вам нужно будет подсчитать индекс в исходной последовательности.

+0

Я искал операторов LINQ (которые всегда возвращают новый IEnumerable, конечно). Я не уверен, что отсутствие таких операторов может быть объяснено невосприимчивостью IEnumerable. В условиях существования Where, Select, Reverse и т. Д. Я не вижу причин, почему мои предложенные функции были бы против основной философии LINQ. Тем не менее, я вижу, что существует вероятность того, что новички ошибочно принимают IEnumerables для списков и путают, что исходный перечислимый никогда не изменяется. Спасибо за ответ, хотя. –

1

IEnumerable по определению является непреложным перечислимы совокупность элементов данного типа. Неизменяемый означает, что вы не можете изменять его напрямую, и вам всегда нужно создать новый экземпляр.

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

Например заменить может выглядеть следующим образом:

public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> collection, int index, T item) 
{ 
    var currentIndex = 0; 
    foreach (var originalItem in collection) 
    { 
     if (currentIndex != index) 
     { 
      //keep the original item in place 
      yield return originalItem; 
     } 
     else 
     { 
      //we reached the index where we want to replace 
      yield return item; 
     } 
     currentIndex++; 
    } 
} 
Смежные вопросы