通过扩展方法将项添加到IEnumerable不起作用?

时间:2012-02-13 18:52:37

标签: c# ienumerable

在我使用的大多数方法中,返回某种集合,我返回IEnumerable而不是特定类型(例如List)。在许多情况下,我有另一个我想要与结果IEnumerable结合的集合,这就像使用List并使用AddRange方法向其添加另一个List一样。我有以下示例,其中我创建了一个扩展方法,该方法应该将一组项添加并添加到基本集合中,而调试它似乎有效,但在原始集合中,项永远不会添加。我不明白这一点,为什么他们没有添加,是否有关于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 个答案:

答案 0 :(得分:4)

您正在修改参数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"

如果您对引用类型的假设为真,则输出将为“False”,然后为“True”

答案 1 :(得分:3)

按照以下方式调用您的扩展程序:

collectionOne.Orders.AddRange(collectionTwo.Orders);

基本上与:

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

现在发生了什么,是您将参考副本传递给collectionOne.OrdersAddRange方法。在AddRange实现中,您尝试为副本分配新值。内部“迷失”。您未向<{1}}分配新值,您将其分配给其本地副本 - 该范围仅在方法正文中。由于所有修改都发生在内部 collectionOne.Orders,外界注意到没有任何变化。

您需要返回 new 可枚举,或直接在列表上工作。在AddRange上使用变异方法相当违反直觉,我会远离这样做。

答案 2 :(得分:3)

您想要什么,称为Concat。基本上,当你在Main中执行此操作时:

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

这里,combined将引用一个IEnumerable,它将在枚举时遍历两个源集合。

答案 3 :(得分:1)

IEnumerable不支持添加。您在代码中实际上做的是从您的枚举中创建新集合,并将项目添加到该新集合中。您的旧系列仍有相同的商品。

例如,您创建了一个这样的数字集合

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

当你做Collection1.ToList()。添加(...)你将获得具有相同成员的新集合,并添加如下所示的新成员:

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,以便您也可以在方法中对其进行修改。

答案 4 :(得分:0)

基本上问题是你不能部分地为enumerationToAddTo赋值,因为它不是引用参数。同样,当phoog提及ToList()时会创建一个新列表,并且不会将现有的IEnumerable转换为列表。

这不是一个很好的扩展使用。我建议您在容器集合中添加一个方法,允许您向IEnumerable实例添加新项目。这样可以更好地封装特定于该类的逻辑。