Добавление элементов в IEnumerable через метод расширения не работает? - PullRequest
2 голосов
/ 13 февраля 2012

В большинстве используемых мной методов, которые возвращают какую-то коллекцию, я возвращаю IEnumerable, а не конкретный тип (например, List).Во многих случаях у меня есть другая коллекция, которую я хочу объединить с результатом IEnumerable, это было бы точно так же, как взять список List и добавить в него другой список с помощью метода AddRange.У меня есть следующий пример, в нем я создал метод расширения, который должен взять коллекцию элементов для добавления и добавить их в базовую коллекцию, при этом отладка этого работает, но в исходной коллекции элементы никогда не добавляются.Я не понимаю этого, почему они не добавлены, есть ли что-то в реализации IEnumerable, что мне не хватает?Я понимаю, что IEnumerable является интерфейсом только для чтения, но я не добавляю этот список в приведенный ниже пример, я заменяю его, но исходный IEnumerable не изменяется.

class Program
{
    static void Main(string[] args)
    {
        var collectionOne = new CollectionContainerOne();
        var collectionTwo = new CollectionContainerTwo();

        // Starts at 1- 50 //
        collectionOne.Orders.AddRange(collectionTwo.Orders);
        // Should now be 100 items but remains original 50 //
    }
}

public class CollectionContainerOne
{
    public IEnumerable<Order> Orders { get; set; }

    public CollectionContainerOne()
    {
        var testIds = Enumerable.Range(1, 50);
        var orders = new List<Order>();
        foreach (int i in testIds)
        {
            orders.Add(new Order() { Id = i, Name = "Order #" + i.ToString() });
        }
        this.Orders = orders;
    }
}

public class CollectionContainerTwo
{
    public IEnumerable<Order> Orders { get; set; }

    public CollectionContainerTwo()
    {
        var testIds = Enumerable.Range(51, 50);
        var orders = new List<Order>();
        foreach (int i in testIds)
        {
            orders.Add(new Order() { Id = i, Name = "Order #" + i.ToString() });
        }
        this.Orders = orders;
    }
}

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return this.Name;
    }
}

public static class IEnumerable
{
    public static void AddRange<T>(this IEnumerable<T> enumerationToAddTo, IEnumerable<T> itemsToAdd)
    {
        var addingToList = enumerationToAddTo.ToList();
        addingToList.AddRange(itemsToAdd);

        // Neither of the following works // 
        enumerationToAddTo.Concat(addingToList);
        // OR
        enumerationToAddTo = addingToList;
        // OR
        enumerationToAddTo = new List<T>(addingToList);
    }
}

Ответы [ 5 ]

4 голосов
/ 13 февраля 2012

Вы изменяете параметр enumerationToAddTo, который является ссылкой.Однако ссылка сама по себе не передается по ссылке, поэтому при изменении ссылки это изменение не наблюдается в вызывающей стороне.Кроме того, невозможно использовать ref параметры в методах расширения.

Вам лучше использовать Enumerable.Concat<T>.Кроме того, вы можете использовать ICollection, который имеет метод Add (T).К сожалению, List<T>.AddRange не определен ни в одном интерфейсе.

Вот пример, иллюстрирующий передачу ссылочных типов по ссылке.Как указывает Никола, это не очень полезный код.Не пытайтесь сделать это дома!

void Caller()
{
    // think of ss as a piece of paper that tells you where to find the list.
    List<string> ss = new List<string> { "a", "b" };

    //passing by value: we take another piece of paper and copy the information on ss to that piece of paper; we pass that to the method
    DoNotReassign(ss);

    //as this point, ss refers to the same list, that now contains { "a", "b", "c" }

    //passing by reference: we pass the actual original piece of paper to the method.
    Reassign(ref ss);

    // now, ss refers to a different list, whose contents are { "x", "y", "z" }
}
void DoNotReassign(List<string> strings)
{
    strings.Add("c");
    strings = new List<string> { "x", "y", "z" }; // the caller will not see the change of reference

    //in the piece of paper analogy, we have erased the piece of paper and written the location
    //of the new list on it.  Because this piece of paper is a copy of SS, the caller doesn't see the change.
}
void Reassign(ref List<string> strings)
{
    strings.Add("d");
    //at this point, strings contains { "a", "b", "c", "d" }, but we're about to throw that away:

    strings = new List<string> { "x", "y", "z" };

    //because strings is a reference to the caller's variable ss, the caller sees the reassignment to a new collection
    //in the piece of paper analogy, when we erase the paper and put the new object's
    //location on it, the caller sees that, because we are operating on the same
    //piece of paper ("ss") as the caller 
}

