简体   繁体   中英

Strange behavior with C# enums

I'm working on a game here, and found this rather intriguing bug. Suppose you have this enum:

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

a base class

public class BaseItem
{
    public string Name = "Default Name";

    public ItemType Type = ItemType.Misc;
}

and two instances of it:

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

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

This is what happened to me: The first instance's type would remain as pre-initialized in the base class, even though I specified in its constructor that I want the type to be Food. The 2nd instance's type would be set correctly to Tools.

IF, I added a new enum value BEFORE Food, such as:

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

Then the 1st instance's type would be, as expected, Food. The 2nd instance's type would also be correct.

What could have caused this behavior? To describe it in short, all instances whose Type had been set in the constructor to the first value of the enum, would actually go back to the value they had in the BaseItem definition. Adding an extra value before the first enum value solved the problem, apparently; but it IS wrong, so I'd like to know what could have caused the issue.

Thanks!

--- Later Edit --- In case this helps: not doing any initialization to the "Type" field inside of BaseItem, and leaving only the braces constructor do the initialization, everything works fine without adding the "Nothing" value to the enum.

Sorry about this; after some more digging, it seems it's an Unity-only bug. Some other people encountered it too. I have solved the problem; everyone gets a vote up from me, and I'll add my own answer; maybe some other Unity users will find it. Thanks a lot for your help and interest!

In case there are other Unity users that search for info here, thinking it's some .NET behavior issue: It seems it's an Unity-only bug. Other people encountered it too. Just avoid any kind of initialization in the class itself for this kind of stuff, or use properties.

It's definitely not a Microsoft .NET issue, and also not a Mono .NET issue (you may start a Mono project at any time, with the same code, and it will work properly).

如果声明您的枚举ItemType的代码在另一个程序集(项目)中,而不是声明BaseItem类的代码或引入Ins1Ins2变量的代码,那么在对其进行更改时重新编译所有程序集至关重要。 ItemType枚举的定义。

Just have an idea. Not sure if it will work. It seems the constructor of the BaseItem is being called after your initialization. Try to put a break point and see when the Constructor of BaseItem is being called. Or you could try to call the constructor of BaseItem explicitly before setting Name and Type.

This is just a guess, but, since the first value of an Enum is equivalent to 0, possibly the combination of setting a default value in the definition (ItemType.Misc, or 5) plus using a type initializer that sets the value back to what it would be if you didn't have a default value (ItemType.Food, or 0) is causing the default value you specified (ItemType.Misc) to be persisted.

Incidentally, and this is important, you don't actually have a constructor defined. The syntax

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

... is not a call to a constructor, it is a call to a type initializer. Yes, the default constructor gets called, but nothing happens in that constructor.

I am betting that if you were to actually use a constructor and set the default value of ItemType there, you would see this problem go away. Something like:

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

While I don't recall reading anything explicitly making this point, I feel that you shouldn't set a default value for a public property or field using the syntax you used. If it needs to be set, set it in the constructor so that it can easily be overridden. The syntax you used is more suitable for private backing fields rather than things that can be set from outside the class.

This code should work fine. Maybe you should try posting a minimal program that reproduces your problem, because it has to be resulting from something else.

There is one thing you should be aware of. What you are using is not a constructor, it's an object initialization. The main difference is that when you use an object initializer, the resulting code first calls the default constructor on your object, and then sets properties/fields.

So this code:

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

Is actually equivalent to this:

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

What you probably want is to define a constructor, like this:

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

    // ...
}

And then use it like this:

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

I would guess that using this more explicit approach might indirectly solve your bug, and would at least probably make your code less error-prone.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM