English 中文(简体)
如何在C#中获取下一个(或前一个)枚举值
原标题:
  • 时间:2009-03-13 12:52:59
  •  标签:

我有一个枚举类型,它被定义如下:

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

因此,鉴于价值 eRat.B ,我想获得下一个价值 eRat.C

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

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+1 Enum.Next(Erat.B)这样的东西吗?

谢谢 (Xiè xiè)

最佳回答

感谢大家的回答和反馈。我很惊讶地收到了这么多回复。经过查看并使用其中一些想法,我得出了这个最适合我的解决方案:

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];            
    }
}

这种方法的美妙之处在于它简单易用且适用于所有情况。它作为通用的扩展方法实现,您可以在任何枚举上以此方式调用它:

return eRat.B.Next();

注意,我正在使用广义扩展方法,因此在调用时不需要指定类型,只需使用.Next()即可。

问题回答

可能有点过度杀伤,但:

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

或者,如果您想要较大的数字:

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

或者为下一个更大的数值(自己进行排序):

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

嘿,有了 LINQ 这个锤子,世界上就到处都是钉子 ;-p

你真的需要将这个问题概括吗?你能不能试着这样做呢?

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;
    }
}

你处理的问题是因为你试图让枚举做不该做的事情。它们应该是类型安全的。允许将整数值分配给枚举,以便能够组合它们,但如果你想要它们表示整数值,请使用类或结构体。这里有一个可能的替代方案:

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
}

这让你能够这么做:

int something = eRat.A + eRat.B;

这个 (and this) 这个 (zhè gè) (and this) 这个 (zhè gè)

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”。

[update1]: Updated according to Marc Gravell s suggestion.

[update2]: Updated according to how husayt s wanted - return "A" for the next value of "D".

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

谢谢你们所有人的启发和解决方案。

这是我的成果,作为一个延伸。

using System; 
using System.Linq;

public static class Enums
{
    public static T Next<T>(this T v) where T : struct
    {
        return Enum.GetValues(v.GetType()).Cast<T>().Concat(new[] { default(T) }).SkipWhile(e => !v.Equals(e)).Skip(1).First();
    }

    public static T Previous<T>(this T v) where T : struct
    {
        return Enum.GetValues(v.GetType()).Cast<T>().Concat(new[] { default(T) }).Reverse().SkipWhile(e => !v.Equals(e)).Skip(1).First();
    }
}

使用

using System; 
using System.Linq;

public enum Test { F1, F2, F3 }

public class Program
{
    public static void Main()
    {
        Test t = Test.F3;   
        
        Console.WriteLine(t);
        Console.WriteLine(t.Next());
        Console.WriteLine(t.Previous());
        
        Console.WriteLine("
");
        
        t = Test.F1;    
        Console.WriteLine(t);
        Console.WriteLine(t.Next());
        Console.WriteLine(t.Previous());
    }
}

结果:

F3
F1
F2

F1
F2
F3

你是否被某些你无法控制的因素限制在使用枚举类型上?

如果您不是这样做,我建议使用另一种方法,可能是 Dictionary<string, int> rat;

如果您创建了一个字典,并将其填充了您的数据,枚举它会更简单一些。此外,这是一种更清晰的映射意图方式 - 您正在使用此枚举将数字映射到字符串,并尝试利用该映射。

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

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

只要您按顺序添加值并保持同步,就可以大大简化获取下一个eRat的操作。

为了简单的解决方案,您只需从枚举中提取数组。

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

然后你可以列举。

foreach (eRat item in list)
    //Do something

或找下一个项目

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

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

但如果你想要更美观的解决方案,创建这个类。

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 移动到下一个() {
        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();
    }
}

实例化

var eRatEnum = new EnumEnumerator<eRat>();

迭代

foreach (eRat item in eRatEnum)
    //Do something

移动到下一个

eRatEnum.Seek(eRat.B);
eRatEnum.移动到下一个();
eRat nextItem = eRatEnum.Current;

Judging from your description, you don t really want an enum. You re stretching enum beyond its capabilities. Why not create a custom class that exposes the values you need as properties, while keeping them in OrderedDictionary. Then getting a next/previous one would be trivial. --update

If you want to enumerate differently on the collection based in the context, make that explicit part of your design. Encapsulate the items within a class, and have few methods each returning IEnumerable where, T is your desired type.

例如 (lì rú)

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

等等...

你可以将其简化并概括一些:

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);
}

EDIT: Note that if your enum contains duplicate values (synonymous entries), then this (or any other technique listed here) will fail, given one of those values. For instance:

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

莱皮 (lái pí)

更新:一种通用版本:

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];
}

(Note: This is already in Chinese. No translation needed.)

@leppie:

你的通用版本允许意外传递一个非枚举值,这只有在运行时才能捕捉到。我最初写成通用版本,但是当编译器拒绝使用where T : Enum时,我将其删除,并意识到我从通用函数中并没有得到太多的好处。唯一的真正缺点是你必须将结果强制转换回你特定的枚举类型。

希望我的代码中的这一部分对你有所帮助:

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();
}

Old post, but I have an alternative solution

//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);
}

And when you need to use it, just do a cast

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

my enum

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

}

