关于.net:如何在C#中获取下一个(或以前的)枚举值

How to get next (or previous) enum value in C#

我有一个枚举,它的定义如下:

1
public enum eRat { A = 0, B=3, C=5, D=8 };

所以给定值eRat.B,我想得到下一个值eRat.C

我看到的解决方案是(不进行范围检查)

1
2
3
4
5
6
7
8
Array a = Enum.GetValues(typeof(eRat));
int i=0 ;
for (i = 0; i < a.GetLength(); i++)
{
       if (a.GetValue(i) == eRat.B)
            break;
}
return (eRat)a.GetValue(i+1):

现在这太复杂了,不适合这么简单的事情。你知道更好的解决办法吗?像eRat.B+1Enum.Next(Erat.B)之类的?

谢谢


感谢大家的回答和反馈。我很惊讶能得到这么多。通过观察并运用其中的一些想法,我想出了一个最适合我的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
public static class Extensions
{

    public static T Next<T>(this T src) where T : struct
    {
        if (!typeof(T).IsEnum) throw new ArgumentException(String.Format("Argument {0} is not an Enum", typeof(T).FullName));

        T[] Arr = (T[])Enum.GetValues(src.GetType());
        int j = Array.IndexOf<T>(Arr, src) + 1;
        return (Arr.Length==j) ? Arr[0] : Arr[j];            
    }
}

这种方法的好处在于,它使用起来既简单又通用。实现为通用扩展方法,可以通过以下方式在任何枚举上调用它:

1
return eRat.B.Next();

注意,我使用的是通用扩展方法,因此不需要在调用时指定类型,只需.Next()


可能有点过头了,但是:

1
2
3
eRat value = eRat.B;
eRat nextValue = Enum.GetValues(typeof(eRat)).Cast<eRat>()
        .SkipWhile(e => e != value).Skip(1).First();

或者如果你想要第一个数值更大的:

1
2
eRat nextValue = Enum.GetValues(typeof(eRat)).Cast<eRat>()
        .First(e => (int)e > (int)value);

或者对于下一个更大的数字(自己排序):

1
2
eRat nextValue = Enum.GetValues(typeof(eRat)).Cast<eRat>()
        .Where(e => (int)e > (int)value).OrderBy(e => e).First();

嘿,用Linq作为你的锤子,这个世界充满了钉子;-p


你真的需要概括这个问题吗?你能改成这样吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void SomeMethod(MyEnum myEnum)
{
    MyEnum? nextMyEnum = myEnum.Next();

    if (nextMyEnum.HasValue)
    {
        ...
    }
}

public static MyEnum? Next(this MyEnum myEnum)
{
    switch (myEnum)
    {
        case MyEnum.A:
            return MyEnum.B;
        case MyEnum.B:
            return MyEnum.C;
        case MyEnum.C:
            return MyEnum.D;
        default:
            return null;
    }
}


你要处理的问题是,你试图让一个枚举做一些它不应该做的事情。它们应该是类型安全的。允许将整数值赋给枚举,以便可以组合它们,但如果希望它们表示整数值,请使用类或结构。这里有一个可能的选择:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static class eRat
{
    public static readonly eRatValue A;
    public static readonly eRatValue B;
    public static readonly eRatValue C;
    public static readonly eRatValue D;

    static eRat()
    {
        D = new eRatValue(8, null);
        C = new eRatValue(5, D);
        B = new eRatValue(3, C);
        A = new eRatValue(0, B);
    }

    #region Nested type: ERatValue
    public class eRatValue
    {
        private readonly eRatValue next;
        private readonly int value;

        public eRatValue(int value, eRatValue next)
        {
            this.value = value;
            this.next = next;
        }

        public int Value
        {
            get { return value; }
        }

        public eRatValue Next
        {
            get { return next; }
        }

        public static implicit operator int(eRatValue eRatValue)
        {
            return eRatValue.Value;
        }
    }
    #endregion
}

这允许您执行以下操作:

1
int something = eRat.A + eRat.B;

而这

1
2
3
4
5
6
eRat.eRatValue current = eRat.A;
while (current != null)
{
    Console.WriteLine(current.Value);
    current = current.Next;
}

只有当您可以从它们的类型安全性中获益时,才应该使用枚举。如果您依赖于它们来表示类型,请切换到常量或类。

编辑

我建议你看一下关于枚举设计的msdn页面。第一个最佳实践是:

Do use an enumeration to strongly type
parameters, properties, and return
values that represent sets of values.

我尽量不去争论教条,所以我不会,但这是你要面对的问题。微软不希望你做你想做的事。他们明确要求你不要做你想做的事。使你很难做你想做的事。为了完成您正在尝试做的事情,您必须构建实用程序代码来强制它看起来工作正常。

