Search code examples
c#xmllistlinqienumerable

c# IEnumerable<object> is bound to XDocument changes


I have an XML file as following:

<products>
  <product id="1" name="USB Flash Memory 2 GB" />
  <product id="2" name="USB Flash Memory 8 GB" />
  <product id="3" name="Wireless Multi Media Keyboard" />
</products>

I used XDocument to read the file and stored the query result in a variable of type IEnumerable<Product>.

If I added a new element to the XDocument the variable is updated automatically, so I thought that there is some bug so I tried that with List<Product> type variable and it worked (i.e. adding an element to the XDocument don't auto update with that input)

So my question is why the IEnumerable is bound to the XDocument changes while the List is not ?

Full Code:

namespace IEnumerableTest
{
    public class Product
    {
        public int ID { get; set; }
        public string ProductName { get; set; }
    }
}


namespace IEnumerableTest
{
    public partial class Form1 : Form
    {
        static string path = @"Products.xml";
        XDocument xDoc = XDocument.Load(path);
        IEnumerable<Product> productsEnum;
        List<Product> productsList;

        public Form1()
        {
            InitializeComponent();

            productsEnum =
                from p in xDoc.Descendants("product")
                select new Product
                {
                    ID = int.Parse(p.Attribute("id").Value),
                    ProductName = p.Attribute("name").Value
                };

            productsList =
                (from p in xDoc.Descendants("product")
                 select new Product
                 {
                     ID = int.Parse(p.Attribute("id").Value),
                     ProductName = p.Attribute("name").Value
                 }).ToList();
        }
        
        void BindDataGridView()
        {
            dataGridViewEnum.DataSource = productsEnum.ToList();
            dataGridViewList.DataSource = productsList;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            BindDataGridView();
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            XElement newProduct =
                new XElement("product",
                new XAttribute("id", 4),
                new XAttribute("name", "Wireless USB Optical Mouse"));

            xDoc.Root.Add(newProduct);

            BindDataGridView();
        }
    }
}

The result is like this:

enter image description here


Solution

  • An IEnumerable is like a promise of data, so it's only when you loop over it (or use something like a ToList on it), that is when the Linq query you have here is actually executed. Consider the following code:

    void Main()
    {
        IEnumerable<int> numbers = Enumerable.Range(1, 5).Select(x => DoubleNumber(x));
    
        Console.WriteLine($"There are {numbers.Count()} numbers that were doubled");
        Console.WriteLine($"There are {numbers.Count()} numbers that were doubled");
    }
    
    public int DoubleNumber(int value)
    {
        Console.WriteLine($"Doubling the number {value}");
        return value * 2;
    }
    

    The output from this is actually:

    Doubling the number 1
    Doubling the number 2
    Doubling the number 3
    Doubling the number 4
    Doubling the number 5
    There are 5 numbers that were doubled
    Doubling the number 1
    Doubling the number 2
    Doubling the number 3
    Doubling the number 4
    Doubling the number 5
    There are 5 numbers that were doubled
    

    Note how the entire collection of numbers was calculated twice. If you add .ToList() to the end of the first line though, that will create all the numbers in memory only once and give you this output:

    Doubling the number 1
    Doubling the number 2
    Doubling the number 3
    Doubling the number 4
    Doubling the number 5
    There are 5 numbers that were doubled
    There are 5 numbers that were doubled