2013-03-10 3 views
1

Первоначально я надеялся, что у меня будет какая-то инкапсуляция с перечислениями, но, видимо, это не yet possible with C#. Моя цель состояла в том, чтобы хранить какие-то данные с перечислением. В идеале, перечислить объекты было бы здорово. Но, похоже, нет никакого способа сделать это. Таким образом, я создал класс состояния, публичное перечисление и создал публичный getter/setter с установщиком, который инициализирует метод, в котором я могу заполнять объекты как свойство. Мне интересно, есть ли несколько лучший способ. Идеальное решение для меня было бы установить состояние (перечисление) обычным способом:Лучший способ обработки методов преобразования enum?

car.State = CarStates.Idle; 

, а затем получить доступ к больше данных о том, что состояние, как так:

string message = car.State.Message(); 

Но это будет связано с присоединением имущества к перечню состояний. Есть ли какой-нибудь классный трюк для достижения этого эффекта? Есть перечисления единственный способ сделать переключаемым, одноточечные значения, просто добавив .Idle до конца?

Вот код, который у меня есть сейчас, который хранит информацию состояния на одном уровне, добавляя слой, который является проходимым, но чувствует себя излишним при объявлении чего-то вроде car.Mode.State = CarModeStates.Idle;.

class Car 
{ 
    public CarState Mode; 

    public Car() 
    { 
     Mode = new CarState(); 
    } 
} 

class CarState //holds any metadata for given enum value 
{ 
    private int _EnumInt; 
    private string _Message; 
    private bool _IsError = false; 

    public CarModeStates State 
    { 
     get { return (CarModeStates)_EnumInt; } 
     set { _EnumInt = (int)value; InitializeState(value); } 
    } 
    public string Message { get { return _Message; } } 
    public bool IsError { get { return _IsError; } } 

    public void InitializeState(CarModeStates? cs) 
    { 
     switch (cs) 
     { 
      case (CarModeStates.Off): 
       { 
        _Message = "Car is off."; 
        _IsError = false; 
        break; 
       } 
      case (CarModeStates.Idle): 
       { 
        _Message = "Car is idling."; 
        _IsError = false; 
        break; 
       } 
      case (CarModeStates.Drive): 
       { 
        _Message = "Car is driving."; 
        _IsError = false; 
        break; 
       } 
      case (CarModeStates.Accident): 
       { 
        _Message = "CRASH!"; 
        _IsError = true; 
        break; 
       } 
     } 
    } 
} 

public enum CarModeStates 
{ 
    Off, 
    Idle, 
    Drive, 
    Accident, 
} 

//And from the outside: 
class SomeController 
{ 
    Car car = new Car(); 
    public string GetStateMessage() 
    { 
     car.Mode.State = CarModeStates.Idle; 

     return car.Mode.Message; 
    } 
} 
+0

Я думаю, что вы используете его, нарушая SRP. Сообщение не должно быть связано с перечислением, но должно быть частью некоторой системы локализации. И IsError контекстно-зависим. Он должен быть только на месте, где это отношение требуется. – Euphoric

+0

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

ответ

3

Вы пробовали метод расширения? В статическом классе определите:

public static string Message(this CarStates value) 
{ 
    switch (value) { 
    ... 
    } 
} 
+0

Я не думаю, что вы можете использовать 'this' в статическом методе, правильно? – RyanJMcGowan

+3

