通过扩展方法向 IEnumerable 添加项目不起作用?

发布于 2025-01-05 07:29:40 字数 2161 浏览 3 评论 0原文

在我使用的大多数返回某种集合的方法中,我返回 IEnumerable 而不是特定类型(例如列表)。在许多情况下,我有另一个集合想要与结果 IEnumerable 组合,这就像获取一个列表并使用 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);
    }
}

In most of the methods I use that return some kind of collection I return IEnumerable rather than the specific type (e.g. List). In many cases I have another collection that I want to combine with the result IEnumerable, this would be exactly like taking a List and adding another List to it using the AddRange method. I have the following example, in it I have created an extension method that should take a collection of items to add and adds them to a base collection, while debugging this appears to works but in the original collection the items are never added. I don't understand this, why aren't they added, is there something about the implementation of the IEnumerable that I am missing? I understand that IEnumerable is a read only interface, but Iam not adding to this list in the example below, I am replacing it, but the original IEnumerable does not change.

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);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

ゃ人海孤独症 2025-01-12 07:29:40

您正在修改参数 enumerationToAddTo,它是一个引用。但是,引用本身并不是通过引用传递的,因此当您修改引用时,调用者中无法观察到更改。此外,无法在扩展方法中使用 ref 参数。

您最好使用 Enumerable.Concat。或者,您可以使用 ICollection,它具有 Add(T) 方法。不幸的是,List.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”

You are modifying the parameter enumerationToAddTo, which is a reference. However, the reference is not itself passed by reference, so when you modify the reference, the change is not observable in the caller. Furthermore, it is not possible to use ref parameters in extension methods.

You are better off using Enumerable.Concat<T>. Alternatively, you can use ICollection, which has an Add(T) method. Unfortunately, List<T>.AddRange isn't defined in any interface.

Here is an example to illustrate the passing of reference types by reference. As Nikola points out, this is not really useful code. Don't try this at home!

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 
}

EDIT

Consider this program fragment:

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"

If your assumption about reference types were true, the output would be "False" then "True"

梦幻的味道 2025-01-12 07:29:40

你想要的东西存在并且被称为Concat。本质上,当您在 Main:

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

Here 中执行此操作时,combined 将引用一个 IEnumerable,该 IEnumerable 在枚举时将遍历两个源集合。

What you want exists and is called Concat. Essentially, when you do this in your Main:

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

Here, combined will refer to an IEnumerable that will traverse both source collections when enumerated.

淡看悲欢离合 2025-01-12 07:29:40

像这样调用您的扩展方法:

collectionOne.Orders.AddRange(collectionTwo.Orders);

本质上是相同的:

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

现在发生的事情是将 引用副本传递给 collectionOne.OrdersAddRange< /代码> 方法。在您的 AddRange 实现中,您尝试为副本分配新值。它在里面“迷失”了。您没有为collectionOne.Orders分配新值,而是将其分配给其本地副本 - 其范围仅在方法主体本身内。由于所有修改都发生在 AddRange内部,外界不会注意到任何变化。

您要么需要返回新的枚举,要么直接处理列表。在 IEnumerable上改变方法是相当违反直觉的,我会避免这样做。

Calling your extensions method like this:

collectionOne.Orders.AddRange(collectionTwo.Orders);

Is essentially the same as:

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

Now what happens there, is you pass copy of reference to the collectionOne.Orders to the AddRange method. In your AddRange implementation you try to assign new value to the copy. It gets "lost" inside. You are not assigning new value to collectionOne.Orders, you assign it to its local copy - which scope is only within the method body itself. As a result of all modifications happenining inside AddRange, outside world notices no changes.

You either need to return new enumerable, or work on lists directly. Having mutating methods on IEnumerable<T> is rather counterintuitive, I'd stay away from doing that.

傲性难收 2025-01-12 07:29:40

IEnumerable 不支持添加。您本质上在代码中所做的就是从可枚举中创建新集合,并将项目添加到该新集合中。您的旧收藏中仍然有相同的物品。

例如,当您执行 Collection1.ToList().Add(...) 时,您将创建一个这样的数字集合,

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

您将获得具有相同成员的新集合,并像这样添加新成员:

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,以便您也可以在您的方法中修改它。

IEnumerable does not support adding. What you in essence did in your code is create new collection from your enumerable, and add items to that new collection. Your old collection still has same items.

E.g., you create a collection of numbers like this

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

when you do Collection1.ToList().Add(...) you will get new collection with same members, and add new members like so:

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

your old collection will however still hold old members, as ToList creates new collection.

Solution #1:

Instead of using IEnumerable use IList which supports modification.

Solution #2 (bad):

Cast your IEnumerable back to it's derived type and add members to it. This is quite bad though, in fact it's better to just return List in the first place

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

General guideline (best):

If you are returning collections which are standard in .NET there is no reason to return their interfaces. In this case it's best to use original type. If you are however returning collection which you implemented yourself, then you should return an interface
It's a completely different case when you are thinking about input parameters. If your method is asking to enumerate over items, then you should ask for IEnumerable. This way you can do what you need over it, and you are placing least constraint on person who is calling it. They can send any enumerable. If you need to add to that collection, you may require IList so that you can also modify it in your method.

妄司 2025-01-12 07:29:40

基本上,问题在于您无法为 enumerationToAddTo 部分分配值,因为它不是引用参数。另外,正如 phoog 提到的 ToList() 创建一个新列表,并且不会将现有 IEnumerable 转换为列表。

这并不是扩展的一个很好的用途。我建议您向容器集合添加一个方法,该方法允许您向 IEnumerable 实例添加新项目。这将更好地封装该类特有的逻辑。

Basically the problem is that you can't assign a value to enumerationToAddTo partially because it isn't a reference parameter. Also as phoog mentions ToList() creates a new list and does not cast the existing IEnumerable to a list.

This isn't really a good use of a extension. I would recommend that you add a method to your container collection that allows you add add new items to the IEnumerable instance. This would better encapsulate the logic that's particular to that class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文