Consider
A a = default;
Console.WriteLine(a.Value); // 0, as expected
((A)(a)).GetEnumerator();
Console.WriteLine(a.Value); // 0, as expected
foreach (var i in a)
;
Console.WriteLine(a.Value); // 1, unexpected per spec
public struct A
{
public int Value;
public IEnumerator<int> GetEnumerator()
{
++Value;
return ((IEnumerable<int>)new int[1]).GetEnumerator();
}
}
Per 13.9.5.2 Synchronous foreach, the correct expansion uses ((C)(x)).GetEnumerator() to acquire the enumerator. In this example, C is A and x is a.
Casting a struct variable to its own type creates a copy, so according to the spec, the correct behavior is to mutate a copy of a.
I think the spec is wrong for the following reasons:
- I do expect
foreach (var i in a) to call GetEnumerator on a without cast.
- When the resolved
GetEnumerator method is an extension method taking this ref A, the expression ((A)(a)).GetEnumerator() (as required by the current spec) doesn't compile. Since extension GetEnumerator is added in or after C# 7.2 (which is when taking receiver by ref is allowed in extension methods), it's intended to work. (Update: It seems foreach by extension method is added in commit 0372a1384efd01df4ed1f7ef6bf2798999c86967 for C# 9.)
In fact, the spec has more incorrect wordings. Suppose struct A implements IEnumerable<int> explicitly without a public GetEnumerator method, the current behavior is to use constrained callvirt on a (hence potentially mutating, and also inside GetEnumerator the struct can check its identity) instead of casting it to the interface then invoking GetEnumerator (which boxes hence only mutates a copy).
Consider
Per 13.9.5.2 Synchronous foreach, the correct expansion uses
((C)(x)).GetEnumerator()to acquire the enumerator. In this example,CisAandxisa.Casting a
structvariable to its own type creates a copy, so according to the spec, the correct behavior is to mutate a copy ofa.I think the spec is wrong for the following reasons:
foreach (var i in a)to callGetEnumeratoronawithout cast.GetEnumeratormethod is an extension method takingthis ref A, the expression((A)(a)).GetEnumerator()(as required by the current spec) doesn't compile. Since extensionGetEnumeratoris added in or after C# 7.2 (which is when taking receiver byrefis allowed in extension methods), it's intended to work. (Update: It seemsforeachby extension method is added in commit0372a1384efd01df4ed1f7ef6bf2798999c86967for C# 9.)In fact, the spec has more incorrect wordings. Suppose
struct AimplementsIEnumerable<int>explicitly without a publicGetEnumeratormethod, the current behavior is to useconstrained callvirtona(hence potentially mutating, and also insideGetEnumeratorthe struct can check its identity) instead of casting it to the interface then invokingGetEnumerator(which boxes hence only mutates a copy).