Как получить следующее (или предыдущее) значение перечисления в C # - PullRequest
47 голосов
/ 13 марта 2009

У меня есть перечисление, которое определяется так:

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)?

Спасибо

Ответы [ 22 ]

56 голосов
/ 13 марта 2009

Спасибо всем за ваши ответы и отзывы. Я был удивлен, получив так много из них. Глядя на них и используя некоторые идеи, я придумал это решение, которое лучше всего подходит для меня:

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().

36 голосов
/ 13 марта 2009

Возможно, немного излишним, но:

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

21 голосов
/ 13 марта 2009

Вам действительно нужно обобщить эту проблему? Вы можете просто сделать это вместо этого?

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;
    }
}
12 голосов
/ 13 марта 2009

Проблема, с которой вы сталкиваетесь, заключается в том, что вы пытаетесь заставить перечисление сделать то, чего не следует делать. Они должны быть безопасными. Присвоение целочисленных значений перечислению разрешено, так что вы можете объединять их, но если вы хотите, чтобы они представляли целочисленные значения, используйте классы или структуры. Вот возможная альтернатива:

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;

и это

eRat.eRatValue current = eRat.A;
while (current != null)
{
    Console.WriteLine(current.Value);
    current = current.Next;
}

Вы действительно должны использовать перечисления только тогда, когда вы можете извлечь выгоду из их безопасности типов. Если вы полагаетесь на то, что они представляют тип, переключитесь на константы или классы.

EDIT

Я бы посоветовал вам взглянуть на страницу MSDN Enumeration Design . Первая лучшая практика:

Использовать перечисление для строгого ввода параметры, свойства и возврат значения, представляющие наборы значений.

Я стараюсь не спорить с догмой, поэтому не буду, но вот проблема, с которой вы столкнетесь. Microsoft не хочет, чтобы вы делали то, что пытались сделать. Они прямо просят вас не делать то, что вы пытаетесь сделать. Вам трудно делать то, что вы пытаетесь сделать. Чтобы выполнить то, что вы пытаетесь сделать, вы должны создать служебный код, чтобы заставить его казаться работающим.

Вы называли свое решение элегантным более одного раза, и это может быть, если перечисления были разработаны по-другому, но поскольку перечисления являются тем, чем они являются, ваше решение не элегантно. Я думаю, что камерная музыка элегантна, но если бы у музыкантов не было надлежащих инструментов, и они должны были играть Вивальди с ножами и кувшинами, это уже не было бы элегантно, независимо от того, насколько они были музыкантами или хороши в музыке. был на бумаге.

10 голосов
/ 13 марта 2009

Работает до "C", так как нет ответа о том, что возвращать после "D".

[update1] : Обновлено в соответствии с предложением Марка Гравелла.

[update2] : обновляется в зависимости от того, как хотел Хусайт - вернуть «A» для следующего значения «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();
    }
}

Результат

Следующее перечисление A = B
Следующее перечисление B = C
Следующее перечисление C = D
Следующее перечисление D = A

4 голосов
/ 13 марта 2009

Судя по вашему описанию, вы на самом деле не хотите перечисление. Вы растягиваете enum вне его возможностей. Почему бы не создать собственный класс, который предоставляет нужные значения в качестве свойств, сохраняя их в OrderedDictionary. Тогда получить следующий / предыдущий будет тривиально. --update

Если вы хотите по-разному перечислить коллекцию в зависимости от контекста, сделайте это явной частью вашего дизайна. Инкапсулируйте элементы в классе, и у вас есть несколько методов, каждый из которых возвращает IEnumerable где, T - желаемый тип.

Например

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

и т.д ...

4 голосов
/ 13 марта 2009

Вы заблокированы использованием enum чем-то, что вы не можете контролировать?

Если нет, я бы предложил использовать альтернативу, вероятно Dictionary<string, int> rat;

Если вы создаете Dictionary и заполняете его своими данными, перечислять его несколько проще. Кроме того, это более четкое отображение намерений - вы сопоставляете числа со строками с помощью этого перечисления и пытаетесь использовать это сопоставление.

Если вы должны использовать enum, я бы предложил что-то еще:

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

Пока вы добавляете значения по порядку и синхронизируете их, вы значительно упрощаете процесс получения следующего eRat.

3 голосов
/ 13 марта 2009

Вы можете упростить и обобщить это:

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 : Обратите внимание, что если ваше перечисление содержит повторяющиеся значения (синонимичные записи), то это ( или любой другой метод, перечисленный здесь ) завершится ошибкой, учитывая одно из этих значений. Например:

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

Если либо BRUSHSTYLE.NULL, либо BRUSHSTYLE.HOLLOW, возвращаемое значение будет BRUSHSTYLE.HOLLOW.

Обновление: универсальная версия:

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

@ leppie :

Ваша универсальная версия позволяет случайно передать не перечисляемое значение, которое будет отслеживаться только во время выполнения. Первоначально я написал его как универсальный, но когда компилятор отклонил where T : Enum, я вынул его и понял, что в любом случае я ничего не получал от обобщений. Единственным реальным недостатком является то, что вы должны привести результат обратно к вашему конкретному типу перечисления.

3 голосов
/ 13 марта 2009

Для простого решения вы можете просто извлечь массив из enum.

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

Хранить массив лучше, чем извлекать из enum каждый раз, когда вам нужно следующее значение.

Но если вы хотите более красивое решение, создайте класс.

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

Instantiate

var eRatEnum = new EnumEnumerator<eRat>();

Iterate

foreach (eRat item in eRatEnum)
    //Do something

MoveNext

eRatEnum.Seek(eRat.B);
eRatEnum.MoveNext();
eRat nextItem = eRatEnum.Current;
2 голосов
/ 17 июня 2011

Надеюсь, эта часть моего кода поможет вам:

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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...