I can think of 2 things:

  • eRat.B+3
  • Enum.Parse(typeof(((int)eRat.B)+3)

var next = (eRat)((int)someRat + 3);

Seems like an abuse of the enum class to me - but this would do it (assuming that calling Next on the last value would cause wrap-around):

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;
    }
}

And this would give you the previous value on the same basis:

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;
    }
}

I m using this, perfect for my.

    //===================================================================================
// NEXT VALUE IN ENUM 
// ex: E_CamModes eNew =  kGlobalsVars.eGetNextValue< E_CamModes >( geCmMode );
public static T eGetNextValue< T >( T eIn ){
    T[] aiAllValues = ( T[] ) Enum.GetValues( typeof( T ));
    int iVal = System.Array.IndexOf( aiAllValues, eIn );
    return aiAllValues[ ( iVal + 1 ) % aiAllValues.Length ];
}

There is a very simple solution (if you can change your integer values) that is specifically designed to work with numbers. The fact that your number is an enum, isn t a problem. It is still the integer (or whatever underlying number type you assign). Enum just adds the complexity of a cast requirement.

Assume your enum is defined like this:

 public enum ItemStatus
    {
        New = 0,
        Draft = 1,
        Received = 2,
        Review = 4,
        Rejected = 8,
        Approved = 16
    }

ItemStatus myStatus = ItemStatus.Draft;

Use bitwise operations on the Enum. For Example:

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

The result is of myStatus is: ItemStatus.Received.

You can also go backwards down the Enum by changing the bitwise operator from << to >>.

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

The result is of myStatus is: ItemStatus.New.

You should always add code to test for an "out of bounds" situation in both directions.

You can understand more about bitwise operations here: http://code.tutsplus.com/articles/understanding-bitwise-operators--active-11301

I would go with Sung Meister s answer but here is an alternative:

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;
  }
}

Note: many assumptions assumed :)

From comments I had many question like: "Why would you ever want to use enum in this way." Since so many of you asked, let me give you my use case and see if you agree then:

I have a fixed array of items int[n]. Depending on the situation I want to enumerate through this array differently. So i defined:

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)]; 
   }
} 

and call walk(typeof(eRAt1)) or walk(typeof(eRAt2))

then i get required output

1) walk(typeof(eRAt1))

A = 1
B = 5
C = 78
D = 30

2) walk(typeof(eRAt2))

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

This is very simplified. But i hope, this explains. There are some other advantages to this, as having enum.toString(). So basically i use enums as indexers.

So using the solution I can do something like this now.

In sequence eRat1 next value to B is C, but in eRat2 it is BB. So depending on which sequence I am interested in, I can do e.next and depending on enumType I will either get C or BB. How would one achieve that with dictionaries?

I think this a rather elegant use of enums.

I m using this here:

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

LINQ solution that does not break on last element but continues at the default again:

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

I tried the first solution but it did not work for me. Below is my solution:

    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);
    }

I did something similar with a different enum. It s for a game and the player has the chance to toggle colors.

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;
}

This solution worked for me.

Based on best answer from Yahya Hussein here is edit of his code for Previous element in Enum

public static class Extensions
{
    public static T Previous<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 (j < 0) ? Arr[Array.Length - 1] : Arr[j];            
    }
}
enum Level
{
    Easy,
    Medium,
    Expert
};

public static void Main()
{
    var difficulty = Level.Easy;
    var level = (int)difficulty;
    Console.WriteLine(difficulty);

    Console.WriteLine(level);
    Console.WriteLine("promote level");
    level++;

    Console.WriteLine(level);
    difficulty = (Level)level;

    Console.WriteLine(difficulty);
}

You can add and remove integers to an enum to obtain the next value. The only problem is that integer operations on the enum will not check the validity of the enum itself, thus could set "invalid" values.

But you can combine the ++enum and the Enum.IsDefined() to obtain a simple way to get next and previous values of your enum. This would be inefficient in your case since the integer values are not continuous, but if you have continuous integers then it works nicely, and one can check when the ++enum is out of range. Check the next example.

 public enum level
    {
        a = 0,
        b = 1,
        c = 2,
        d = 3,
        e = 4
    }

private static void Main(string[] args)
    {
        var levelValue = level.a;
        Console.WriteLine(levelValue);
        ++levelValue;
        Console.WriteLine(levelValue);
        ++levelValue;
        Console.WriteLine(levelValue);
        ++levelValue;
        Console.WriteLine(levelValue);
        ++levelValue;
        Console.WriteLine(levelValue);
        ++levelValue;
        Console.WriteLine(Enum.IsDefined(typeof(Program.level), levelValue));
        Console.WriteLine(levelValue);
        --levelValue;
        Console.WriteLine(levelValue);
        --levelValue;
        Console.WriteLine(levelValue);
        --levelValue;
        Console.WriteLine(levelValue);
        --levelValue;
        Console.WriteLine(levelValue);
        --levelValue;
        Console.WriteLine(Enum.IsDefined(typeof(Program.level), levelValue));
        Console.WriteLine(levelValue);
        --levelValue;
        Console.WriteLine(Enum.IsDefined(typeof(Program.level), levelValue));
        Console.WriteLine(levelValue);
    }

The output for this would be:

a
b
c
d
e
False
5
e
d
c
b
True
a
False
-1




相关问题
热门标签