您不止一次将解决方案称为优雅的,而且如果枚举是以不同的方式设计的,那么可能是这样,但是由于枚举是它们的本质,所以您的解决方案并不优雅。我认为室内乐很优雅,但是如果音乐家没有合适的乐器,不得不用锯片和罐子演奏维瓦尔第,不管他们作为音乐家有多能干,或者音乐在纸上有多好,它都将不再优雅。


因为在"d"之后没有关于返回内容的答案,所以一直到"c"为止。

【更新1】:根据Marc Gravell的建议进行更新。
BR/>【更新2】:根据Husayt的通缉方式进行更新,下一个值"d"返回"a"。
BR/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Next enum of A = {0}", eRatEnumHelper.GetNextEnumValueOf(eRat.A));
        Console.WriteLine("Next enum of B = {0}", eRatEnumHelper.GetNextEnumValueOf(eRat.B));
        Console.WriteLine("Next enum of C = {0}", eRatEnumHelper.GetNextEnumValueOf(eRat.C));
    }
}

public enum eRat { A = 0, B = 3, C = 5, D = 8 };

public class eRatEnumHelper
{
    public static eRat GetNextEnumValueOf(eRat value)
    {
        return (from eRat val in Enum.GetValues(typeof (eRat))
                where val > value
                orderby val
                select val).DefaultIfEmpty().First();
    }
}

结果

Next enum of A = B
Next enum of B = C
Next enum of C = D
Next enum of D = A


从你的描述来看,你并不真的想要一个枚举。您将枚举扩展到其功能之外。为什么不创建一个自定义类,它将您需要的值作为属性公开,同时将它们保存在ordereddictionary中。那么,得到下一个/上一个是微不足道的。——更新

如果您想在基于上下文的集合上以不同的方式枚举,请将其作为设计的显式部分。封装类中的项,并有几个方法,每个方法返回IEnumerable,其中,t是您想要的类型。

例如

1
2
IEnumerable<Foo> GetFoosByBar()
IEnumerable<Foo> GetFoosByBaz()

等。。。


您是否被一些您无法控制的东西锁定在使用枚举中?

如果你没有,我建议你用另一种方法,可能是Dictionary rat;

如果您创建一个Dictionary,并用数据填充它,那么枚举它就比较简单。另外,这是一个更清晰的意图映射——您使用这个枚举将数字映射到字符串,并试图利用这个映射。

如果您必须使用枚举,我建议您使用其他方法:

1
var rats = new List<eRat>() {eRat.A, eRat.B, eRat.C, eRat.D};

只要按顺序添加值并保持同步,就可以大大简化检索下一个ERA的操作。


您可以简化它并将其概括为:

1
2
3
4
5
6
7
8
9
static Enum GetNextValue(Enum e){
    Array all = Enum.GetValues(e.GetType());
    int i = Array.IndexOf(all, e);
    if(i < 0)
        throw new InvalidEnumArgumentException();
    if(i == all.Length - 1)
        throw new ArgumentException("No more values","e");
    return (Enum)all.GetValue(i + 1);
}

编辑:请注意,如果枚举包含重复值(同义项),那么给定这些值之一,此方法(或此处列出的任何其他技术)将失败。例如:

1
2
3
4
5
6
7
8
9
10
11
enum BRUSHSTYLE{
    SOLID         = 0,
    HOLLOW        = 1,
    NULL          = 1,
    HATCHED       = 2,
    PATTERN       = 3,
    DIBPATTERN    = 5,
    DIBPATTERNPT  = 6,
    PATTERN8X8    = 7,
    DIBPATTERN8X8 = 8
}

考虑到BRUSHSTYLE.NULLBRUSHSTYLE.HOLLOW,返回值为BRUSHSTYLE.HOLLOW

Update: a generics version:

1
2
3
4
5
6
7
8
9
10
static T GetNextValue<T>(T e)
{
  T[] all = (T[]) Enum.GetValues(typeof(T));
  int i = Array.IndexOf(all, e);
  if (i < 0)
    throw new InvalidEnumArgumentException();
  if (i == all.Length - 1)
    throw new ArgumentException("No more values","e");
  return all[i + 1];
}

@勒皮:

通用版本允许意外传递非枚举值,该值只在运行时捕获。我最初把它写成了一个泛型,但是当编译器拒绝了where T : Enum时,我把它取出来,意识到无论如何我从泛型中获得的并不是很多。唯一的缺点是必须将结果强制转换回特定的枚举类型。


对于简单的解决方案,您可以从枚举中提取数组。

1
eRat[] list = (eRat[])Enum.GetValues(typeof(eRat));

然后你可以列举

1
2
foreach (eRat item in list)
    //Do something

或查找下一项