РЕДАКТИРОВАТЬ

Рассмотрите этот фрагмент программы:

string originalValue = "Hello, World!";
string workingCopy = originalValue;
workingCopy = workingCopy.Substring(0, workingCopy.Length - 1);
workingCopy = workingCopy + "?";
Console.WriteLine(originalValue.Equals("Hello, World!"); // writes "True"
Console.WriteLine(originalValue.Equals(workingCopy); // writes "False"

Если ваше предположение о ссылочных типахбыли бы верны, вывод был бы "Ложь" тогда "Истина"

3 голосов
/ 13 февраля 2012

То, что вы хотите, существует и называется Concat.По сути, когда вы делаете это в вашем Main:

var combined = collectionOne.Orders.Concat(collectionTwo.Orders);

Здесь, combined будет ссылаться на IEnumerable, который будет проходить через обе исходные коллекции при перечислении.

3 голосов
/ 13 февраля 2012

Вызов метода вашего расширения следующим образом:

collectionOne.Orders.AddRange(collectionTwo.Orders);

По сути то же самое, что и

IEnumerable.AddRange(collectionOne.Orders, collectionTwo.Orders);

Что теперь происходит, вы передаете копию ссылки методу collectionOne.Orders методу AddRange. В вашей реализации AddRange вы пытаетесь присвоить копии новое значение. Это теряется внутри. Вы не присваиваете новое значение для collectionOne.Orders, вы присваиваете его локальной копии - эта область видимости только внутри самого тела метода. В результате всех изменений, происходящих внутри AddRange, внешний мир не замечает никаких изменений.

Вам нужно либо вернуть new enumerable, либо работать со списками напрямую. Использование методов мутации в IEnumerable<T> довольно нелогично, я бы держался подальше от этого.

1 голос
/ 13 февраля 2012

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

Например, вы создаете коллекцию чисел, как это

Collection1 = [ 1, 2, 3, 4, 5 ]

когда вы делаете Collection1.ToList (). Add (...) вы получите новую коллекцию с теми же членами, и добавите новых членов, например, так:

Collection1 = [ 1, 2, 3, 4, 5, 6, 7, ... ]

однако ваша старая коллекция будет по-прежнему содержать старых членов, поскольку ToList создает новую коллекцию.

Решение № 1:

Вместо использования IEnumerable используйте IList , который поддерживает модификацию.

Решение № 2 (плохое):

Приведите ваш IEnumerable обратно к его производному типу и добавьте в него членов. Это довольно плохо, хотя на самом деле лучше просто вернуть List в первую очередь

IEnumerable<Order> collectionOne = ...;
List<Order> collectionOneList = (List<Order>)collectionOne;
collectionOneList.Add(new Order());

Общее руководство (лучше всего):

Если вы возвращаете коллекции, которые являются стандартными в .NET, нет причин возвращать их интерфейсы. В этом случае лучше всего использовать оригинальный тип. Если вы, однако, возвращаете коллекцию, которую вы реализовали сами, вы должны вернуть интерфейс Это совершенно другой случай, когда вы думаете о входных параметрах. Если ваш метод запрашивает перечисление элементов, вам следует запросить IEnumerable. Таким образом, вы можете делать то, что вам нужно, и вы меньше всего ограничиваете человека, который его называет. Они могут отправлять любые перечисляемые. Если вам нужно добавить эту коллекцию, вам может потребоваться IList, чтобы вы также могли изменить ее в своем методе.

0 голосов
/ 13 февраля 2012

В основном проблема в том, что вы не можете присвоить значение enumerationToAddTo частично, потому что это не ссылочный параметр. Также, как упоминает Фог, ToList() создает новый список и не приводит существующий IEnumerable к списку.

Это не очень хорошее использование расширения. Я бы порекомендовал добавить в свою коллекцию контейнеров метод, позволяющий добавлять новые элементы в экземпляр IEnumerable. Это будет лучше инкапсулировать логику, характерную для этого класса.

...