Здравствуйте жители Хабра.
Managed Extensibility Framework, он же MEF, что бы ни говорили любители навороченных Autofacs и прочих StructureMaps, — это простой и наглядный способ организации композиции в приложении.
И после продолжительного обсуждения с достопочтенным.
Разаз Что касается сильных и слабых сторон MEF, я хотел бы продемонстрировать возможность определять свои собственные области видимости в этом контейнере.
Как вы знаете, в MEF есть только две области видимости — Shared (один экземпляр для всего контейнера) и NonShared (новый экземпляр для каждого запроса на экспорт).
Наверное, один из первых вопросов тех, кто изучает этот контейнер после Unity: «Где попоточная видимостьЭ» Или, для разработчиков служб WCF, «за вызов».
Не вдаваясь в вопрос, зачем это нужно в рамках задач композиции, попытаюсь показать простой пример реализации этих политик видимости в общем виде.
Для тех, кто не хочет читать об этапах создания и технических подробностях, здесь вы можете коснуться кода руками, и здесь - забрать как посылку.
Работает только в MEF 2.0, почему — ниже.
Так.
Попробуем сначала сформулировать задачу в общем виде: «В запросе на экспорт есть некоторый контекст. Во время этого запроса необходимо вернуть один и тот же экземпляр экспорта для одного и того же контекста и разные экземпляры для разных контекстов».
Не знаю, как вы, а я сразу увидел в этом обычный словарь «ключ-значение», где ключом является наш контекст. Разумеется, словарь должен быть один для всего контейнера, поэтому Shared, но это мы установим позже.
Здесь объяснять, наверное, нечего, просто обращу внимание на очевидный факт: каждый раз, когда мы запрашиваем экспорт для заданного контекста, чтобы без необходимости не создавать объект, мы передаем фабричный метод. Но где взять этот фабричный метод? Мы помним, что он должен вернуть полную часть, возможно, за счет собственного импорта.public abstract class AffinityStorage<TExport, TAffinity> where TExport : class { private ConcurrentDictionary<TAffinity,TExport> _partStorage = new ConcurrentDictionary<TAffinity,TExport>(); internal TExport GetOrAdd(TAffinity affinity, Func<TExport> creator) { var t = _partStorage.GetOrAdd(affinity, (a) =>creator()); return t; } internal void RemoveAffinity(TAffinity affinity) { TExport val; _partStorage.TryRemove(affinity, out val); } }
Для этого мы воспользуемся способностью MEF возвращать экземпляр детали в «ленивой» форме.
А для определения контекста и вообще в качестве обертки создадим еще один класс — принимающую политику.
Это в перспективе NonShared, потому что наш ленивый экспорт должен быть новым при каждом запросе (и наше хранилище само определит, создавать его или нет).
public abstract class Policy<TExport, TAffinity> where TExport : class
{
private readonly AffinityStorage<TExport, TAffinity> _storage;
[Import(AllowRecomposition = true, RequiredCreationPolicy = CreationPolicy.NonShared)]
private Lazy<TExport> _lazyPart;
private bool _wasCreated;
private int _wasDisposed;
protected abstract TAffinity GetAffinity();
protected Policy(AffinityStorage<TExport, TAffinity> storage)
{
_storage = storage;
}
private TExport GetExportedValue()
{
_wasCreated = true;
return _storage.GetOrAdd(GetAffinity(), () => _lazyPart.Value);
}
protected void DestroyAffinity(TAffinity affinity)
{
var wasDisposed = Interlocked.CompareExchange(ref _wasDisposed, 1, 0);
if (_wasCreated && wasDisposed == 0)
{
_storage.RemoveAffinity(affinity);
}
}
public static implicit operator TExport(Policy<TExport, TAffinity> threadPolicy)
{
return threadPolicy.GetExportedValue();
}
}
Здесь, как мы видим, есть:
- наш фабричный метод в виде Lazy<TExport>, обязательно NonShared, иначе зачем он нужен
- неявное преобразование политики в целевой тип путем получения экспорта в нашем репозитории
- фактическое определение того, что такое контекст, в форме абстрактного метода, который мы реализуем в потомках
- метод удаления привязки контекста
С того момента, как мы написали наш класс AffinityStorage, стало ясно, что если мы управляем созданием и хранением экземпляров нашего экспорта, то мы также должны управлять их очисткой.
Вопрос очистки самих экземпляров очень болезненный для IoC-контейнеров в целом; короче, проблема в том, что контейнер не может просто взять и Dispose созданный им экспорт, потому что он не знает, где находится этот экспорт и как он используется после создания.
Поэтому задача очистки деталей ложится на пользователя.
В нашем случае, не задумываясь о том, как используются сами части, мы очистим наше контекстно-привязанное хранилище в момент устранения контекста.
А момент устранения контекста, опять же, пусть определяется окончательной реализацией политики.
Давайте, наконец, сделаем эту окончательную реализацию — для потока.
[Export(typeof(ThreadStorage<>))]
[PartCreationPolicy(CreationPolicy.Shared)]
internal sealed class ThreadStorage<TExport> : AffinityStorage<TExport, Thread> where T : class
{
}
[Export(typeof (ThreadPolicy<>))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public sealed class ThreadPolicy<TExport> : Policy<TExport, Thread> where T : class
{
protected override Thread GetAffinity()
{
return Thread.CurrentThread;
}
[ImportingConstructor]
internal ThreadPolicy(
[Import(RequiredCreationPolicy = CreationPolicy.Shared)]
ThreadStorage<TExport> storage)
: base(storage)
{
}
}
Это будет работать только в MEF 2.0, который поддерживает общедоступные универсальные типы в качестве экспорта.
Из-за особенностей настройки контрактов для каждой политики вам придется создавать частично закрытый по типу контекста класс хранилища и экспортировать его напрямую.
Например, это работает так (все, что мы создали, мы, конечно же, должны поместить в контейнер): TestPart part = _container.GetExportedValue<ThreadPolicy<TestPart>>();
или вот так: [Export]
class Outer {
Inner _perThreadObject;
[ImportingConstructor]
Outer([Import]ThreadPolicy <Inner> perThreadObject)
{
_perThreadObject = perThreadObject;
}
}
То, что осталось за кадром, чтобы не усложнять, находится в git:
- Реализация транзакции и контекста WCF одинакова: TAffinity — это Transaction.Current и OperationContext.Current.
- Если контекст пришел в виде default(TAffinity), то будем считать, что нам нужно отдать обычный экспорт NonShared
- Перехват создания объекта — если мы создаем часть, возможно, нам придется что-то с ней сделать — например, для транзакций я проверяю, является ли часть ресурсом транзакции (ISinglePhaseNotification или IEnlistmentNotification) и присоединяю ее к транзакции как изменчивый ресурс, если это.
- Уничтожение Affinity. При упомянутой выше инициализации потока я создаю поток, который выполняет DestroyAffinity после завершения контекстного потока.
Для транзакции/операции я просто привязываюсь к событию завершения транзакции/операции.
NET #.
NET #C++
-
Роуфут-Ходжкин, Дороти
19 Oct, 24 -
Uwp – Выпуск 253
19 Oct, 24 -
Проблемы С Dkim В Классе Phpmailer
19 Oct, 24 -
Обрыв Оптической Магистрали "Ростелекома"
19 Oct, 24