【问题标题】:How to modify the properties of a struct in a foreach loop?如何在 foreach 循环中修改结构的属性?
【发布时间】:2018-10-21 06:56:35
【问题描述】:

我希望下面的代码工作:

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++;
        }
    } 
}

由于 Rect 是一个结构,它是一个值类型,如果我在调用方法时通过引用传递参数,我只能更改它的 Y 属性。不幸的是,foreach 不喜欢在遍历它们时更改它的元素,但由于 VM.Cubes 也是一个 List,我也不能使用常规的 for 循环。

我应该怎么做才能让它工作?

【问题讨论】:

  • VM.Cubes是什么类型?
  • 这是因为 structs 是您提到的值类型。在列表中更改它们不会更改对任何内容的引用并且不允许。您不能拥有结构列表并通过引用修改列表;但由于列表是引用类型,您可以更新列表中的项目(更新新结构并存储它。)
  • 为什么你认为你不能在列表中使用常规的 for 循环?即使这是一个限制,也可以使用任何其他循环。
  • @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 是这里的问题
  • foreach 不适用于集合。它适用于枚举。它将在两者之间隐式转换。您所拥有的是 Enumeration 特定限制。假设您也没有特意为此使用 Enumeartion,则该限制不适用于任何其他循环。

标签: c# struct foreach reference ref


【解决方案1】:

您可以创建一个新列表。这使您可以将 Rect 结构视为不可变的,对于 c# 中的结构,它是 considered a good idea

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

然后将 FallDown 定义为:

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

这可能违反直觉(似乎它可能对内存压力或性能不利),但这就是所有函数式编程语言的工作方式,以及 LINQ 的设计方式,并且通常会表现得很好,甚至可以表现得比更新到位。

【讨论】:

  • “似乎它可能对内存压力或性能不利” 是的,如果我必须在每次立方体移动一个像素时创建一个新列表,我将在第一个立方体时已经有 800 个列表撞到地上。确定没有其他方法了吗?
【解决方案2】:

我想我可能会在这里插话......

虽然我同意 不可变 结构在很多情况下都有意义,但可读性和维护性也应始终考虑到您的考虑因素。如果性能是您的目标,那么在 Linq Select 中创建新结构不会叠加(在这种情况下)。您只是在执行更多分配并产生更多垃圾。

在任何正常情况下,这可能都无关紧要。但是,“如果”你对性能感兴趣并且你有指令和循环OCD,那么我会考虑替代方案并对你的解决方案进行基准测试。

给出以下3种方法

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();
}

结果

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

总结

我会对这些结果持保留态度。当您查看高性能代码时,事情并不是那么干净。但是这个故事的寓意是,如果您要追求性能而不是可读性和可维护性,则需要在实际情况下对代码进行基准测试。

【讨论】:

    猜你喜欢
    • 2020-06-05
    • 1970-01-01
    • 2013-03-21
    • 1970-01-01
    • 2013-03-22
    • 2013-02-01
    • 2011-09-11
    • 2013-10-09
    • 1970-01-01
    相关资源
    最近更新 更多