Привязываем к объекту любые данные с помощью словаря свойств-расширений
Наверняка, многие из вас любят писать разные методы-расширения. Хочу с вами поделиться одним из таких методов, возможно кому-то он покажется удобным.
Проблема
Иногда в процессе работы встает необходимость удобно и быстро привязать к объекту какие-то вспомогательные данные, а строго типизированный 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 и количества элементов в нем.
Я применяю его, например, для написания других методов-расширений, когда необходимо закешировать результат их выполнения. Возможно, вы найдете другие разумные сценарии применения данного подхода.
Исходный код можно скачать здесь