在 C# 中将整数转换为枚举

发布于 2024-08-11 05:29:26 字数 973 浏览 5 评论 0原文

C# 中有一些我无法理解的东西。您可以将超出范围的 int 转换为 enum 并且编译器不会退缩。想象一下这个enum

enum Colour
{
    Red = 1,
    Green = 2,
    Blue = 3
}

现在,如果你写:

Colour eco;
eco = (Colour)17;

编译器认为这样就可以了。还有运行时。呃?

为什么 C# 团队决定让这一切成为可能?我认为,这个决定忽略了使用枚举的意义,在这样的场景中:

void DoSomethingWithColour(Colour eco)
{
    //do something to eco.
}

在像 C# 这样的强类型语言中,我想假设 eco 将始终拥有合法的 Colour< /代码> 值。但事实并非如此。程序员可以调用我的方法,并将值 17 分配给 eco(如前面的代码片段所示),因此我的方法中的代码不得假设 eco 持有合法的颜色值。我需要明确地测试它并根据需要处理异常值。这是为什么呢?

以我的拙见,如果编译器在将超出范围的 int 转换为 enum 时发出错误(甚至警告)消息,那就更好了,如果int 值在编译时已知。如果不是,运行时应该在赋值语句处抛出异常。

你怎么认为?这有什么原因吗?

(注意。这是一个问题我很久以前在我的博客上发布了 但没有得到任何信息回复。)

There is something that I cannot understand in C#. You can cast an out-of-range int into an enum and the compiler does not flinch. Imagine this enum:

enum Colour
{
    Red = 1,
    Green = 2,
    Blue = 3
}

Now, if you write:

Colour eco;
eco = (Colour)17;

The compiler thinks that’s fine. And the runtime, too. Uh?

Why did the C# team decide to make this possible? This decision misses the point of using enums, I think, in scenarios like this:

void DoSomethingWithColour(Colour eco)
{
    //do something to eco.
}

In a strong-typed language like C#, I would like to assume that eco will always hold a legal Colour value. But this is not the case. A programmer could call my method with a value of 17 assigned to eco (as in previous code snippet), so the code in my method must not assume that eco holds a legal Colour value. I need to test for it explicitly and handle the exceptional values as I please. Why is this?

In my humble opinion, it would be much nicer if the compiler issued an error (or even a warning) message when casting an out-of range int into an enum, if the int value is known at compile time. If not, the runtime should throw an exception at the assignment statement.

What do you think? Is there any reason why this is so?

(Note. This is a question I posted ages ago on my blog but got no informative response.)

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

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

发布评论

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

评论(12

盗琴音 2024-08-18 05:29:26

猜测“为什么”总是危险的,但请考虑一下:

enum Direction { North =1, East = 2, South = 4, West = 8 }
Direction ne = Direction.North | Direction.East;

int value = (int) ne; // value == 3
string text = ne.ToString();  // text == "3"

[Flags] 属性放在枚举前面时,最后一行将更改为

string text = ne.ToString();  // text == "North, East"

Guessing about 'why' is always dangerous, but consider this:

enum Direction { North =1, East = 2, South = 4, West = 8 }
Direction ne = Direction.North | Direction.East;

int value = (int) ne; // value == 3
string text = ne.ToString();  // text == "3"

When the [Flags] attribute is put in front of the enum, that last line changes to

string text = ne.ToString();  // text == "North, East"
燕归巢 2024-08-18 05:29:26

不知道为什么,但我最近发现这个“功能”非常有用。前几天我写了这样的东西

// a simple enum
public enum TransmissionStatus
{
    Success = 0,
    Failure = 1,
    Error = 2,
}
// a consumer of enum
public class MyClass 
{
    public void ProcessTransmissionStatus (TransmissionStatus status)
    {
        ...
        // an exhaustive switch statement, but only if
        // enum remains the same
        switch (status)
        {
            case TransmissionStatus.Success: ... break;
            case TransmissionStatus.Failure: ... break;
            case TransmissionStatus.Error: ... break;
            // should never be called, unless enum is 
            // extended - which is entirely possible!
            // remember, code defensively! future proof!
            default:
                throw new NotSupportedException ();
                break;
        }
        ...
    }
}

,问题是,如何测试最后一个 case 子句?假设有人可能扩展 TransmissionStatus 并且不更新其使用者,这是完全合理的,就像上面可怜的小 MyClass 一样。然而,我仍然想验证它在这种情况下的行为。一种方法是使用铸造,例如

[Test]
[ExpectedException (typeof (NotSupportedException))]
public void Test_ProcessTransmissionStatus_ExtendedEnum ()
{
    MyClass myClass = new MyClass ();
    myClass.ProcessTransmissionStatus ((TransmissionStatus)(10));
}

Not sure about why, but I recently found this "feature" incredibly useful. I wrote something like this the other day

// a simple enum
public enum TransmissionStatus
{
    Success = 0,
    Failure = 1,
    Error = 2,
}
// a consumer of enum
public class MyClass 
{
    public void ProcessTransmissionStatus (TransmissionStatus status)
    {
        ...
        // an exhaustive switch statement, but only if
        // enum remains the same
        switch (status)
        {
            case TransmissionStatus.Success: ... break;
            case TransmissionStatus.Failure: ... break;
            case TransmissionStatus.Error: ... break;
            // should never be called, unless enum is 
            // extended - which is entirely possible!
            // remember, code defensively! future proof!
            default:
                throw new NotSupportedException ();
                break;
        }
        ...
    }
}

question is, how do I test that last case clause? It is completely reasonable to assume someone may extend TransmissionStatus and not update its consumers, like poor little MyClass above. Yet, I would still like to verify its behaviour in this scenario. One way is to use casting, such as

[Test]
[ExpectedException (typeof (NotSupportedException))]
public void Test_ProcessTransmissionStatus_ExtendedEnum ()
{
    MyClass myClass = new MyClass ();
    myClass.ProcessTransmissionStatus ((TransmissionStatus)(10));
}
暮年慕年 2024-08-18 05:29:26

我当然明白塞萨尔的观点,我记得这最初也让我感到困惑。在我看来,枚举在当前的实现中确实有点太低级并且存在漏洞。在我看来,这个问题有两种解决方案。

1) 如果枚举的定义具有 FlagsAttribute,则仅允许将任意值存储在枚举中。这样,我们可以在适当的时候(并显式声明)继续将它们用作位掩码,但是当简单地用作常量的占位符时,我们将在运行时检查值。

