【问题标题】:Modify Struct variable in a Dictionary修改字典中的结构变量
【发布时间】:2011-09-09 11:07:43
【问题描述】:

我有一个这样的结构:

public struct MapTile
{
    public int bgAnimation;
    public int bgFrame;
}

但是当我用 foreach 循环它来改变动画帧时,我做不到......

代码如下:

foreach (KeyValuePair<string, MapTile> tile in tilesData)
{
        if (tilesData[tile.Key].bgFrame >= tilesData[tile.Key].bgAnimation)
        {
            tilesData[tile.Key].bgFrame = 0;
        }
        else
        {
            tilesData[tile.Key].bgFrame++;
        }
}

它给了我编译错误:

Error 1 Cannot modify the return value of 'System.Collections.Generic.Dictionary<string,Warudo.MapTile>.this[string]' because it is not a variable
Error 2 Cannot modify the return value of 'System.Collections.Generic.Dictionary<string,Warudo.MapTile>.this[string]' because it is not a variable

为什么我不能更改字典中的结构中的值?

【问题讨论】:

  • Ewww...可变结构。 (有人不得不说)
  • 你没有在这里使用课程有什么原因吗?
  • @Justin Niessner,为什么不好?
  • @Lurler - 因为它们会导致大量混乱。人们开始像使用类一样使用它们,并期望它们的行为也与类相同……但事实并非如此(这就是您的代码存在问题的原因)。
  • @Lurler:这正是可变结构的问题——行为是有问题的。大多数时候,出于性能原因选择结构是过早的优化。最好将您的代码编写成可用和可维护的,然后进行分析,然后在必要时进行优化。在这种情况下,我怀疑这实际上会更快,尤其是如果它存储在字典中。

标签: c# dictionary struct foreach


【解决方案1】:

机会结构到类

之前:

public struct MapTile
{
    public int bgAnimation;
    public int bgFrame;
}

之后:

public Class MapTile
{
    public int bgAnimation;
    public int bgFrame;
}

【讨论】:

    【解决方案2】:

    我建议创建一个实用程序类:

    public class MutableHolder<T>
    {
        public T Value;
        public MutableHolder(T value)
        {
            this.Value = value;
        }
    }
    

    然后将新创建的MutableHolder&lt;MapTile&gt; 存储到每个字典槽中,而不是直接存储MapTile。这将允许您轻松更新与任何特定键关联的地图图块,而无需修改字典本身(否则,至少会使 foreach 循环使用的枚举器无效)。

    【讨论】:

    • 框架中是否已经有什么是我现在应该使用的通用 MutableHolder 了?我主要是自己创建的。
    • @MichaelCovelli:可以使用单元素数组,当然一开始就可以使用,但我不知道是否存在任何与上述等效的标准框架实用程序类。另一方面,MutableHolder 足够简单,使用自己的没有太多缺点。
    【解决方案3】:

    tilesData[tile.Key] 不是存储位置(即,它不是变量)。它是与字典 tilesData 中的键 tile.Key 关联的 MapTile 实例的副本。这就是struct 发生的情况。它们的实例的副本到处传递并返回(这也是为什么可变结构被认为是邪恶的很大一部分)。

    你需要做的是:

        MapTile tile = tilesData[tile.Key];
        if (tile.bgFrame >= tile.bgAnimation)
        {
            tile.bgFrame = 0;
        }
        else
        {
            tile.bgFrame++;
        }
        tilesData[tile.Key] = tile;
    

    【讨论】:

    • 我认为结构不是引用类型,而是变量类型......奇怪。谢谢你的解释。
    • 现在我也明白了为什么它们被认为是邪恶的 :) 我可能会在未来改变它的工作方式 :)
    • struct 是值类型。值类型的显着特点是它们是按值复制的,相等是由值相等定义的。
    【解决方案4】:

    索引器将返回该值的副本。对该副本进行更改不会对字典中的值做任何事情...编译器阻止您编写错误代码。如果你想修改字典中的值,你需要使用类似的东西:

    // Note: copying the contents to start with as you can't modify a collection
    // while iterating over it
    foreach (KeyValuePair<string, MapTile> pair in tilesData.ToList())
    {
        MapTile tile = pair.Value;
        tile.bgFrame = tile.bgFrame >= tile.bgAnimation ? 0 : tile.bgFrame + 1;
        tilesData[pair.Key] = tile;
    }
    

    请注意,这也是避免无缘无故进行多次查找,您的原始代码正在这样做。

    我个人强烈建议反对以可变结构开始,请注意...

    当然,另一种选择是使其成为引用类型,此时您可以使用:

    // If MapTile is a reference type...
    // No need to copy anything this time; we're not changing the value in the
    // dictionary, which is just a reference. Also, we don't care about the
    // key this time.
    foreach (MapTile tile in tilesData.Values)
    {
        tile.bgFrame = tile.bgFrame >= tile.bgAnimation ? 0 : tile.bgFrame + 1;
    }
    

    【讨论】:

    • ...这就是我发表评论而不是答案的原因。我知道当可变结构的话题出现时,Jon Skeet 也不会落后。
    • 啊,现在我知道是什么问题了。感谢您的帮助!
    • @JustinNiessner 不管怎样,Jon Skeet 提供了一个非常好的和简洁的答案!
    猜你喜欢
    • 1970-01-01
    • 2018-10-29
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    相关资源
    最近更新 更多