Search code examples
c#.netienumerableparallel.foreach

Parallel.ForEach apparently cloning reference type?


I have a simple method:

private IEnumerable<OrderResult> SubmitOrders(IEnumerable<OrderItem> items)
{
    ConcurrentBag<OrderResult> results = new ConcurrentBag<OrderResult>();

    Parallel.ForEach(items, item =>
        {
            OrderResult result = SubmitOrder(item);
            results.Add(result);
        });

    return results;
}

To add further context, the SubmitOrder(item) method referenced above changes the Sent (DateTime?) property on the item object to DateTime.Now, if the order is submitted successfully.

Inside the Parallel.ForEach loop, after SubmitOrder(item), I can see that the Sent property has been updated correctly.

HOWEVER, if I examine the object in the passed in items parameter, then none of the Sent properties have been updated. It is as though the items being passed into the Parallel.ForEach loop aren't those in the original items collection.

Why is this? If the objects in the passed in collection were modified in the loop, I expected the changes to be reflected in the items in the original collection, but they don't appear to be.


Solution

  • The parameter type of items is IEnumerable<OrderItem>. If items hasn't been enumerated yet, and enumerating them creates new objects, then that could be the cause, as the objects you're updating in SubmitOrder() are not the same as the objects the next time items is enumerated. Here is a full LINQPad C# program that demonstrates what I mean.

    void Main()
    {
        IEnumerable<OrderItem> itemsAsIEnumerable =
            Enumerable
                .Range(1, 5)
                .Select(i => new OrderItem() { ItemNumber = i });
        SubmitOrders(itemsAsIEnumerable);
        itemsAsIEnumerable.Dump();
            /* Displays:
            ItemNumber Sent
            1 null 
            2 null 
            3 null 
            4 null 
            5 null 
            */
    
        IEnumerable<OrderItem> itemsAsList =
            Enumerable
                .Range(1, 5)
                .Select(i => new OrderItem() { ItemNumber = i })
                .ToList();
        SubmitOrders(itemsAsList);
        itemsAsList.Dump();
            /* Displays:
            ItemNumber Sent
            1 2/5/2014 10:01:58 AM
            2 2/5/2014 10:01:58 AM
            3 2/5/2014 10:01:58 AM
            4 2/5/2014 10:01:58 AM
            5 2/5/2014 10:01:58 AM
            */      
    }
    
    private IEnumerable<OrderResult> SubmitOrders(IEnumerable<OrderItem> items)
    {
        ConcurrentBag<OrderResult> results = new ConcurrentBag<OrderResult>();
    
        Parallel.ForEach(items, item =>
            {
                OrderResult result = SubmitOrder(item);
                results.Add(result);
            });
    
        return results;
    }
    
    private OrderResult SubmitOrder(OrderItem item)
    {
        item.Sent = DateTime.Now;
        return new OrderResult();
    }
    
    public class OrderItem
    {
        public int ItemNumber { get; set; }
    
        public DateTime? Sent { get; set; }
    }
    
    public class OrderResult
    {
    }