2) 引入一个单独的原始类型,称为位掩码,它允许任何 ulong 值。同样,我们将标准枚举限制为仅声明值。这样做的另一个好处是允许编译器为您分配位值。所以这:

[Flags]
enum MyBitmask
{ 
    FirstValue = 1, SecondValue = 2, ThirdValue = 4, FourthValue = 8 
}

相当于:

bitmask MyBitmask
{
    FirstValue, SecondValue, ThirdValue, FourthValue 
}

毕竟,任何位掩码的值都是完全可预测的,对吧?作为一名程序员,我非常高兴能够抽象出这些细节。

不过,现在已经太晚了,我想我们将永远停留在当前的实现上。 :/

I certainly see Cesar's point, and I remember this initially confused me too. In my opinion enums, in their current implementation, are indeed a little too low level and leaky. It seems to me that there would be two solutions to the problem.

1) Only allow arbitrary values to be stored in an enum if its definition had the FlagsAttribute. This way, we can continue to use them for a bitmask when appropriate (and explicitly declared), but when used simply as placeholders for constants we would get the value checked at runtime.

2) Introduce a separate primitive type called say, bitmask, that would allow for any ulong value. Again, we restrict standard enums to declared values only. This would have the added benefit of allowing the compiler to assign the bit values for you. So this:

[Flags]
enum MyBitmask
{ 
    FirstValue = 1, SecondValue = 2, ThirdValue = 4, FourthValue = 8 
}

would be equivalent to this:

bitmask MyBitmask
{
    FirstValue, SecondValue, ThirdValue, FourthValue 
}

After all, the values for any bitmask are completely predictable, right? As a programmer, I am more than happy to have this detail abstracted away.

Still, too late now, I guess we're stuck with the current implementation forever. :/

人心善变 2024-08-18 05:29:26

您不需要处理异常。该方法的前提条件是调用者应该使用枚举,而不是随意将任何 int 类型转换为该枚举。那将是疯狂的。难道枚举的重点不是使用整数吗?

就我而言,任何将 17 投射到 Color 枚举的开发人员都需要 17 踢他们的后背。

You don't need to deal with exceptions. The precondition for the method is that callers should use the enum, not cast any willy nilly int to the said enum. That would be madness. Isn't the point of enums not to use the ints?

Any dev who would cast 17 to the Colour enum would need 17 kicks up their backside as far as I'm concerned.

泅人 2024-08-18 05:29:26

这是您永远不应该为枚举分配整数值的众多原因之一。如果它们具有需要在代码其他部分使用的重要值,请将枚举转换为对象。

That is one of the many reasons why you should never be assigning integer values to your enums. If they have important values that need to be used in other parts of the code turn the enum into an object.

