I thought i might chime in here...
Though i agree Immutable structs make sense in a lot of situations, also readability and maintenance should always factor into your considerations. If performance is your goal, then creating a new struct in a Linq Select
doesn't stack-up (in this situation). You are just performing more allocations and creating more garbage.
In any normal situation it probably doesn't matter. However, "if" you are interested in performance and you have instruction and cycle OCD, then i would consider alternatives and benchmark you solutions.
Given the following 3 methods
public void ByRef(ref Rect entity)
{
if (entity.Y + entity.Height > 800)
{
entity.Y++;
}
}
public Rect ByImutableReturn(Rect entity)
{
return entity.Y + entity.Height > 800 ? new Rect(entity.Height, entity.Y + 1) : entity;
}
public Rect ByReturn(Rect entity)
{
if (entity.Y + entity.Height > 800)
{
entity.Y++;
}
return entity;
}
LinqImutableReturn
protected override List<Rect> InternalRun()
{
return Input.Cubes = Input.Cubes.Select(r => Input.ByImutableReturn(r))
.ToList();
}
LinqReturn
protected override List<Rect> InternalRun()
{
return Input.Cubes = Input.Cubes.Select(r => Input.ByReturn(r))
.ToList();
}
ForLoopByRef
protected override List<Rect> InternalRun()
{
for (var index = 0; index < Input.Cubes.Count; index++)
{
var t = Input.Cubes[index];
Input.ByRef(ref t);
Input.Cubes[index] = t;
}
return Input.Cubes.ToList();
}
ForLoopImutableReturn
protected override List<Rect> InternalRun()
{
for (var index = 0; index < Input.Cubes.Count; index++)
{
Input.Cubes[index] = Input.ByImutableReturn(Input.Cubes[index]);
}
return Input.Cubes.ToList();
}
ForLoopReturn
protected override List<Rect> InternalRun()
{
for (var index = 0; index < Input.Cubes.Count; index++)
{
Input.Cubes[index] = Input.ByReturn(Input.Cubes[index]);
}
return Input.Cubes.ToList();
}
Results
Mode : Release
Test Framework : .NET Framework 4.7.1
Benchmarks runs : 100 times (averaged/scale)
Scale : 10,000
Name | Time | Range | StdDev | Cycles
--------------------------------------------------------------------------
ForLoopByRef | 0.073 ms | 0.001 ms | 0.07 | 244,964
ForLoopReturn | 0.097 ms | 0.006 ms | 0.05 | 332,372
ForLoopImutableReturn | 0.116 ms | 0.003 ms | 0.08 | 388,188
LinqImutableReturn | 0.325 ms | 0.007 ms | 0.25 | 1,117,130
LinqReturn | 0.347 ms | 0.002 ms | 0.07 | 1,195,351
Scale : 100,000
Name | Time | Range | StdDev | Cycles
---------------------------------------------------------------------------
ForLoopByRef | 0.635 ms | 0.168 ms | 0.11 | 2,215,066
ForLoopImutableReturn | 0.867 ms | 0.175 ms | 0.10 | 3,027,096
ForLoopReturn | 0.890 ms | 0.225 ms | 0.09 | 3,109,831
LinqReturn | 2.957 ms | 0.166 ms | 0.17 | 10,347,672
LinqImutableReturn | 3.084 ms | 0.219 ms | 0.40 | 10,780,304
Scale : 1,000,000
Name | Time | Range | StdDev | Cycles
-----------------------------------------------------------------------------
ForLoopByRef | 6.624 ms | 1.685 ms | 0.83 | 23,156,409
ForLoopImutableReturn | 9.574 ms | 1.678 ms | 0.82 | 33,503,375
ForLoopReturn | 9.811 ms | 2.290 ms | 0.86 | 34,324,963
LinqImutableReturn | 32.463 ms | 1.401 ms | 1.11 | 113,246,111
LinqReturn | 32.973 ms | 0.830 ms | 1.18 | 114,892,311
Summary
I would take these results with a grain of salt. Things are not so clean cut when you are looking at high performance code. But the moral of this story is if you are going for performance over readability and maintainability you need to bench mark code in realistic situations.