@Ryan: Это не «использование» объекта this, это специальный синтаксис с ключевым словом 'this' [называемым методом расширения] (http://msdn.microsoft.com/en-us/library /vstudio/bb383977.aspx). –

+0

ОК, мне нужно больше в этом разобраться. – RyanJMcGowan

2

Регулярные перечисления на C# часто не так проблематичны, как предполагает ваш вопрос. Просто убедитесь, что ваш оператор switch имеет случай default, который выдает ArgumentException, чтобы убедиться, что вы получаете одно из допустимых значений. Вы должны быть осторожны с литеральным значением 0 (который неявно конвертируется в любой тип перечисления), но кроме этого enum в C# действительно обеспечивает существенную безопасность типов на уровне API.

C# перечисления выполняют по существу лучше, чем полностью инкапсулированные перечисления, подобные Java. Есть несколько дополнительных деталей, которые нужно знать (например, значения вне диапазона), но они редко вызывают проблемы на практике.

Классы перечисления Java-стиля легко создавать на C#. Редактировать: за исключением того факта, что вы не можете использовать оператор switch в версии перечисления C# как это.

  1. Оцените класс sealed.
  2. Убедитесь, что класс имеет как минимум 1 явный конструктор, и убедитесь, что все конструкторы private.
  3. Перевести члены, такие как следующие:

Java:

enum Color { RED, GREEN, BLUE } 

C#:

private class Color { 
    public static readonly Color RED = new Color(); 
    public static readonly Color GREEN = new Color(); 
    public static readonly Color BLUE = new Color(); 

    private Color() { } 
} 

Если вы действительно хотите, чтобы имитировать Java, вы можете получить умный, создавая Enum<T> абстрактный базовый класс для перечислений этой формы, который поддерживает свойство Ordinal, а также создает статическое свойство Values, например, Color класс:

public Color[] Values { get { return new[] { RED, GREEN, BLUE }; } } 
+0

Я просто хотел спросить о заявлении коммутатора. Благодарю. – RyanJMcGowan

+0

@RyanJMcGowan Я добавил абзац в начале, чтобы обратиться к оператору 'switch'. –

+0

Я знаю, что могу использовать перечисления в обычном смысле. Я не собираюсь работать, но эффективный и интуитивно понятный способ распространения информации из enum, перейдя в 'EnumName.EnumValue.MoreData'. Мой код достигает этого, но мне пришлось добавить слой. – RyanJMcGowan

0

Метод расширения в сочетании со словарем может быть решением вместо оператора switch.

public static class CarExtensionMethods 
{ 
    public static string Message(this CarStates value) 
    { 
     return carStateDictionary[value]; 
    } 

    private static readonly Dictionary<CarStates, string> carStateDictionary; 

    static CarExtensionMethods() 
    { 
     carStateDictionary = new Dictionary<CarStates, string>(); 

     carStateDictionary.Add(CarStates.Off, "Car is off."); 
     carStateDictionary.Add(CarStates.Idle, "Car is idling."); 
     carStateDictionary.Add(CarStates.Drive, "Car is driving."); 
     carStateDictionary.Add(CarStates.Accident, "CRASH!"); 
    } 
} 

Использование довольно прямо вперед:

CarStates state = CarState.Idle; 
Console.WriteLine(state.Message()); //writes "Car is idling." 

Также как примечание стороны, как правило, enum имена должны только во множественном числе, когда они имеют атрибут [Flags]. Дело в том, что они могут иметь более одного состояния. Более стилистически подходящее имя для вашего перечисления - CarState, поскольку оно не может иметь более одного состояния как время.

+0

-1: Использование словаря здесь добавляет существенные, совершенно ненужные накладные расходы. Вместо этого используйте инструкцию 'switch' в реализации' Message() '. –

+0

Я не согласен с @Sam Harwell. Добавление большего количества состояний в будущем означает, что код в инструкции switch должен быть расширен для покрытия новых состояний. Этот путь будет обрабатывать любые новые состояния, когда они придут, не так ли? – Dib

1

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

Ниже приведен пример работы , но Я лично не буду моделировать свой домен таким образом.

// sample test app 
class Program 
{ 
    static void Main(string[] args) 
    { 
     var carState = CarModeStates.Accident; 

     // the call to get the meta data could and probably should be stored in a local variable 
     Console.WriteLine(carState.GetMetaData().Message); 
     Console.WriteLine(carState.GetMetaData().IsError); 
     Console.WriteLine(carState.GetMetaData().IsUsingPetrol); 
     Console.Read(); 
    } 
} 

Расширенная перечисление Пример

// enum with meta data 
public enum CarModeStates 
{ 
    [CarStatus("Car is off."), IsError(false), IsUsingPetrol(false)] 
    Off, 

    [CarStatus("Car is idling."), IsError(false), IsUsingPetrol(true)] 
    Idle, 

    [CarStatus("Car is driving."), IsError(false), IsUsingPetrol(true)] 
    Drive, 

    [CarStatus("CRASH!"), IsError(true), IsUsingPetrol(false)] 
    Accident 
} 

Пользовательские атрибуты для украшения перечисления

public interface IAttribute<out T> 
{ 
    T Description { get; } 
} 

[AttributeUsage(AttributeTargets.Field)] 
public class CarStatusAttribute : Attribute, IAttribute<string> 
{ 
    private readonly string _value; 

    public CarStatusAttribute(string value) 
    { 
     _value = value; 
    } 

    public string Description 
    { 
     get { return _value; } 
    } 
} 

[AttributeUsage(AttributeTargets.Field)] 
public class IsErrorAttribute : Attribute, IAttribute<bool> 
{ 
    private readonly bool _value; 

    public IsErrorAttribute(bool value) 
    { 
     _value = value; 
    } 

    public bool Description 
    { 
     get { return _value; } 
    } 
} 

[AttributeUsage(AttributeTargets.Field)] 
public class IsUsingPetrolAttribute : Attribute, IAttribute<bool> 
{ 
    private readonly bool _value; 

    public IsUsingPetrolAttribute(bool value) 
    { 
     _value = value; 
    } 

    public bool Description 
    { 
     get { return _value; } 
    } 
} 

метод (ы) расширения, чтобы получить мета данные о перечислении.

public static class CarModeStatesExtensions 
{ 
    public static CarModeStateModel GetMetaData(this CarModeStates value) 
    { 
     var model = new CarModeStateModel 
      { 
       Message = value.GetDescriptionFromEnumValue<string>(typeof (CarStatusAttribute)), 
       IsError = value.GetDescriptionFromEnumValue<bool>(typeof(IsErrorAttribute)), 
       IsUsingPetrol = value.GetDescriptionFromEnumValue<bool>(typeof (IsUsingPetrolAttribute)) 
      }; 

     return model; 
    } 
} 

public class CarModeStateModel 
{ 
    public string Message { get; set; } 
    public bool IsError { get; set; } 
    public bool IsUsingPetrol { get; set; } 
} 

public static class EnumExtensions 
{ 
    public static T GetDescriptionFromEnumValue<T>(this CarModeStates value, Type attributeType) 
    { 
     var attribute = value.GetType() 
      .GetField(value.ToString()) 
      .GetCustomAttributes(attributeType, false).SingleOrDefault(); 

     if (attribute == null) 
     { 
      return default(T); 
     } 

     return ((IAttribute<T>)attribute).Description; 
    } 
} 
+0

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

1

Может быть, вам нужен "завод", как, что:

public class CarState 
{ 
    private string message; 
    private bool error; 

    public CarState(string message, bool error) 
    { 
     this.message = message; 
     this.error = error; 
    } 

    public string Message 
    { 
     get { return this.message; } 
    } 

    public bool Error 
    { 
     get { return this.error; } 
    } 
} 

public static class CarStateFactory 
{ 
    public enum CarStateId { Off, Idle, Driving, Accident } 

    public static CarState GetCarState(CarStateId carStateId) 
    { 
     switch(carStateId) 
     { 
      case (CarStateId.Off): 
       { return new CarState("Car is off", false); } 

      //add more cases 

      default: 
       return null; 
     } 
    } 
} 

При этом вы можете установить состояние Вашего автомобиля по телефону:

car.State = CarStateFactory.GetCarState(CarStateFactory.ID.Off); //ID.Idle, ID.Driving, ID.Accident 

И

Вы можете получить доступ к сообщению с помощью car.State.Message.

EDIT: Статический геттер для CarStateFactory

public static CarState Idle 
{ 
    get 
    { 
     return new CarState("Car is off", false); 
    } 
} 
+0

Это будет тоссой между моим кодом и этим. 'car.Mode.State = CarModeStates.Idle;' выглядит намного красивее. – RyanJMcGowan

+0

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

+0

Я добавил код для статического геттера к моему ответу, чтобы вы могли сделать 'CarStateFactoy.Idle'. Мне лично не нравится, как это выглядит, но вы можете попробовать. Затем вы можете переименовать «CarStateFactory» в любое удобное для вас время. –

0

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

Аннотация

/// <summary> 
/// Defines the expected members of a state 
/// </summary> 
internal interface ICarState 
{ 
    /// <summary> 
    /// Gets or sets the message. 
    /// </summary> 
    /// <value> 
    /// The message. 
    /// </value> 
    string Message { get; } 
} 

Основание состояние

/// <summary> 
/// The class that all car states should inherit from. 
/// </summary> 
internal abstract class CarBaseState : ICarState 
{ 
    #region ICarState Members 

    /// <summary> 
    /// Gets or sets the message. 
    /// </summary> 
    /// <value> 
    /// The message. 
    /// </value> 
    /// </exception> 
    public abstract string Message { get; } 

    #endregion 
} 

Реализация

/// <summary> 
/// Represents the state when the car is off 
/// </summary> 
internal class OffState : CarBaseState 
{ 
    /// <summary> 
    /// Gets or sets the message. 
    /// </summary> 
    /// <value> 
    /// The message. 
    /// </value> 
    /// </exception> 
    public override string Message { get { return "Off"; } } 
} 

/// <summary> 
/// Represents the state when the car is idling 
/// </summary> 
internal class IdleState : CarBaseState 
{ 
    /// <summary> 
    /// Gets or sets the message. 
    /// </summary> 
    /// <value> 
    /// The message. 
    /// </value> 
    /// </exception> 
    public override string Message { get { return "Idling"; } } 
} 

менеджер

internal class CarStateManager 
{ 
    #region Fields 

    Dictionary<string, ICarState> _stateStore = null; 

    #endregion 

    #region Properties 

    /// <summary> 
    /// Gets (or privately sets) the state of the current. 
    /// </summary> 
    /// <value> 
    /// The state of the current. 
    /// </value> 
    internal ICarState CurrentState { get; private set; } 

    #endregion 

    #region Constructors and Initialisation 

    /// <summary> 
    /// Initializes a new instance of the <see cref="StateManager"/> class. 
    /// </summary> 
    public CarStateManager() 
    { 
     _stateStore = new Dictionary<string, ICarState>(); 
    } 

    #endregion 

    #region Methods 

    /// <summary> 
    /// Adds a state. 
    /// </summary> 
    /// <param name="stateId">The state identifier.</param> 
    /// <param name="state">The state.</param> 
    public void AddState(string stateId, ICarState state) 
    { 
     // Add the state to the collection 
     _stateStore.Add(stateId, state); 
    } 

    /// <summary> 
    /// Changes the state. 
    /// </summary> 
    /// <param name="stateId">The state identifier.</param> 
    public void ChangeState(string stateId) 
    { 

     // Set thr current state 
     CurrentState = _stateStore[stateId]; 
    } 

    #endregion 
} 

Программа

class Program 
{ 
    internal class StateKeys 
    { 
     public const string Off = "Off"; 
     public const string Idle = "Idle"; 
    } 

    static void Main(string[] args) 
    { 
     // Instantiate the state manager 
     CarStateManager stateManager = new CarStateManager(); 

     // Add the states 
     stateManager.AddState(StateKeys.Off, new OffState()); 
     stateManager.AddState(StateKeys.Idle, new IdleState()); 

     // Change the state and display the message 
     stateManager.ChangeState(StateKeys.Off); 
     Console.WriteLine(stateManager.CurrentState.Message); 

     // Change the state and display the message 
     stateManager.ChangeState(StateKeys.Idle); 
     Console.WriteLine(stateManager.CurrentState.Message); 
     Console.ReadLine(); 
    } 
} 

Выход:

Off 
Idling 

НТН!

Смежные вопросы