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

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

Чулан
Уверен, многие периодически сталкивались с необходимостью указания каких-либо простых свойств для элементов перечислений в 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. Не судите строго — мой первый пост.
Теги:.net 3.5cenumerationsатрибутыattributesрефлексияотражениеreflectionметоды расширенияextension methods
Хабы: Чулан
Всего голосов 15: ↑9 и ↓6+3
Просмотры315

Похожие публикации

Лучшие публикации за сутки