Привязываем к объекту любые данные с помощью словаря свойств-расширений
Наверняка, многие из вас любят писать разные методы-расширения. Хочу с вами поделиться одним из таких методов, возможно кому-то он покажется удобным.
Проблема
Иногда в процессе работы встает необходимость удобно и быстро привязать к объекту какие-то вспомогательные данные, а строго типизированный C# не позволяет нам этого сделать.
Ниже я приведу свое решение данной проблемы.
Вкратце, его суть в том, что к объекту прикрепляется словарь свойств расширений.
Для начала посмотрите, как решение можно использовать.
Решение
//создаем самый простой объект var obj = new object(); //расширяем объект свойством haha типа String obj.Extension("haha", "хехе"); //расширяем объект свойством Haha типа DateTime (имена регистрозависимы) obj.Extension("Haha", DateTime.Now); //теперь получаем наши значения из свойств расширений Console.WriteLine(obj.Extension("haha")); Console.WriteLine(obj.Extension<DateTime>("Haha").Year); //результат: //хехе //2010
По-моему, довольно удобно. Описание работы и исходники ниже.
Как оно работает
Свойства-расширения хранятся во вспомогательном словаре, ключом в котором является слабая ссылка на объект, а значением является как раз словарь свойств-расширений этого объекта (имя свойства-значение). Благодаря слабым ссылкам, мы уверены в том, что наш объект не зависнет в памяти и будет нормально уничтожен сборщиком мусора. Однако, после уничтожения самого объекта, нам нужно удалить его свойства-расширения из вспомогательного словаря. За это будет отвечать специальный таймер.
//вспомогательный словарь свойств-расширений private static readonly Dictionary<WeakReference, Dictionary<string, object>> _extensionMembers; //таймер, убивающий словари-расширения мертвых объектов private static readonly Timer _cleaner; static ObjectExtensions() { _extensionMembers = new Dictionary<WeakReference, Dictionary<string, object>>(); _cleaner = new Timer((o) => { //будем убирать мусор каждые 10 секунд foreach (var key in _extensionMembers.Keys.Where(k => !k.IsAlive).ToArray()) { _extensionMembers.Remove(key); } }, null, 0, 10000); }
Далее привожу методы создания и удаления словаря свойств-расширений для объекта:
/// <summary> /// метод создания/получения существующего словаря свойств-расширений объекта /// </summary> private static Dictionary<string, object> Extensions(this object targetObject, bool createIfNotExist) { //нет объекта - расширять нечего if (targetObject == null) return null; lock (targetObject) { //получаем ключ из вспомогательного словаря var weakKey = _extensionMembers.Keys.FirstOrDefault(w => object.ReferenceEquals(w.Target, targetObject)); if (weakKey != null) { //ключ найден, возвращаем словарь return _extensionMembers[weakKey]; } else if (createIfNotExist) { //создаем новый ключ и словарь для объекта weakKey = new WeakReference(targetObject, false); var members = new Dictionary<string, object>(StringComparer.Ordinal); _extensionMembers[weakKey] = members; return members; } } return null; } /// <summary> /// метод удаления словаря свойств-расширений объекта /// </summary> public static void ClearExtensions(this object targetObject) { //нет объекта - удалять нечего if (targetObject == null) return; lock (targetObject) { //получаем ключ из вспомогательного словаря var weakKey = _extensionMembers.Keys.FirstOrDefault(w => object.ReferenceEquals(w.Target, targetObject)); if (weakKey != null) { //ключ найден, удаляем словарь _extensionMembers.Remove(weakKey); } } }
А теперь, собственно, основные методы для установки и получения значений свойств:
/// <summary> /// Установить значение свойства-расширения /// </summary> public static void Extension(this object targetObject, string key, object value) { if (targetObject == null || String.IsNullOrEmpty(key)) return; //получаем словарь свойств-расширений, либо создаем новый var extensions = targetObject.Extensions(true); //устанавливаем значение, либо удаляем его, если value == null if (value != null) { extensions[key] = value; } else { lock(targetObject) { extensions.Remove(key); } } } /// <summary> /// Получить значение свойства-расширения /// </summary> public static T Extension<T>(this object targetObject, string key) { if (targetObject != null && !String.IsNullOrEmpty(key)) { //получаем словарь свойств-расширений, новый не создаем var extensions = targetObject.Extensions(false); //если есть словарь if (extensions != null) { //если есть свойство нужное в словаре if (extensions.ContainsKey(key)) { lock(targetObject) { if (extensions.ContainsKey(key)) { //и это свойство того типа, который мы ожидаем var value = extensions[key]; if (value is T) //возвращаем значение return (T)extensions[key]; } } } } } return default(T); }
Применение
Надеюсь, что данный пример борьбы со строгой типизацией будет понят правильно и не использован во зло. Стоит учитывать, что производительность решения целиком зависит от класса Dictionary и количества элементов в нем.
Я применяю его, например, для написания других методов-расширений, когда необходимо закешировать результат их выполнения. Возможно, вы найдете другие разумные сценарии применения данного подхода.
Исходный код можно скачать здесь