Why can iterators in structs modify this?
In order to justify a warning, it should be in a situation where the programmer is likely to get unexpected results. According to Eric Lippert, "we try to reserve warnings for only those situations where we can say with almost certainty that the code is broken, misleading or useless." Here is an instance where the warning would be misleading.
Let's say you have this perfectly valid – if not terribly useful – object:
struct Number
{
int value;
public Number(int value) { this.value = value; }
public int Value { get { return value; } }
// iterator that mutates "this"
public IEnumerable<int> UpTo(int max)
{
for (; value <= max; value++)
yield return value;
}
}
And you have this loop:
var num = new Number(1);
foreach (var x in num.UpTo(4))
Console.WriteLine(num.Value);
You'd expect this loop to print 1,1,1,1
, not 1,2,3,4
, right? So the class works exactly as you expect. This is an instance where the warning would be unjustified.
Since this is clearly not a situation where the code is broken, misleading, or useless, how would you propose that the compiler generate an error or warning?
To cite yourself "mutable structs are evil" :) The same thing as you experienced happens if you implement an extension method for a struct. If you try to modify the struct within the extension method you still will have your original struct unchanged. It is a bit less surprising since the extension method signature looks like:
static void DoSideEffects(this MyStruct x) { x.foo = ...
Looking at it we realize that something like parameter passing happens and therefore the struct is copied. But when you use the extension it looks like:
x.DoSideEffects()
and you will be surprised not to have any effects on your variable x. I suppose that behind the scenes your yield constructs do something similar to extensions. I would phrase the starting sentence harder: "structs are evil" .. in general ;)