1
2
int index = Array.IndexOf<eRat>(list, eRat.B);
eRat nextItem = list[index + 1];

每次需要下一个值时,存储数组比从枚举中提取要好。

但是如果你想要更漂亮的解决方案,那就创建这个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class EnumEnumerator<T> : IEnumerator<T>, IEnumerable<T> {
    int _index;
    T[] _list;

    public EnumEnumerator() {
        if (!typeof(T).IsEnum)
            throw new NotSupportedException();
        _list = (T[])Enum.GetValues(typeof(T));
    }
    public T Current {
        get { return _list[_index]; }
    }
    public bool MoveNext() {
        if (_index + 1 >= _list.Length)
            return false;
        _index++;
        return true;
    }
    public bool MovePrevious() {
        if (_index <= 0)
            return false;
        _index--;
        return true;
    }
    public bool Seek(T item) {
        int i = Array.IndexOf<T>(_list, item);
        if (i >= 0) {
            _index = i;
            return true;
        } else
            return false;
    }
    public void Reset() {
        _index = 0;
    }
    public IEnumerator<T> GetEnumerator() {
        return ((IEnumerable<T>)_list).GetEnumerator();
    }
    void IDisposable.Dispose() { }
    object System.Collections.IEnumerator.Current {
        get { return Current; }
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return _list.GetEnumerator();
    }
}

实例化

1
var eRatEnum = new EnumEnumerator<eRat>();

迭代

1
2
foreach (eRat item in eRatEnum)
    //Do something

移到下一行

1
2
3
eRatEnum.Seek(eRat.B);
eRatEnum.MoveNext();
eRat nextItem = eRatEnum.Current;


希望我的这部分代码能帮助您:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum EGroupedBy
{
    Type,
    InterfaceAndType,
    Alpha,
    _max
}

private void _btnViewUnit_Click(object sender, EventArgs e)
{
    int i = (int)GroupedBy;

    i = (i + 1) % (int)EGroupedBy._max;

    GroupedBy = (EGroupedBy) i;

    RefreshUnit();
}


我可以想到两件事:

  • ERAT B+ 3
  • 枚举分析(typeof(((int)erat.b)+3)


我用这个,非常适合我的。

16


旧职位,但我有另一个解决办法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Next with looping    
public static Enum Next(this Enum input)
{
    Array Arr = Enum.GetValues(input.GetType());
    int j = Array.IndexOf(Arr, input) + 1;
    return (Arr.Length == j) ? (Enum)Arr.GetValue(0) : (Enum)Arr.GetValue(j);
}

//Previous with looping
public static Enum Prev(this Enum input)
{
   Array Arr = Enum.GetValues(input.GetType());
   int j = Array.IndexOf(Arr, input) - 1;
   return (j == -1) ? (Enum)Arr.GetValue(Arr.Length -1) : (Enum)Arr.GetValue(j);
}

当你需要使用它的时候,就做一个演员表

1
2
BootstrapThemeEnum theme = BootstrapThemeEnum.Info;
var next = (BootstrapThemeEnum)theme.Next();

我的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum BootstrapThemeEnum
{
    [Description("white")]
    White = 0,
    [Description("default")]
    Default = 1,
    [Description("info")]
    Info = 2,
    [Description("primary")]
    Primary = 3,
    [Description("success")]
    Success = 4,
    [Description("warning")]
    Warning = 5,
    [Description("danger")]
    Danger = 6,
    [Description("inverse")]
    Inverse = 7

}

有一个非常简单的解决方案(如果可以更改整数值),专门设计用于处理数字。你的号码是一个enum不是问题。它仍然是integer(或您指定的任何基础数字类型)。enum只是增加了强制转换需求的复杂性。

假设您的enum定义如下:

1
2
3
4
5
6
7
8
9
10
11
 public enum ItemStatus
    {
        New = 0,
        Draft = 1,
        Received = 2,
        Review = 4,
        Rejected = 8,
        Approved = 16
    }

ItemStatus myStatus = ItemStatus.Draft;

enum上使用位操作。例如:

1
myStatus = (ItemStatus)(((int)myStatus) << 1)

mystatus的结果是:itemstatus.received。

您还可以通过将按位运算符从<<更改为>>,向下返回enum

1
myStatus = (ItemStatus)(((int)myStatus) >> 1)

mystatus的结果是:itemstatus.new。

您应该始终在两个方向上添加代码来测试"越界"情况。

您可以在下面了解关于位操作的更多信息:http://code.tutspus.com/articles/understanding-bitwise-operators--active-11301


var next=(erat)((int)somerat+3);


从评论中,我有很多问题,比如:"为什么你会想用这种方式使用枚举。"既然你们中有这么多人问过,让我给你我的用例,看看你是否同意:

我有一个固定的项目数组int[n]。根据具体情况,我想用不同的方式枚举这个数组。所以我定义:

1
2
3
4
5
6
7
8
9
10
11
int[] Arr= {1,2,34,5,6,78,9,90,30};
enum eRat1 { A = 0, B=3, C=5, D=8 };
enum eRat2 { A, AA,AAA,B,BB,C,C,CC,D };

void walk(Type enumType)
{
   foreach (Type t in Enum.GetValues(enumType))
   {
      write(t.ToString() +" =" + Arr[(int)t)];
   }
}

打电话给walk(typeof(eRAt1))walk(typeof(eRAt2))

然后我得到所需的输出

1)行走(类型1)

