C#中将整数转换为通用枚举

125

类似于在C#中将int转换为枚举类型,但是我的枚举类型是一个泛型类型参数。如何处理是最佳的方式?

示例:

private T ConvertEnum<T>(int i) where T : struct, IConvertible
{
    return (T)i;
}

生成编译错误 无法将类型'int'转换为'T'

完整代码如下,其中value可以包含int或null。

private int? TryParseInt(string value)
{
    var i = 0;
    if (!int.TryParse(value, out i))
    {
        return null;
    }
    return i;
}

private T? TryParseEnum<T>(string value) where T : struct, IConvertible
{
    var i = TryParseInt(value);
    if (!i.HasValue)
    {
        return null;
    }

    return (T)i.Value;
}

这个链接或许有帮助:https://dev59.com/G3E85IYBdhLWcg3wgDvd - Sunny
在https://dev59.com/ZnM_5IYBdhLWcg3wjj0p上的最后一个答案更接近于你想要的内容。不过仍然不够聪明。我倾向于使用反射来进行操作,这样代码可以更加强大。在我看来,结构体并不足以限制泛型的操作。 - Tony Hopkinson
1
不需要装箱的东西:C#中将泛型枚举转换为int的非装箱方法 - nawfal
9个回答

166
我找到的最简单的方法是强制编译器通过向 object 添加一个转换来实现。
return (T)(object)i.Value;

14
如果您不喜欢拳击:c-sharp-non-boxing-conversion-of-generic-enum-to-int - nawfal
6
我们将枚举转换为整数,与您链接的 Stack Overflow 问题中情况相反。此外,那个问题没有解决方案。 - MatteoSp
您还可以使用枚举值分配静态数组,然后只需传递索引以检索正确的枚举值。这样可以避免进行任何类型的强制转换。示例(仅第11、14和34行与此概念有关):https://pastebin.com/iPEzttM4 - Krythic
我觉得这样做有点邪恶,但是嘿,它有效。 - Dunno
3
如果枚举的基础类型不是 int(即 byte 或 long 枚举,甚至 uint),则会引发异常。 - Oscar Abraham
(T)(对象)(整数)i.Value 对我起作用了。 - Night94

26
您可以使用 Enum.Parse 来实现此操作:
return (T)Enum.Parse(typeof(T), i.Value.ToString(), true);

这篇文章介绍了如何使用扩展方法来解析通用枚举:

1
@Guvante:我想我在我的示例中将值转换为了字符串。你认为这会引起问题吗? - James Johnson

19

以下是一种非常快速的解决方案,利用运行时创建静态泛型类的多个实例的特性。释放你内心的优化恶魔!

当以通用方式从流中读取枚举类型时,这种方法真的很出色。 结合一个外部类,它还缓存枚举的底层类型和 BitConverter,可以释放强大的能力。

void Main() 
{
    Console.WriteLine("Cast (reference): {0}", (TestEnum)5);
    Console.WriteLine("EnumConverter: {0}", EnumConverter<TestEnum>.Convert(5));
    Console.WriteLine("Enum.ToObject: {0}", Enum.ToObject(typeof(TestEnum), 5));

    int iterations = 1000 * 1000 * 100;
    Measure(iterations, "Cast (reference)", () => { var t = (TestEnum)5; });
    Measure(iterations, "EnumConverter", () => EnumConverter<TestEnum>.Convert(5));
    Measure(iterations, "Enum.ToObject", () => Enum.ToObject(typeof(TestEnum), 5));
}

static class EnumConverter<TEnum> where TEnum : struct, IConvertible
{
    public static readonly Func<long, TEnum> Convert = GenerateConverter();

    static Func<long, TEnum> GenerateConverter()
    {
        var parameter = Expression.Parameter(typeof(long));
        var dynamicMethod = Expression.Lambda<Func<long, TEnum>>(
            Expression.Convert(parameter, typeof(TEnum)),
            parameter);
        return dynamicMethod.Compile();
    }
}

enum TestEnum 
{
    Value = 5
}

static void Measure(int repetitions, string what, Action action)
{
    action();

    var total = Stopwatch.StartNew();
    for (int i = 0; i < repetitions; i++)
    {
        action();
    }
    Console.WriteLine("{0}: {1}", what, total.Elapsed);
}

