Amir Rezaei
Amir Rezaei

Reputation: 5086

Why IEnumerable<T> becomes empty after adding elements to a collection?

Update

The ToList() solved the problem. The root of the problem was that the caller to AddFruits(IEnumerable fruitsToAdd) send fruitsToAdd that was like.

fruitsToAdd = obj.Fruits.Except(apples.Fruits);

Each time IEnumerable fruitsToAdd was Rest it run above statement. Which at next iteration run Except and thereby returned no fruits.

The right way is fruitsToAdd = obj.Fruits.Except(apples.Fruits).ToList(); Since we want one evaluation.

Upvotes: 1

Views: 1985

Answers (3)

vgru
vgru

Reputation: 51214

The code in your question shouldn't exhibit such behavior, so I am presuming you tried to simplify it, but removed a lot of functionality from it.

What looks a bit suspicious is that your _fruits field is of type ICollection<T>. This interface is often used with custom collection implementations. Is it possible that, in the actual code, this field isn't instantiated with a List<T>, but rather with a custom implementation of that interface?

If you have a custom collection implementation, then it is perfectly possible for its Add method to do weird stuff (like removing an item from its previous "parent" collection before adding it to its new "parent"). Tree collections often do such things to simplify moving nodes around.

[Edit]

I am aware that this is not OPs actual problem, but I will nevertheless add an example to demonstrate that a custom collection implementation can in fact modify the input collection when its members are added to a different collection.

Let's say the Fruit class looks like this:

partial class Fruit
{
    private ICollection<Fruit> _children;
    private Fruit _parent;

    public String Name { get; set; }

    public Fruit()
    {
        _children = new FruitCollection(this);
    }

    public void AddFruits(IEnumerable<Fruit> fruits)
    {
        foreach (Fruit f in fruits)
            _children.Add(f);
    }

    public int NumberOfChildren
    {
        get { return _children.Count; }
    }

    public IEnumerable<Fruit> GetFruits()
    {
        return _children.ToList();
    }
}

And there is a custom collection defined as:

partial class Fruit
{
    public class FruitCollection : Collection<Fruit>
    {
        private readonly Fruit _parent;
        public FruitCollection(Fruit parent)
        {
            _parent = parent;
        }

        protected override void InsertItem(int index, Fruit item)
        {
            // item already has a parent?
            if (item._parent != null)
            {
                // remove it from previous parent
                item._parent._children.Remove(item);
            }

            // set the new parent
            item._parent = _parent;

            base.InsertItem(index, item);
        }

        // other methods should be overriden in a similar way
    }
}

Then the following program:

static void Main(string[] args)
{
    List<Fruit> abc = new List<Fruit>()
    {
        new Fruit() { Name = "a" },
        new Fruit() { Name = "b" },
        new Fruit() { Name = "c" }
    };

    Fruit apple = new Fruit() { Name = "apple" };
    apple.AddFruits(abc);

    Console.WriteLine("{0} has {1} children", apple.Name, apple.NumberOfChildren);


    // now try to add apples's children to
    // each of the following fruits
    List<Fruit> def = new List<Fruit>()
    {
        new Fruit() { Name = "d" },
        new Fruit() { Name = "e" },
        new Fruit() { Name = "f" }
    };

    foreach (Fruit f in def)
    {
        f.AddFruits(apple.GetFruits());
        Console.WriteLine("{0} has {1} children", f.Name, f.NumberOfChildren);
    }

    Console.Read();
}

Would print:

    apple has 3 children

    d has 3 children
    e has 0 children
    f has 0 children

Because apple.GetFruits() will return 0 after the first iteration.

By looking at the custom collection's source, it is hard to realize that _children.Add(f) in AddFruits in fact modifies the fruits previous parent collection.

Upvotes: 0

Henk Holterman
Henk Holterman

Reputation: 273219

Ok, try this:

public void AddFruits(IEnumerable<Fruit> fruitsToAdd)
{
    var fruitsToAddCopy = fruitsToAdd.ToList();  // add just this line

    foreach (var apple in apples)
    {           
        apple.AddFruits(fruitsToAddCopy);    // and change this        
    }
}

Without knowing the origin of your fruitsToAdd it's impossible to say more. Some IEnumerable<> can't be re-used. Others can.

Upvotes: 3

Maggie
Maggie

Reputation: 1547

I modified your code to get it to compile and wrote a test. Your list does not become empty after copying it's elements into the apples.

    using System;
    using System.Collections.Generic;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ClassLibrary3
{
    [TestClass]
    public class Class1
    {
        [TestMethod]
        public void test()
        {
            var fruits = new List<Fruit> {new Fruit(), new Fruit(), new Fruit()};

            var lists = AddFruits(fruits);
            Assert.IsTrue(fruits.Count == 3);

        }

        public List<Apple> AddFruits(IEnumerable<Fruit> fruitsToAdd)
        {
            var apples = new List<Apple>
                             {
                                 new Apple(),
                                 new Apple()
                             };

            foreach (var apple in apples)
            {
                apple.AddFruits(fruitsToAdd);
            }
            return apples;
        }
    }

    public class Fruit 
    {
    }

    public class Apple
    {
        private ICollection<Fruit> _fruits = new List<Fruit>();

        public void AddFruits(IEnumerable<Fruit> fruits)
        {
            if (fruits == null) throw new ArgumentNullException("fruits");
            foreach (var fruit in fruits)
            {
                _fruits.Add(fruit);
            }
        }
    }
}

Upvotes: 0

Related Questions