更改 foreach 迭代变量以及 C# 和 C++/CLI 之间的实现差异

发布于 2024-08-17 01:47:32 字数 939 浏览 4 评论 0原文

考虑以下 C# 代码。

string[] stringArray = new string[10];
foreach (string s in stringArray)
    s = "a new string";  // Compiler error - Can't assign to foreach iteration variable

现在考虑以下有效 C++/CLI 代码。

array<String^>^ stringArray = gcnew array<String^>(10);
for each(String^% s in stringArray)
    s = "a new string"; 

foreach 与数组类型一起使用时,编译器会将其转换为正常的 for 循环。此实现对于 C# 和 C++/CLI 是相同的。所以我想知道 C++/CLI 是否可以允许这样做,为什么 C# 编译器不允许?

当类型不是数组时,此错误是有意义的,因为 foreach 将被编译为 GetEnumerator 调用并使用枚举器进行迭代。但我认为数组类型是可以允许的。

有什么想法吗?

附带说明一下,以下也是有效的 C++/CLI 代码,但不会产生预期的结果。

List<String^>^ stringList = gcnew List<String^>(10);
for each(String^% s in stringList)
    s = "a new string"; // I think this should be prevented by compiler as it makes no sense.

Consider the following C# code.

string[] stringArray = new string[10];
foreach (string s in stringArray)
    s = "a new string";  // Compiler error - Can't assign to foreach iteration variable

Now consider the following valid C++/CLI code.

array<String^>^ stringArray = gcnew array<String^>(10);
for each(String^% s in stringArray)
    s = "a new string"; 

When foreach is used with array type, compiler translates it into normal for loop. This implementation is same for C# and C++/CLI. So I wonder if C++/CLI can allow this, why not for C# compiler?

This error makes sense when the type is not an array as foreach will be compiled into GetEnumerator call and use the enumerator for iteration. But I think it can be allowed for array types.

Any thoughts?

As a side note, the following is a valid C++/CLI code too but will not produce the expected result.

List<String^>^ stringList = gcnew List<String^>(10);
for each(String^% s in stringList)
    s = "a new string"; // I think this should be prevented by compiler as it makes no sense.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

内心荒芜 2024-08-24 01:47:32

这里似乎存在三个不同的问题:

  1. 为什么 C++ 允许为每个迭代变量分配一个
  2. 为什么 C# 没有?
  3. 为什么 C++ 和 C# 编译器的行为不同?

答案相当简单:

  1. 因为 C++ 团队没有决定明确禁止它,而且从技术上讲,迭代变量只是一个局部变量 - 它没有得到特殊对待。 p>

  2. 因为 C# 团队确实决定禁止它,因为(很可能)他们认为这会导致错误或不正确的代码。分配给任何循环变量通常被认为是一种代码味道。

  3. 因为C++团队和C#团队是不同的团队。 C++ 一直是一种让你搬起石头砸自己脚的语言,如果你愿意的话,甚至会把装满子弹的枪递给你。 C# 通常会尝试强制执行“正确代码”规则。

这里实际上可能还有另一个问题,那就是:

  • 如果 C# 不允许赋值,为什么要把 foreach 编译成 for 呢?或者,相反 - 如果这就是它的编译方式,为什么不允许这样做?

这个问题实际上有两个答案:

  • 因为它更快。 foreachIEnumerable 进行操作,这需要实例化一个新的 IEnumerator 类。数组类型是编译器识别的特殊类型,因此如果编译器已经知道 IEnumerable 实际上是一个 Array,那么它会编译为索引访问,这要多得多更便宜。

    然而,这个小小的性能调整只是一个实现细节;它不是规范的一部分,如果您能够编写依赖于特定实现的代码,那么 C# 团队以后将无法在不破坏现有代码的情况下更改该实现。他们当然希望避免这种情况。

  • 因为人们认为的方式实际上并不重要。如果您可以在 C# 中进行赋值,那么您实际上不会修改数组,而只会修改最初保存数组中某些内容的局部变量的内容。这又属于“使编写不正确的代码变得困难”的类别 - 如果构造确实允许您分配给变量,一些程序员可能会认为这实际上会改变集合,这是错误的。

我认为这应该很好地解释它。

There seem to be three different questions here:

  1. Why does C++ allow you to assign to a for each iteration variable?
  2. Why doesn't C#?
  3. Why do the C++ and C# compilers behave differently?

The answers are fairly straightforward:

  1. Because the C++ team didn't decide to explicitly disallow it, and technically the iteration variable is just a local variable - it doesn't get special treatment.

  2. Because the C# team did decide to disallow it, because (most likely) they believe it would lead to bugs or incorrect code. Assigning to any loop variable is commonly considered a code smell.

  3. Because the C++ team and the C# team are different teams. C++ has always been a language that allows you to shoot yourself in the foot, if you so choose, and goes so far as to hand you the loaded gun. C# will often try to enforce "correct code" rules.