启用优化后在Core i7-3740QM上的结果:

Cast (reference): Value
EnumConverter: Value
Enum.ToObject: Value
Cast (reference): 00:00:00.3175615
EnumConverter: 00:00:00.4335949
Enum.ToObject: 00:00:14.3396366

3
非常好,谢谢。不过你可能想要使用 Expression.ConvertChecked 来代替,这样枚举类型的数值溢出会导致抛出 OverflowException 异常。 - Drew Noakes
你的结果可能会有所不同,我在 try.dot.net(blazor)上运行了代码,那里的 EnumConverter<T> 比其他替代方案慢得多。首先转换为对象比直接转换慢了约 6 倍,但仍远好于其他选项。 - Herman

15

8

3
public static class Extensions
    {
        public static T ToEnum<T>(this int param)
        {
            var info = typeof(T);
            if (info.IsEnum)
            {
                T result = (T)Enum.Parse(typeof(T), param.ToString(), true);
                return result;
            }

            return default(T);
        }
    }

你可以在这里添加一些约束条件。这样就不需要进行 if (info.IsEnum) 检查了。public static T ToEnum<T>(this int param) where T : struct, Enum - Mike Christiansen

1

自C# 7.3以来,可以在.NET Framework中使用此不安全的转换,假设基础类型为int。

unsafe static TEnum Int2EnumUnsafe<TEnum>(int i) where TEnum : unmanaged, Enum
        => *(TEnum*)&i;

0

((T[])Enum.GetValues(typeof(T))) 可以用于构建从 int 到 Enum 类型的字典/查找表。总体而言,我更喜欢 Ramon 使用 "Unsafe.As" 来进行强制转换,因为枚举类型只是整数上的薄层,不值得围绕其伪装建造城堡(尽管这种薄度并不是什么坏事)。请注意来自 c# 7.3 的 Enum 类型约束。(基本的东西就是我们可以在通用枚举约束下强制转换 T 数组)

(如果我有声望,这将是一条评论)

    public static TEnum IntToEnum<TEnum>(int i)
    where TEnum : Enum
    {
        Array array = Enum.GetValues(typeof(TEnum));
        int[] intValues = (int[])array;
        TEnum[] enumValues = (TEnum[])array;
        var b = intValues.Zip(enumValues);
        //Consider saving the dictionary to avoid recreating each time
        var c = b.ToDictionary<(int n, TEnum e), int, TEnum>(p => p.n, p => p.e);
        return c[i];//KeyNotFoundException possible here
    }

在 @trinalbadger587 指出的可怕错误之后应该可以工作了(谢谢.. https://dotnetfiddle.net/1oYWjD)


当然,不使用 linq 也可以完成相同的事情,将 .Zip 和 .ToDictionary 行替换为 Dictionary<int, TEnum> c = new Dictionary<int, TEnum>(array.Length); for (int j = 0; j < array.Length; j++) c.Add(intValues[j], enumValues[j]); - Xela.Trawets
同样的数组转换也可以用于将泛型枚举设置为任意整数值; TEnum enumValue = ((TEnum[])(Array)(new int[] { -1 }))[0]; - Xela.Trawets
你可以在.NET fiddle上查看这个答案: https://dotnetfiddle.net/Nrc2oL - trinalbadger587
关于表现的事情兄弟要好好考虑一下 - undefined

0

这是一个新的答案,因为它有不同的看法。虽然这是一个古老的问题,但我昨天刚好在做这个...

与 @Ramon-de-Klein 相似,使用了 @trinalbadger587 的 dotnet-fiddle 示例

相当简洁和晦涩,但有时候这也没关系。 请注意,如果枚举存储在字节或16位ushort中,则需要正确的基础值类型。

        //Int to Enum using the hot span newness - but without unsafe{}
        Span<int> XS = stackalloc int[] { 100 };
        Console.WriteLine(MemoryMarshal.Cast<int, Bla>(XS)[0]);

        //Int to Enum using good old arrays
        Console.WriteLine(((Bla[])(Array)(new int[] { 100 }))[0]);

        //Enum to Int
        Console.WriteLine(((int[])(Array)(new Bla[] { Bla.B }))[0]);


enum Bla
{
    A = 0,
    B = 100
}

哪里有仿制药? - undefined

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