C#枚举类型的奇怪行为

5

我是一名有用的助手,可以翻译文本。

我正在处理一个游戏项目,发现了一个相当有趣的 Bug。假设你有这样一个枚举:

public enum ItemType 
{
    Food,
    Weapon,
    Tools,
    Written,
    Misc
};

一个基础类。
public class BaseItem
{
    public string Name = "Default Name";

    public ItemType Type = ItemType.Misc;
}

并且有两个该实例:

Ins1 = new BaseItem
{
   Name = "Something",
   Type = ItemType.Food
};

Ins2 = new BaseItem
{
   Name = "Something too",
   Type = ItemType.Tools
}

这是我的经历:第一个实例的类型会保持为基类中的预初始化状态,即使我在其构造函数中指定要将类型设置为Food。 第二个实例的类型将正确地设置为Tools。
如果我在Food之前添加了一个新的枚举值,例如:
public enum ItemType 
{
    Nothing,
    Food,
    Weapon,
    Tools,
    Written,
    Misc
};

那么第一个实例的类型应该是食品,这是预期的。第二个实例的类型也是正确的。是什么导致了这种行为呢?简单来说,所有在构造函数中将Type设置为枚举的第一个值的实例实际上会回到它们在BaseItem定义中的值。在第一个枚举值之前添加一个额外的值似乎解决了问题,但这是错误的,因此我想知道是什么导致了这个问题。谢谢! --- 后续编辑 --- 如果有帮助的话:不对BaseItem内部的“Type”字段进行任何初始化,只留下大括号构造函数进行初始化,而没有添加“Nothing”值到枚举中,一切都可以正常运行。很抱歉,经过更多挖掘,似乎这是一个仅存在于Unity的错误。其他人也遇到了这个问题。我已经解决了这个问题,每个人都得到了我的赞成票,我会添加自己的答案,也许其他Unity用户会找到它。非常感谢您的帮助和支持!

8
你没有任何构造函数。 - SLaks
6
你的代码在我的电脑上能够正常运行。请提供一个 SSCCE - SLaks
1
首先,使用类型名称作为字段的做法是不好的,因为在之前已经定义了System.Type类。尝试重新命名并查看结果。另外,明确定义构造函数是一个好习惯。 - Kath
1
请检查VS项目设置,同时枚举是否在同一程序集中定义为类/代码块?在发布之前,您是否删除了任何常量数值等内容? - sll
1
关于构造函数:当用户代码为非静态类提供零个实例构造函数时,编译器将发出一个无参数的实例构造函数(对于非抽象类将是public),因此这不是问题。 - Jeppe Stig Nielsen
显示剩余5条评论
5个回答

2

如果有其他Unity用户在这里搜索信息,认为这是某种.NET行为问题:

似乎这是一个仅存在于Unity的错误。其他人也遇到了这个问题。对于这种情况,只需避免在类本身中进行任何形式的初始化,或使用属性。

这绝对不是Microsoft .NET问题,也不是Mono .NET问题(您可以随时启动一个Mono项目,并使用相同的代码,它将正常工作)。


你应该将自己的答案设置为问题的被采纳答案。 - Guillaume
1
我已经尝试过了,但是StackOverflow不允许我这样做,除非我再等一天。 - user932887

1

如果声明枚举ItemType的代码位于另一个程序集(项目)中,而不是声明BaseItem类或引入Ins1Ins2变量的代码,则在更改ItemType枚举的定义时重新编译所有程序集非常重要。


它们在同一个项目中,所以这不是问题 :( - user932887
@BogdanMarginean 当你对枚举“ItemType”进行更改时,仍需要停止正在运行/调试的进程,重新编译并从头开始启动。否则源代码可能与已编译的IL代码有不同的常量,这会导致奇怪的调试行为。 - Jeppe Stig Nielsen

0

这只是一个猜测,但由于枚举的第一个值等同于0,可能在定义中设置默认值(ItemType.Misc或5)加上使用类型初始化程序将值设置回如果没有默认值会是什么(ItemType.Food或0),导致您指定的默认值(ItemType.Misc)被保留。

顺便说一句,这很重要,您实际上没有定义构造函数。 语法

Ins1 = new BaseItem
{
   Name = "Something",
   Type = ItemType.Food
};

这不是对构造函数的调用,而是对类型初始化器的调用。是的,会调用默认构造函数,但在该构造函数中什么也不会发生。

我敢打赌,如果您实际使用构造函数并设置ItemType的默认值,您将看到此问题消失。例如:

public BaseItem()
{
    this.Type = ItemType.Misc;
}

虽然我不记得有任何明确说明这一点的阅读材料,但我认为你不应该使用你所使用的语法为公共属性或字段设置默认值。如果需要设置它,请在构造函数中设置,以便可以轻松地覆盖它。你使用的语法更适合于私有后备字段,而不是可以从类外部设置的东西。


添加BaseItem()构造函数并没有提供预期的结果;奇怪的是,只有在花括号构造函数中设置枚举的第一个值时,实例才会恢复到BaseItem中的值。 - user932887
1
不明确地提供构造函数没有问题。保证按照以下顺序进行:(1)字段初始化程序将值设置为“Misc”。 (2)默认构造函数运行(在我们的情况下什么也不做)。 (3)对象初始化程序中的赋值(new表达式)运行并将值设置为“Food”。因此最终结果是“Food”。这是由规范定义的。 - Jeppe Stig Nielsen
我已经更新了我的帖子,将BaseItem内部的任何初始化都删除了。 - user932887

0

这段代码应该可以正常工作。也许你应该尝试发布一个最小化的程序来重现你的问题,因为它必须是由其他原因导致的。

有一件事情你应该知道。你正在使用的不是构造函数,而是对象初始化。主要区别在于当你使用对象初始化时,生成的代码首先调用对象的默认构造函数,然后设置属性/字段。

所以这段代码:

var myItem = new BaseItem
{
    Name = "something",
    Type = ItemType.Misc
}

实际上等同于这个:

var myItem = new BaseItem();
myItem.Name = "something";
myItem.Type = ItemType.Misc;

你可能想要做的是定义一个构造函数,像这样:

class BaseItem
{
    public BaseItem(string name, ItemType type)
    {
        this.Name = name;
        this.Type = type;
    }

    // ...
}

然后像这样使用它:

var myItem = new BaseItem("something", ItemType.Misc);

我猜使用这种更明确的方法可能会间接解决您的错误,并且至少可能会使您的代码更少出错。

0
只是有一个想法,不确定它是否可行。似乎在初始化之后调用了BaseItem的构造函数。尝试设置断点并查看何时调用BaseItem的构造函数。或者您可以在设置名称和类型之前显式调用BaseItem的构造函数。

主构造函数在花括号的构造函数之前被调用;此外,如果有帮助的话,基类还有另一个字段:IsConsumable,在BaseItem中初始化为False,并在第一个实例中重新初始化为True;第一个实例中的枚举值是错误的,但IsConsumable具有正确的值True。 - user932887

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接