晚风撩人 2024-08-18 05:29:26

这是出乎意料的......我们真正想要的是控制铸造......例如:

Colour eco;
if(Enum.TryParse("17", out eco)) //Parse successfully??
{
  var isValid = Enum.GetValues(typeof (Colour)).Cast<Colour>().Contains(eco);
  if(isValid)
  {
     //It is really a valid Enum Colour. Here is safe!
  }
}

That is unexpected... what we really want is to control the casting... for instance:

Colour eco;
if(Enum.TryParse("17", out eco)) //Parse successfully??
{
  var isValid = Enum.GetValues(typeof (Colour)).Cast<Colour>().Contains(eco);
  if(isValid)
  {
     //It is really a valid Enum Colour. Here is safe!
  }
}
趴在窗边数星星i 2024-08-18 05:29:26

当你定义一个枚举时,你本质上是给值命名(如果你愿意的话,可以是语法糖)。当您将 17 转换为 Color 时,您正在存储一个没有名称的 Color 值。正如您最终可能知道的那样,无论如何它只是一个 int 字段。

这类似于声明一个只接受 1 到 100 之间的值的整数;我见过的唯一支持这种级别检查的语言是 CHILL。

When you define an enum you are essentially giving names to values (syntatic sugar if you will). When you cast 17 to Colour, you are storing a value for a Colour that has no name. As you probably know in the end it is just an int field anyways.

This is akin to declaring an integer that would accept only values from 1 to 100; the only language I ever saw that supported this level of checking was CHILL.

深海蓝天 2024-08-18 05:29:26
if (!Enum.IsDefined(typeof(Colour), 17))
{
    // Do something
}
if (!Enum.IsDefined(typeof(Colour), 17))
{
    // Do something
}
幸福还没到 2024-08-18 05:29:26

简短版本:

不要这样做。

尝试将枚举更改为整数且仅允许有效值(可能是默认后备)需要辅助方法。那时你就没有枚举了——你真的有一个类。

如果整数很重要,那就加倍如此——就像 Bryan Rowe 所说的那样。

Short version:

Don't do this.

Trying to change enums into ints with only allowing valid values (maybe a default fallback) requires helper methods. At that point you have don't have an enum--you reallly have a class.

Doubly so if the ints are improtant--like Bryan Rowe said.

少女七分熟 2024-08-18 05:29:26

我想我会分享我最终用来验证枚举的代码,因为到目前为止我们似乎没有任何有效的东西......

public static class EnumHelper<T>
{
    public static bool IsValidValue(int value)
    {
        try
        {
            Parse(value.ToString());
        }
        catch
        {
            return false;
        }

        return true;
    }

    public static T Parse(string value)
    {
        var values = GetValues();
        int valueAsInt;
        var isInteger = Int32.TryParse(value, out valueAsInt);
        if(!values.Select(v => v.ToString()).Contains(value)
            && (!isInteger || !values.Select(v => Convert.ToInt32(v)).Contains(valueAsInt)))
        {
            throw new ArgumentException("Value '" + value + "' is not a valid value for " + typeof(T));
        }

        return (T)Enum.Parse(typeof(T), value);
    }

    public static bool TryParse(string value, out T p)
    {
        try
        {
            p = Parse(value);
            return true;
        }
        catch (Exception)
        {
            p = default(T);
            return false;
        }

    }

    public static IEnumerable<T> GetValues()
    {
        return Enum.GetValues(typeof (T)).Cast<T>();
    }
}

Thought I'd share the code I ended up using to validate Enums, as so far we don't seem to have anything here that works...

public static class EnumHelper<T>
{
    public static bool IsValidValue(int value)
    {
        try
        {
            Parse(value.ToString());
        }
        catch
        {
            return false;
        }

        return true;
    }

    public static T Parse(string value)
    {
        var values = GetValues();
        int valueAsInt;
        var isInteger = Int32.TryParse(value, out valueAsInt);
        if(!values.Select(v => v.ToString()).Contains(value)
            && (!isInteger || !values.Select(v => Convert.ToInt32(v)).Contains(valueAsInt)))
        {
            throw new ArgumentException("Value '" + value + "' is not a valid value for " + typeof(T));
        }

        return (T)Enum.Parse(typeof(T), value);
    }

    public static bool TryParse(string value, out T p)
    {
        try
        {
            p = Parse(value);
            return true;
        }
        catch (Exception)
        {
            p = default(T);
            return false;
        }

    }

    public static IEnumerable<T> GetValues()
    {
        return Enum.GetValues(typeof (T)).Cast<T>();
    }
}
酒几许 2024-08-18 05:29:26

