1

I have a collection of objects entering a method as an IEnumerable of which I am grouping them by a reference property and then processing them group by group. The processing involves mutating the other properties of the objects. When the method completes and returns, the caller expects to have the collection of objects it passed in to be mutated. The whole process is asynchronous and the method looks like the following:

public async Task MutateMyObjects(IEnumerable<MyObjects> myObjects,
    CancellationToken cancellationToken)
{
    var myObjectsGroupedByGroupingEntity = myObjects
        .GroupBy(myObject => myObject.GroupingEntity);

    foreach (var myObjectGroup in myObjectsGroupedByGroupingEntity )
    {
        await ProcessGroup(myObjectGroup.Key, myObjectGroup, cancellationToken);
    }
}

Both MyObject and GroupingEntity are classes, as such my expectation is that MyObjects are passed as reference types and mutation is inherent through the process.

What actually happens is the caller of MutateMyObjects observes the same property values of MyObjects as it observed before the method call. These results are observed after the Task completes. While debugging the method above, observing the variable states before the method returns shows that the object collection under the variable myObjectGroup contain the mutated properties while the object collection under the variable myObjects does not.

I'm not sure what aspect of my understanding is missing causing me to get the wrong expectation, any insights would be greatly appreciated.

9
  • 4
    What are you passing to the method? If it's something like someList.Select(x => new MyObjects(x)) then each time you iterated it will create new objects. You can pass it to the MutateMyObjects and it will mutate the objects it creates, but if you iterated the IEnumerable again it will create brand new unmutated objects. Commented Dec 22, 2021 at 1:08
  • 1
    Is the IEnumerable<MyObjects> perhaps hitting your database again for new data? Commented Dec 22, 2021 at 1:19
  • 1
    @Llama that's after work's completed - that's not really what I meant - you can't use myObj into an async function and observe it's changes in the meantime it's working Commented Dec 22, 2021 at 1:26
  • 1
    @riffnl I see, I misunderstood your comment then. Commented Dec 22, 2021 at 1:28
  • 2
    @juharr Thanks to your question I looked at the source of the data which is a LINQ to SQL query piped into a mapper using Select. So I executed the query by adding a ToList() call after the mapping Select and now the object mutation persists because of what you described. Commented Dec 22, 2021 at 2:58

1 Answer 1

0

The issue is that I needed to execute the LINQ query first to materialise the objects before sending them off for mutation. What isn't shown in the question is how the IEnumerable<MyObject> myObjects is represented. In my case, myObjects was a deferred execution of a database fetch piped into an object mapping. This meant that when I mutated the objects, the groupBy forced the initialisation of the objects which were subsequently lost when the shown method returned. When the "mutated" objects were "used", the objects were in fact reinitialised.

As pointed out by @Enigmativity, in this method, the signature should probably be asking for IList<MyObject> to be sure of the existence of the objects as the method intends to mutate them. This means that the caller of the method is required to execute any LINQ queries before calling the method thereby avoiding this pitfall.

Sign up to request clarification or add additional context in comments.

2 Comments

Or if you'd captured the mutated objects (returned them) - I personally think the GroupBy might have triggered the db query to run, it was captured temporarily but not returned. I guess if you'd stayed away from mutating and tended towards thinking in "immutable mode" then you probably would have returned it.. Good learning day though! 😀
Mutability and deferred execution can lead to issues.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.