using System;
using System.Numerics;
public class Particles
{
private readonly List<Vector2> _positions = new();
public int Count => _positions.Count;
public Vector2 this[int i]
{
get => _positions[i];
set => _positions[i] = value;
}
public void Add(in Vector2 p) => _positions.Add(p); // List<T> stores values inline (no boxing)
}
public class Program
{
public static void Main()
{
var rnd = new Random(1);
var bounds = new BoundingBox(new Vector2(0, 0), new Vector2(100, 100));
var particles = new List<Particle>(capacity: 100);
for (int i = 0; i < 100; i++)
{
var pos = new Vector2(rnd.NextDouble() * 100, rnd.NextDouble() * 100);
var dir = new Vector2(rnd.NextDouble() - 0.5, rnd.NextDouble() - 0.5).Normalized();
var speed = 10 + rnd.NextDouble() * 20; // 10..30 units/sec
particles.Add(new Particle(pos, dir * speed));
}
double dt = 0.016; // ~60 FPS
double totalDistance = 0;
for (int step = 0; step < 1000; step++)
{
foreach (var p in particles)
{
totalDistance += p.Speed * dt;
p.Step(dt, in bounds);
}
}
Console.WriteLine($"Total distance: {totalDistance:F2}");
Console.WriteLine($"Average speed: {totalDistance / (particles.Count * 1000 * dt):F2}");
// Pitfall demonstration
var store = new Particles();
store.Add(new Vector2(1, 1));
foreach (var pos in Iterate(store))
{
// 'pos' is a copy — this does NOT mutate stored value
var copy = pos; copy = new Vector2(copy.X + 1, copy.Y + 1);
}
Console.WriteLine(store[0]); // still (1.00,1.00)
// Correct: index and assign back
var tmp = store[0];
tmp = new Vector2(tmp.X + 1, tmp.Y + 1);
store[0] = tmp;
Console.WriteLine(store[0]); // now (2.00,2.00)
}
private static IEnumerable<Vector2> Iterate(Particles ps)
{
for (int i = 0; i < ps.Count; i++)
yield return ps[i];
}
}
Notes on the solution
Vector2is areadonly struct, so property access doesn’t create defensive copies.- Operators take
in Vector2to avoid copying; the JIT can inline aggressively. - The simulation uses only value‑type math in the hot loop (no boxing/allocations).
- The pitfall demo shows why mutating a value returned from an indexer/
foreachvariable doesn’t affect the original.