1
2
3
4
A = 1
B = 5
C = 78
D = 30

2)江户十一〔3〕

1
2
3
4
5
6
7
8
A = 1
AA = 2
AAA = 34
B = 5
BB = 6
C = 78
CC = 90
D = 30

这是非常简单的。但我希望,这可以解释。这还有一些其他的好处,比如拥有Enum.ToString()。所以基本上我使用枚举作为索引器。

所以使用这个解决方案,我现在可以做类似的事情。

按照顺序,ERAT1,B的下一个值是C,但在ERAT2中,它是BB。因此,根据我感兴趣的序列,我可以做e.next,根据EnumType,我将得到c或bb。用字典怎么能做到这一点?

我认为这是一种相当优雅的枚举用法。


我用不同的枚举做了类似的事情。这是一个游戏,玩家有机会切换颜色。

1
2
3
4
5
6
7
8
9
public enum PlayerColor {
    Red = 0, Green, Blue, Cyan, Yellow, Orange, Purple, Magenta
}

public PlayerColor GetNextFreeColor(PlayerColor oldColor) {

    PlayerColor newColor = (PlayerColor)((int)(oldColor + 1) % 8);
    return newColor;
}

这个解决方案对我有效。


我在这里用这个:

1
2
3
4
5
public MyEnum getNext() {
    return this.ordinal() < MyEnum.values().length - 1 ?
                            MyEnum.values()[this.ordinal() + 1] :
                            MyEnum.values()[0];
}


我同意Sung Meister的回答,但还有一个选择:

1
2
3
4
5
6
7
8
9
10
MyEnum initial = MyEnum.B, next;

for (int i = ((int) initial) + 1, i < int.MaxValue; i++)
{
  if (Enum.IsDefined(typeof(MyEnum), (MyEnum) i))
  {
     next = (MyEnum) i;
     break;
  }
}

注:假设的假设很多:)


Linq解决方案不会在最后一个元素上中断,但在默认情况下再次继续:

1
var nextValue = Enum.GetValues(typeof(EnumT)).Cast<EnumT>().Concat(new[]{default(EnumT)}).SkipWhile(_ => _ != value).Skip(1).First();


我尝试了第一个解决方案,但它对我不起作用。以下是我的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    public  object NextEnumItem(object currentEnumItem)
    {
        if (!currentEnumItem.GetType().IsEnum) throw new
                ArgumentException(String.Format("Argument is not an Enum"));
        Array Arr = Enum.GetValues(currentEnumItem.GetType());
        int j = Array.IndexOf(Arr,currentEnumItem) + 1;
        return (Arr.Length == j) ? currentEnumItem : Arr.GetValue(j);
    }

    public object PreviousEnumItem(object currentEnumItem)
    {
        if (!currentEnumItem.GetType().IsEnum)
            throw new ArgumentException(String.Format("Argument is not an Enum"));
        Array Arr = Enum.GetValues(currentEnumItem.GetType());
        int j = Array.IndexOf(Arr, currentEnumItem) - 1;
        return (j==-1) ? currentEnumItem : Arr.GetValue(j);
    }

对我来说,这似乎是对枚举类的一种滥用——但这可以做到(假设对最后一个值调用next将导致环绕):

1
2
3
4
5
6
7
8
9
10
11
12
public static eRat Next(this eRat target)
{
    var nextValueQuery = Enum.GetValues(typeof(eRat)).Cast<eRat>().SkipWhile(e => e != target).Skip(1);
    if (nextValueQuery.Count() != 0)
    {
        return (eRat)nextValueQuery.First();
    }
    else
    {
        return eRat.A;
    }
}

这将使您在相同的基础上得到先前的值:

1
2
3
4
5
6
7
8
9
10
11
12
public static eRat Previous(this eRat target)
{
    var nextValueQuery = Enum.GetValues(typeof(eRat)).Cast<eRat>().Reverse().SkipWhile(e => e != target).Skip(1);
    if (nextValueQuery.Count() != 0)
    {
        return (eRat)nextValueQuery.First();
    }
    else
    {
        return eRat.D;
    }
}