Как стать автором
Обновить

Имитация свойств для элементов перечислений (Enumerations) в .NET Framework 3.5

Время на прочтение4 мин
Количество просмотров1.3K
Уверен, многие периодически сталкивались с необходимостью указания каких-либо простых свойств для элементов перечислений в C#.

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

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

Атрибуты предоставляют эффективный метод связывания декларативной информации с кодом C# (типы, методы, свойства и т. д.). Атрибут, связанный с сущностью программы, может быть запрошен во время выполнения с помощью метода, называемого отражением.
© MSDN, http://msdn.microsoft.com/ru-ru/library/z0w1kczw.aspx

Рефлексию (отражение) используют для динамического создания экземпляра типа, привязки типа к существующему объекту, а также получения типа из существующего объекта и динамического вызова его методов или доступа к его полям и свойствам.
© MSDN, http://msdn.microsoft.com/ru-ru/library/ms173183.aspx

Методы расширения позволяют «добавлять» методы в существующие типы без создания нового производного типа, перекомпиляции или иного изменения исходного типа. Методы расширения являются особым видом статического метода, но они вызываются, как если бы они были методами экземпляра в расширенном типе.
© MSDN, http://msdn.microsoft.com/ru-ru/library/bb383977.aspx


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

public enum Womans
 {
  [Age(25)]
  [Weight(54.5)]
  Masha,

  [Age(32)]
  [Weight(102.5)]
  Lena,

  [Age(44)]
  [Weight(77.4)]
  Ira,

  [Age(28)]
  [Weight(63.75)]
  Fekla
 }

 public enum Status
 {
  [RussianName("Открыт")]
  Opened = 100,

  [RussianName("Закрыт")]
  Closed = 200,

  [RussianName("Что-то еще")]
  AnythingElse = 500
 }

… и насколько просто мы можем получать значения этих элементов:

double iraWeight = Womans.Ira.GetWeight();
int lenaAge = Womans.Lena.GetAge();
string closedName = Status.Closed.GetRussianName();


Реализация



Создадим абстрактный класс BaseAttribute, от которого в дальнейшем будут наследоваться наши собственные атрибуты. Конструктор принимает и размещает объект на хранение. Метод GetValue() возвращает хранимый объект.

public abstract class BaseAttribute : Attribute
{
 private readonly object _value;
 public BaseAttribute(object value) { this._value = value; }

 public object GetValue() { return this._value; }
}

Далее нам необходимо создать метод расширения (extension method), который для элемента любого перечисления будет возвращать значение необходимого атрибута. Для этого создадим статический класс EnumAttributesBaseLogic и определим в нем наш метод, который назовем GetAttributeValue. Последний аргумент метода — значение, которое будет возвращено, если переданный элемент перечисления не отмечен переданным атрибутом.

public static class EnumAttributesBaseLogic
{
 public static VAL GetAttributeValue<ENUM, VAL>(this ENUM enumItem, Type attributeType, VAL defaultValue)
 {
  var attribute = enumItem.GetType().GetField(enumItem.ToString()).GetCustomAttributes(attributeType, true)
   .Where(a => a is BaseAttribute)
   .Select(a => (BaseAttribute)a)
   .FirstOrDefault();

  return attribute == null ? defaultValue : (VAL)attribute.GetValue();
 }
}

На этом всё! Теперь мы можем создавать необходимые нам атрибуты всего в одну строчку. Взгляните, как просто создать атрибут Вес*:

public class Weight : BaseAttribute { public Weight(double value) : base(value) { } }

* Помните, что в качестве аргументов атрибута можно использовать только константы или массивы примитивных типов.

Также просто мы можем создать теперь и метод расширения для чтения значения только что созданного нами атрибута:

public static double GetWeight(this Enum enumItem)
{
 return enumItem.GetAttributeValue(typeof(Weight), 0m);
}

Для удобства, я рекоммендую вынести определения собственных атрибутов и статический класс с расширенными методами для чтения атрибутов в отдельный файл:

using System;

namespace EnumAttributesDemo
{
 public class Age : BaseAttribute { public Age(int value) : base(value) { } }
 public class Weight : BaseAttribute { public Weight(double value) : base(value) { } }
 public class RussianName : BaseAttribute { public RussianName(string value) : base(value) { } }

 public static class EnumExtensionMethods
 {
  public static int GetAge(this Womans enumItem)
  {
   return enumItem.GetAttributeValue(typeof(Age), 0);
  }

  public static double GetWeight(this Womans enumItem)
  {
   return enumItem.GetAttributeValue(typeof(Weight), 0d);
  }

  public static string GetRussianName(this Status enumItem)
  {
   return enumItem.GetAttributeValue(typeof(RussianName), string.Empty);
  }
 }
}

Рабочий пример можно скачать отсюда:
http://www.googman.ru/sources/enumattributesdemo.zip (9,1 кб)

P.S. Не судите строго — мой первый пост.
Теги:
Хабы:
Всего голосов 15: ↑9 и ↓6+3
Комментарии22

Публикации