为什么结构体中的迭代器可以修改它?
我发现允许值类型中的迭代器方法修改此
。
但是,由于 CLR 的限制,调用方法看不到这些修改。 (this
按值传递)
因此,迭代器和非迭代器中的相同代码会产生不同的结果:
static void Main() {
Mutable m1 = new Mutable();
m1.MutateWrong().ToArray(); //Force the iterator to execute
Console.WriteLine("After MutateWrong(): " + m1.Value);
Console.WriteLine();
Mutable m2 = new Mutable();
m2.MutateRight();
Console.WriteLine("After MutateRight(): " + m2.Value);
}
struct Mutable {
public int Value;
public IEnumerable<int> MutateWrong() {
Value = 7;
Console.WriteLine("Inside MutateWrong(): " + Value);
yield break;
}
public IEnumerable<int> MutateRight() {
Value = 7;
Console.WriteLine("Inside MutateRight(): " + Value);
return new int[0];
}
}
输出:
Inside MutateWrong(): 7 After MutateWrong(): 0 Inside MutateRight(): 7 After MutateRight(): 7
为什么它不是编译器错误(或者在最少警告)来改变迭代器中的结构?
这种行为是一个不易理解的微妙陷阱。
匿名方法具有相同的限制,不能使用此
根本。
注意:可变结构是邪恶的;这在实践中永远不应该出现。
I discovered that iterator methods in value types are allowed to modify this
.
However, due to limitations in the CLR, the modifications are not seen by the calling method. (this
is passed by value)
Therefore, identical code in an iterator and a non-iterator produce different results:
static void Main() {
Mutable m1 = new Mutable();
m1.MutateWrong().ToArray(); //Force the iterator to execute
Console.WriteLine("After MutateWrong(): " + m1.Value);
Console.WriteLine();
Mutable m2 = new Mutable();
m2.MutateRight();
Console.WriteLine("After MutateRight(): " + m2.Value);
}
struct Mutable {
public int Value;
public IEnumerable<int> MutateWrong() {
Value = 7;
Console.WriteLine("Inside MutateWrong(): " + Value);
yield break;
}
public IEnumerable<int> MutateRight() {
Value = 7;
Console.WriteLine("Inside MutateRight(): " + Value);
return new int[0];
}
}
Output:
Inside MutateWrong(): 7 After MutateWrong(): 0 Inside MutateRight(): 7 After MutateRight(): 7
Why isn't it a compiler error (or at least warning) to mutate a struct in an iterator?
This behavior is a subtle trap which is not easily understood.
Anonymous methods, which share the same limitation, cannot use this
at all.
Note: mutable structs are evil; this should never come up in practice.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
为了证明警告的合理性,应该是在程序员可能得到意外结果的情况下。根据 Eric Lippert 的说法,“我们尝试仅在那些我们几乎可以肯定地说代码已损坏、具有误导性或无用的情况下保留警告。” 这是一个警告可能会产生误导的实例。
假设你有一个完全有效的对象:如果不是非常有用的话,
你有这个循环:
你希望这个循环打印
1,1,1,1
,而不是1, 2,3,4,对吧?所以课程完全按照你的预期进行。在这种情况下,警告是不合理的。
由于这显然不是代码被破坏、误导或无用的情况,因此您如何建议编译器生成错误或警告?
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:
And you have this loop:
You'd expect this loop to print
1,1,1,1
, not1,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?
引用自己的话“可变结构是邪恶的”:)
如果您为结构实现扩展方法,则会发生与您经历的相同的情况。
如果您尝试在扩展方法中修改结构,您的原始结构仍然不会改变。
这并不令人惊讶,因为扩展方法签名看起来像:
看着它,我们意识到发生了参数传递之类的事情,因此结构被复制。但是当您使用扩展时,它看起来像:
您会惊讶地发现变量 x 没有受到任何影响。
我认为您的收益构造在幕后做了类似于扩展的事情。
我会更难地表达开头的句子:
“结构是邪恶的”..一般来说;)
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:
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:
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 ;)
我的想法与 Gabe 所说的类似。至少从理论上讲,人们可能会选择使用 struct 来表现得像一种方法,将该方法的局部变量封装为实例字段:
我的意思是,它是一种显然很奇怪。不过,这让我想起了我过去遇到的想法,开发人员炮制了调用方法的更奇怪的方式,例如将方法的参数包装到对象中em> 并让该对象调用该方法——从某种意义上说是倒退。我想说的大概就是这样。
我并不是说这将是一件明智的事情,但它至少是一种以您所描述的方式使用
this
的方式,这可能是有意的,并且从技术上讲会表现出正确的行为。I had a similar thought to what Gabe said. It seems at least theoretically possible that one might opt to use a
struct
to behave kind of like a method, encapsulating that method's local variables as instance fields:I mean, it's kind of weird, obviously. It kind of reminds me of ideas I've encountered in the past, though, where developers have concocted ever-stranger ways of calling methods, such as wrapping a method's parameters into an object and letting that object call the method—going backwards, in a sense. I'd say that's roughly what this is.
I'm not saying this would be a wise thing to do, but it is at least a way of using
this
in the way you are describing that might be intentional, and would technically exhibit correct behavior.虽然我认为 .net 的缺陷在于不需要修改“this”的方法的特殊属性,但目前还不清楚会发生什么;这样的属性对于应用于不可变类类型以及可变结构可能很有用。用此类属性标记的方法只能用于结构变量、字段和参数,而不能用于临时值。
我认为没有任何方法可以避免迭代器按值捕获结构。完全有可能的是,当使用迭代器时,它所基于的原始结构可能不再存在。另一方面,如果结构实现了继承 IEnumerable的接口,但还包含返回 Value 的函数,则在使用枚举器之前将结构转换为该接口类型理论上可以允许迭代器保留对结构体而无需重新复制其值;即使在这种情况下,如果枚举器按值复制结构,我也不会感到惊讶。
It's not really clear what should happen, though I think .net is deficient in not requiring a special attribute for methods which modify 'this'; such an attribute could be useful applied to immutable class types as well as mutable structs. Methods which are tagged with such an attribute should only be usable on structure variables, fields, and parameters, and not on temporary values.
I don't think there's any way to avoid having the iterator capture the struct by value. It's entirely possible that by the time an iterator is used, the original struct upon which it was based may not exist anymore. On the other hand, if the struct implemented an interface that inherited IEnumerable<int>, but also included a function to return Value, casting the struct to that interface type before using the enumerator could in theory allow the iterator to keep a reference to the struct without having to recopy its value; I wouldn't be at all surprised if the enumerator copies the struct by value even in that case, though.