1

I would like the following code to work:

foreach (Rect item in VM.Cubes)
{
     VM.FallDown(ref item);
}

class VM
{
   public void FallDown(ref Rect entity)
    {
        if ((entity.Y + entity.Height < 800))
        {
            entity.Y++;
        }
    } 
}

Since Rect is a struct it is a valuetype and I can only change it's Y property if I pass parameters by reference when calling the method. Unfortunately foreach is not fond of changing it's elements when going through them, but since VM.Cubes is also a List I can't use regular for loop either.

What should I do to make it work?

D.W.
  • 23
  • 4
  • What type is `VM.Cubes`? – Rufus L May 10 '18 at 22:45
  • This is because structs is a value type as you mentioned. Changing them in a list is not changing a reference to anything and not allowed. You can't have a list of struct and modify the list by reference; but since the list is reference type you can update the items in the list (update a new struct and store it.) – Michael Puckett II May 10 '18 at 22:46
  • 1
    Why do you think you can not use a regular for loop on a list? Even if that was a limitation, just use any other loop instead. – Christopher May 10 '18 at 22:58
  • @Christopher foreach `CS1657 Cannot use 'blah' as a ref or out value because it is a 'foreach iteration variable'` alternatives `CS0206 A property or indexer may not be passed as an out or ref parameter` is the issue here – TheGeneral May 11 '18 at 00:40
  • foreach does not work on collections. It works on Enumerations. And it will implicitly convert between the two. What you have there is a **Enumeration** specific limitation. Asuming you do not go out of your way to use a Enumeartion for that as well, the limit does not apply to any other loop. – Christopher May 11 '18 at 11:51

2 Answers2

2

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.

TheGeneral
  • 79,002
  • 9
  • 103
  • 141
1

You could just create a new list. This lets you treat your Rect struct as immutable, which is considered a good idea for structs in c#.

VM.Cubes = VM.Cubes.Select
( 
    r => VM.FallDown(r)
)
.ToList();

Then define FallDown as:

public Rect FallDown(Rect input)
{
    if (input.Y >= 800) return input;
    return new Rect(input.X, input.Y+1);
}

It's maybe counterintuitive (seems like it might be bad for memory pressure or performance) but this is how all functional programming languages work, and how LINQ was designed to work, and in general will perform just fine and could even perform better than updating in place.

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • "seems like it might be bad for memory pressure or performance" Yeah it does, if i have to create a new list everytime a cube moves a pixel I will already have 800 lists just when the first cube hits the ground. Sure there isn't another way? – D.W. May 10 '18 at 23:06