Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - LINQ Select() calls function twice

I'm using Unity. I'm using IEnumerable.Select() to take a List of types and add them (as components) to a GameObject. Running this:

var newObjects = types.Select(t => (IGameManager)gameObject.AddComponent(t));

Actually adds two of the same type of component to the gameObject in question, although newObjects only contains a reference to one. The other created component just floats around, without returning a reference. Running this:

foreach(var t in types)
{
    newObjects.Add((IGameManager)gameObject.AddComponent(t));
}

Works, and only adds one of each component type to the GameObject. But, it looks kinda ugly. (IGameManager is an interface all the types in question implement.)

I could just use the foreach loop, but that's not entirely eloquent, and besides, I can't find any articles online explaining this behavior and thus my curiosity is getting the better of me.

So, my question is: Why does Select() call the specified function twice per input, yet only return one copy of the resulting data? And how do I fix/prevent/compensate for this behavior?

Thanks!

like image 232
Lucas Niewohner Avatar asked May 05 '17 03:05

Lucas Niewohner


1 Answers

You haven't supplied the surrounding code where you use it, the line you give actually doesn't do anything at that point, nothing is evaluated, just an expression tree is setup.

Taking some liberties with your code and assuming you can get to the Components on gameobject

 var gameObject = new GameObject();
 var types = new List<string>() { "a", "b", "c" };
 var newObjects = types.Select(t => (IGameManager)gameObject.AddComponent(t));
// at this point, nothing has happened....
 Console.WriteLine(gameObject.Components.Count());  // 0
 Console.WriteLine(newObjects.Count());   // 3 - we trigger the selects
 Console.WriteLine(gameObject.Components.Count()); // 3
 Console.WriteLine(newObjects.Count()); // 3 - we trigger the selects again
 Console.WriteLine(gameObject.Components.Count());  // 6

with your code you've created an expression tree, each time you do something with it, its going to trigger the expressions within it. I'm guessing this is what's happening to you. You can see here in my code that each time you ask for the count you get more objects in your gameobjects.

To avoid this, evaluate it once by turning it into a list or array like so :-

var newObjects = types.Select(t => (IGameManager)gameObject.AddComponent(t)).ToList();
like image 166
Keith Nicholas Avatar answered Sep 19 '22 23:09

Keith Nicholas