老问题,但这最近让我困惑,谷歌把我带到了这里。经过进一步搜索,我发现了一篇文章,最终让我感兴趣,并认为我会回来分享。

要点是 Enum 是一个结构体,这意味着它是一个值类型 (source)。但本质上它充当基础类型(int、byte、long 等)的派生类型。因此,如果您可以将枚举类型视为与其基础类型相同的东西,并添加一些附加功能/语法糖(如 Otávio 所说),那么您就会意识到这个“问题”并能够防范它。

说到这里,这是我编写的一个扩展方法的核心,用于轻松地将事物转换/解析为枚举:

if (value != null)
{
    TEnum result;
    if (Enum.TryParse(value.ToString(), true, out result))
    {
        // since an out-of-range int can be cast to TEnum, double-check that result is valid
        if (Enum.IsDefined(typeof(TEnum), result.ToString()))
        {
            return result;
        }
    }
}

// deal with null and defaults...

变量 value 的类型为 object,因为此扩展具有接受 int 的“重载” 、int?、字符串、Enum 和 Enum?。它们都被装箱并发送到进行解析的私有方法。通过按顺序同时使用 TryParseIsDefined ,我可以解析刚才提到的所有类型,包括混合大小写字符串,而且它非常强大。

用法如下(假设 NUnit):

[Test]
public void MultipleInputTypeSample()
{
    int source;
    SampleEnum result;

    // valid int value
    source = 0;
    result = source.ParseToEnum<SampleEnum>();
    Assert.That(result, Is.EqualTo(SampleEnum.Value1));

    // out of range int value
    source = 15;
    Assert.Throws<ArgumentException>(() => source.ParseToEnum<SampleEnum>());

    // out of range int with default provided
    source = 30;
    result = source.ParseToEnum<SampleEnum>(SampleEnum.Value2);
    Assert.That(result, Is.EqualTo(SampleEnum.Value2));
}

private enum SampleEnum
{
    Value1,
    Value2
}

希望对某人有所帮助。

免责声明:我们不使用标志/位掩码枚举...这尚未在该使用场景中进行测试,可能无法工作。

Old question, but this confused me recently, and Google brought me here. I found an article with further searching that finally made it click for me, and thought I'd come back and share.

The gist is that Enum is a struct, which means it's a value type (source). But in essence it acts as a derived type of the underlying type (int, byte, long, etc.). So if you can think of an Enum type as really just the same thing as its underlying type with some added functionality/syntactic sugar (as Otávio said) then you'll be aware of this "problem" and be able to protect against it.

Speaking of that, here is the heart of an extension method I wrote to easily convert/parse things to an Enum:

if (value != null)
{
    TEnum result;
    if (Enum.TryParse(value.ToString(), true, out result))
    {
        // since an out-of-range int can be cast to TEnum, double-check that result is valid
        if (Enum.IsDefined(typeof(TEnum), result.ToString()))
        {
            return result;
        }
    }
}

// deal with null and defaults...

The variable value there is of type object, as this extension has "overloads" that accept int, int?, string, Enum, and Enum?. They're all boxed and sent to the private method that does the parsing. By using both TryParse and IsDefined in that order, I can parse all the types just mentioned, including mixed case strings, and it's pretty robust.

Usage is like so (assumes NUnit):

[Test]
public void MultipleInputTypeSample()
{
    int source;
    SampleEnum result;

    // valid int value
    source = 0;
    result = source.ParseToEnum<SampleEnum>();
    Assert.That(result, Is.EqualTo(SampleEnum.Value1));

    // out of range int value
    source = 15;
    Assert.Throws<ArgumentException>(() => source.ParseToEnum<SampleEnum>());

    // out of range int with default provided
    source = 30;
    result = source.ParseToEnum<SampleEnum>(SampleEnum.Value2);
    Assert.That(result, Is.EqualTo(SampleEnum.Value2));
}

private enum SampleEnum
{
    Value1,
    Value2
}

Hope that helps somebody.

Disclaimer: we do not use flags/bitmask enums... this has not been tested with that usage scenario and probably wouldn't work.

雨夜星沙 2024-08-18 05:29:26

使用 Enum.Parse() 会抛出错误;

Enum parsedColour = (Colour)Enum.Parse(typeof(Colour), "17");

如果您愿意,可能值得使用它来引发运行时错误。

It would throw an error by using the Enum.Parse();

Enum parsedColour = (Colour)Enum.Parse(typeof(Colour), "17");

Might be worth using to get a runtime error thrown, if you like.

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