There might actually be another question here, which is:

  • Why would C# compile a foreach into a for if it doesn't allow assignment? Or, the converse - why doesn't allow this if that's how it gets compiled anyway?

There are actually two answers to this one:

  • Because it's faster. foreach operates on IEnumerable, which requires a new IEnumerator class to be instantiated. Array types are special types recognized by the compiler, so if the compiler already knows that the IEnumerable is actually an Array, then it compiles down to indexed access instead, which is much cheaper.

    This little performance tweak is simply an implementation detail, however; it is not part of the specification, and if you were able to write code that depends on the specific implementation, the C# team would be unable to change that implementation later without breaking existing code. They would certainly want to avoid such a situation.

  • Because it doesn't actually matter the way one might think it does. If you could do the assignment in C#, you would not actually be modifying the array, only the contents of the local variable that initially held something from the array. This, again, falls under the category of "make it difficult to write incorrect code" - if the construct did allow you to assign to the variable, some programmers might think that this would actually change the collection, which would be false.

I think that should explain it pretty well.

爺獨霸怡葒院 2024-08-24 01:47:32

这是因为 C# 使用了烟雾和镜子(即魔法)。您在 foreach 中返回的变量不是数组中的实际项目,它是由迭代对象创建的副本......或其他东西。我们真的不知道(我们确实知道,但我们必须打破抽象层并查看迭代器对象的实现。)

如果您想更改数组中的值,则必须直接处理数组的接口访问这些项目。在c++中会发生这种情况,但大多是错误的(很多c++都是这样,最初的实现实际上是宏和预处理)。在 C# 中,它被明确定义为不起作用——因此是一条编译器消息。 (请参阅规范中的第 5.3.3.16 节。

This is because C# is using smoke and mirrors (ie magic). The variable you get back in the foreach is not the actual item in the array, it is a copy made by the iteration object... or something else. We don't really know (well we do, but we have to break the abstraction layer and look at the implementation of the iterator object.)

If you want to change the values IN the array, you have to deal directly with the array's interface to access those items. In c++ this happens, but mostly by mistake (a lot of c++ is like this, the original implementations were actually macros and pre-processing). In C# it is explicitly defined not to work -- thus a compiler message. (See section 5.3.3.16 in the spec.)

_蜘蛛 2024-08-24 01:47:32

我认为当您针对 C++ 情况陈述这一点时,您已经回答了自己的问题:

foreach与数组类型一起使用时,编译器会将其转换为正常的for循环。

如果您将 C# 编码为 for 循环,则可以执行此操作。

MSDN 页面 上的评论解释了更多信息,但归结为这样一个事实:通过修改字符串,您正在更改集合的索引 - 这就是循环中断的原因。

I think you've answered your own question when you state that for the C++ case:

When foreach is used with array type, compiler translates it into normal for loop.

If you code the C# as a for loop you can do this.

The comments on this MSDN page explain more, but it boils down to the fact that by modifying the string you are changing the indexing of the collection - which is why the loop breaks.

无人问我粥可暖 2024-08-24 01:47:32

回复霍根:
是的,它会制作一份副本。事实上,当我(今天)遇到它时,我指望它是一个可修改的副本:

foreach (string Token in tokenize(Command))
{
foreach (KeyValuePair<string, string> Replacement in TokensToReplace)
    {
    if (Token==Replacement.Key)
        {
        Token = Replacement.Value;
        }
    }
TokenList.Add(Token);
}

我认为不幸的是这不起作用。

Re Hogan:
Yes, a copy gets made by it. In fact, when I was running into it (today) I was banking on it being a modifieable copy:

foreach (string Token in tokenize(Command))
{
foreach (KeyValuePair<string, string> Replacement in TokensToReplace)
    {
    if (Token==Replacement.Key)
        {
        Token = Replacement.Value;
        }
    }
TokenList.Add(Token);
}

I think it's unfortunate that this doesn't work.

川水往事 2024-08-24 01:47:32

Aaronaught,循环不必反转该函数,它只需创建一个字符串变量“Token”并使用 tokenize 中的值对其进行初始化。之后它与 tokenize 的任何内部结构都没有任何关系。
我想要了解的是,循环变量不应该是指向集合的魔术指针,而是由“string Token=”创建的对象,就像在 for 或 while 循环中创建的一样。

我不是试图修改某些内部集合值,而是覆盖局部变量。它不应该对集合中的数据产生影响,因为指向旧字符串的指针在分配后会丢失。

在 C++ 中它可以工作。

Aaronaught, the loop doesn't have to reverse the function it just has to create a string variable "Token" and initialize it with the value from tokenize. After that it doesn't have anything to do with any internals of tokenize.
What I was trying to get at is that the loop variable shouldn't be some magic pointer into a collection but an Object that gets created by "string Token=" just as if it was created in a for or while loop.

I am NOT trying to modify some internal collection value but overwrite a local variable. It's not supposed to have consequences for the data in the collection since the pointer to the old string gets lost after the assignment.

In C++ it works.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文