Mercurial > pub > ImplabNet
changeset 40:fe33f4e02ad5
improved tracing
added text listeners (file,console)
author | cin |
---|---|
date | Tue, 15 Apr 2014 17:52:09 +0400 |
parents | 6498078ae368 |
children | 2fc0fbe7d58b |
files | Implab/Component.cs Implab/Diagnostics/ConsoleTraceListener.cs Implab/Diagnostics/EventText.cs Implab/Diagnostics/IEventTextFormatter.cs Implab/Diagnostics/TextFileListener.cs Implab/Diagnostics/TextListenerBase.cs Implab/Diagnostics/TraceContext.cs Implab/Diagnostics/TraceEvent.cs Implab/Diagnostics/TraceLog.cs Implab/Disposable.cs Implab/IServiceLocator.cs Implab/Implab.csproj Implab/Parallels/AsyncPool.cs Implab/Parallels/WorkerPool.cs Implab/ServiceLocator.cs |
diffstat | 15 files changed, 587 insertions(+), 55 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Component.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Implab { + /// <summary> + /// Компоненты являются состовными объектами, имеют детерминированный период жизни, автоматически освобождают ресурсы входящие в них. + /// </summary> + /// <remarks>Компонента управляет временем жизни включенных в нее компонент</remarks> + public class Component: Disposable { + LinkedList<IDisposable> m_components = new LinkedList<IDisposable>(); + + /// <summary> + /// Коллекция компонент, из которых состоит текущая компонента. + /// </summary> + public ICollection<IDisposable> Components { + get { + AssertNotDisposed(); + return m_components; + } + } + + /// <summary> + /// Освобождает компоненты, входящие в состав текущей компоненты. + /// </summary> + /// <param name="disposing">Признак того, что происходит освобождение ресурсов.</param> + protected override void Dispose(bool disposing) { + if (disposing) { + foreach (var item in m_components) + item.Dispose(); + m_components.Clear(); + } + base.Dispose(disposing); + } + } +} \ No newline at end of file
--- a/Implab/Diagnostics/ConsoleTraceListener.cs Tue Apr 15 02:46:53 2014 +0400 +++ b/Implab/Diagnostics/ConsoleTraceListener.cs Tue Apr 15 17:52:09 2014 +0400 @@ -4,29 +4,16 @@ using System.Text; namespace Implab.Diagnostics { - public class ConsoleTraceListener { + public class ConsoleTraceListener: TextListenerBase { static readonly object _consoleLock = new object(); - public void Subscribe() { - LogChannel<TraceEvent>.Default.Events += Default_Events; - } - - public void Unsubscribe() { - LogChannel<TraceEvent>.Default.Events -= Default_Events; - } + protected override void WriteEntry(TraceContext context, EventText text) { + var msg = new StringBuilder(); - void Default_Events(object sender, ValueEventArgs<TraceEvent> e) { - LogEvent((TraceContext)sender, e.Value); - } - - void LogEvent(TraceContext context, TraceEvent evt) { - var msg = new StringBuilder(); - for (int i = 0; i < context.CurrentOperation.Level; i++) + for (int i = 0; i < text.indent; i++) msg.Append(" "); - msg.Append(evt.EventType); - msg.AppendFormat("[{0}]: ",context.ThreadId); - msg.Append(evt.Message); + msg.AppendFormat("[{0}]: {1}", context.ThreadId, text.content); lock (_consoleLock) { Console.ForegroundColor = (ConsoleColor)(context.ThreadId % 15 + 1);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Diagnostics/EventText.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Implab.Diagnostics { + public struct EventText { + public int indent; + + public string content; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Diagnostics/IEventTextFormatter.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Implab.Diagnostics { + public interface IEventTextFormatter<in TEvent> { + EventText Format(TraceContext context, TEvent data); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Diagnostics/TextFileListener.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Implab.Diagnostics { + public class TextFileListener: TextListenerBase { + readonly TextWriter m_textWriter; + + public TextFileListener(string fileName) { + m_textWriter = File.CreateText(fileName); + + m_textWriter.WriteLine("LOG {0}", DateTime.Now); + Register(this); + } + + protected override void WriteEntry(TraceContext context, EventText text) { + var msg = new StringBuilder(); + for (int i = 0; i < text.indent; i++) + msg.Append(" "); + msg.AppendFormat("[{0}]: {1}", context.ThreadId, text.content); + + lock (m_textWriter) { + if (!IsDisposed) { + m_textWriter.WriteLine(msg.ToString()); + m_textWriter.Flush(); + } + } + } + + + protected override void Dispose(bool disposing) { + base.Dispose(disposing); + if (disposing) { + lock (m_textWriter) { + Safe.Dispose(m_textWriter); + } + } + } + + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Diagnostics/TextListenerBase.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Implab.Diagnostics { + public abstract class TextListenerBase : ServiceLocator, IEventTextFormatter<object>, IEventTextFormatter<TraceEvent> { + + readonly Dictionary<object, Action> m_subscriptions = new Dictionary<object, Action>(); + + protected TextListenerBase() { + Register(this); + } + + public void Subscribe(Type eventType) { + if (eventType == null) + throw new ArgumentNullException("eventType"); + GetType().GetMethod("Subscribe", new Type[0]).MakeGenericMethod(eventType).Invoke(this, null); + } + + public void Subscribe<TEvent>() { + Subscribe<TEvent>(LogChannel<TEvent>.Default); + } + + public void Subscribe<TEvent>(LogChannel<TEvent> channel) { + if (channel == null) + throw new ArgumentNullException("channel"); + + lock (m_subscriptions) { + AssertNotDisposed(); + + var formatter = GetService<IEventTextFormatter<TEvent>>(); + + EventHandler<ValueEventArgs<TEvent>> handler = (sender, args) => { + WriteEntry((TraceContext)sender, formatter.Format((TraceContext)sender, args.Value)); + }; + + if (m_subscriptions.ContainsKey(channel)) + return; + + channel.Events += handler; + + Action unsubscribe = () => { + channel.Events -= handler; + }; + + m_subscriptions.Add(channel, unsubscribe); + } + } + + public void Unsubscribe<TEvent>(LogChannel<TEvent> channel) { + if (channel == null) + throw new ArgumentNullException("channel"); + + lock (m_subscriptions) { + Action subscription; + if (m_subscriptions.TryGetValue(channel, out subscription)) { + subscription(); + m_subscriptions.Remove(channel); + } + } + } + + public void UnsubscribeAll() { + lock (m_subscriptions) { + foreach (var subscription in m_subscriptions.Values) + subscription(); + m_subscriptions.Clear(); + } + } + + protected abstract void WriteEntry(TraceContext context, EventText text); + + public EventText Format(TraceContext context, object data) { + return new EventText { + indent = context.CurrentOperation.Level, + content = data.ToString() + }; + } + + public EventText Format(TraceContext context, TraceEvent data) { + var level = context.CurrentOperation.Level; + if (data.EventType == TraceEventType.OperationCompleted || data.EventType == TraceEventType.OperationStarted) + level--; + + return new EventText { + indent = level, + content = data.ToString() + }; + } + + protected override void Dispose(bool disposing) { + if (disposing) { + UnsubscribeAll(); + } + base.Dispose(disposing); + } + } +}
--- a/Implab/Diagnostics/TraceContext.cs Tue Apr 15 02:46:53 2014 +0400 +++ b/Implab/Diagnostics/TraceContext.cs Tue Apr 15 17:52:09 2014 +0400 @@ -6,17 +6,23 @@ using System.Threading.Tasks; namespace Implab.Diagnostics { + /// <summary> + /// Контекст трассировки, привязывается к потоку и содержит в себе информацию о стеке логических операций. + /// </summary> + /// <remarks> + /// Контекст трассировки передается слушателям событий для определения места, где возникло событие. + /// </remarks> public class TraceContext { LogicalOperation m_currentOperation; - readonly LogicalOperation m_traceBound; + readonly LogicalOperation m_bound; readonly int m_threadId; - readonly TraceContext m_parent; - - readonly static object _consoleLock = new object(); [ThreadStatic] static TraceContext _current; + /// <summary> + /// Текущий контекст трассировки для потока, создается астоматически при первом обращении. + /// </summary> public static TraceContext Current { get { if (_current == null) @@ -29,64 +35,124 @@ if (context == null) throw new ArgumentNullException("context"); - m_parent = context; m_currentOperation = context.CurrentOperation; - m_traceBound = context.CurrentOperation; + m_bound = context.CurrentOperation; m_threadId = Thread.CurrentThread.ManagedThreadId; - - LogEvent(TraceEventType.Transfer, "FORK {0}", context.ThreadId); } TraceContext() { m_currentOperation = new LogicalOperation(); - m_traceBound = m_currentOperation; + m_bound = m_currentOperation; m_threadId = Thread.CurrentThread.ManagedThreadId; } + /// <summary> + /// При необходимости копирует состояние контекста трассивровки в текущий поток. + /// </summary> + /// <param name="from">Исходный контекст трассировки, который передается.</param> + /// <remarks> + /// <para> + /// Копирование происходит за счет создания нового контекста трассировки и заполнением его + /// состояния из переданного контекста. При этом копируется стек операций, однако в новом + /// контексте ранее начатые логические операции не могут быть завершены. + /// </para> + /// <para> + /// Если передача состояния состоялась, то вызывается событие трассировки <see cref="TraceEventType.Transfer"/>. + /// </para> + /// </remarks> public static void Transfer(TraceContext from) { - _current = from == null ? new TraceContext() : new TraceContext(from); - } - - public TraceContext ParentContext { - get { - return m_parent; + if (_current == from) + return; + if (from != null) { + var context = new TraceContext(from); + context.LogEvent(TraceEventType.Transfer, "[{0}]-->[{1}]",from.ThreadId, context.ThreadId); + _current = context; + } else { + _current = new TraceContext(); } } + /// <summary> + /// Создает постоянную копию текущего контекста, данную копию можно хранить и использовать для передачи через <see cref="Transfer(TraceContext)"/> + /// </summary> + /// <returns>Копия текущего контекста трассировки или <c>null</c> если таковой не был создан.</returns> + public static TraceContext Snapshot() { + return _current == null ? null : new TraceContext(_current); + } + + /// <summary> + /// Выполняет переданное действие в указанном контексте трассировки, по окончании восстанавливает предыдущий контекст трассировки потока. + /// </summary> + /// <param name="action"></param> + public void Invoke(Action action) { + if (action == null) + throw new ArgumentNullException("action"); + var old = _current; + Transfer(this); + try { + action(); + } finally { + _current = old; + } + } + + /// <summary> + /// Текущая логическая операция. + /// </summary> public LogicalOperation CurrentOperation { get { return m_currentOperation; } } - public LogicalOperation TraceBound { + /// <summary> + /// Операция ниже которой нельзя опускаться в стеке логических операций, т.е. она не может быть завершена в текущем контексте. + /// </summary> + public LogicalOperation BoundOperation { get { - return m_traceBound; + return m_bound; } } + /// <summary> + /// Поток, в котором создан контекст трассировки. + /// </summary> public int ThreadId { get { return m_threadId; } } + /// <summary> + /// Начинает безымянную логическую операцию. + /// </summary> public void StartLogicalOperation() { StartLogicalOperation(null); } + /// <summary> + /// Начинает логическую операцию с указанным именем. Созданная операция будет добвалена в стек логических операций контекста, затем будет создано соответсвующее событие. + /// </summary> + /// <param name="name">Имя начинаемой операции.</param> public void StartLogicalOperation(string name) { - LogEvent(TraceEventType.OperationStarted, "{0}", name); m_currentOperation = new LogicalOperation(name, m_currentOperation); + LogEvent(TraceEventType.OperationStarted, name); } + /// <summary> + /// Заканчивает логическую операцию начатую в текущем контексте. Операции, начатые в других контекстах не могут быть закончены в текущем контексте. + /// </summary> + /// <remarks> + /// При вызове данного метода создается событие журнала трассировки, либо о завершении операции, либо об ошибки, поскольку данная операция + /// начата в другом контексте. + /// </remarks> public void EndLogicalOperation() { - if (m_traceBound == m_currentOperation) { + if (m_bound == m_currentOperation) { LogEvent(TraceEventType.Error, "Trying to end the operation which isn't belongs to current trace"); } else { var op = m_currentOperation; + LogEvent(TraceEventType.OperationCompleted, "{0} {1} ms", op.Name, op.Duration); m_currentOperation = m_currentOperation.Parent; - LogEvent(TraceEventType.OperationCompleted, "{0} {1} ms", op.Name, op.Duration); } }
--- a/Implab/Diagnostics/TraceEvent.cs Tue Apr 15 02:46:53 2014 +0400 +++ b/Implab/Diagnostics/TraceEvent.cs Tue Apr 15 17:52:09 2014 +0400 @@ -20,6 +20,10 @@ Message = message; } + public override string ToString() { + return String.Format("{0}: {1}", EventType, Message); + } + public static TraceEvent Create(TraceEventType type, string format, params object[] args) { return new TraceEvent(type, String.Format(format, args)); }
--- a/Implab/Diagnostics/TraceLog.cs Tue Apr 15 02:46:53 2014 +0400 +++ b/Implab/Diagnostics/TraceLog.cs Tue Apr 15 17:52:09 2014 +0400 @@ -7,15 +7,12 @@ namespace Implab.Diagnostics { /// <summary> - /// Класс для публикации событий выполнения программы, события публикуются через <see cref="LogChannel{TraceEvent}"/> + /// Класс для публикации событий выполнения программы, события публикуются через <see cref="LogChannel{TraceEvent}"/>. + /// Журнал трассировки отражает логический ход выполнения программы и существует всегда, поскольку тесно связан с + /// контекстом трассировки. /// </summary> public static class TraceLog { [Conditional("TRACE")] - public static void Transfer(TraceContext from) { - TraceContext.Transfer(from); - } - - [Conditional("TRACE")] public static void StartLogicalOperation() { TraceContext.Current.StartLogicalOperation(); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Disposable.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Web; + +namespace Implab { + public class Disposable : IDisposable { + + bool m_disposed; + + public event EventHandler Disposed; + + public bool IsDisposed { + get { return m_disposed; } + } + + protected void AssertNotDisposed() { + if (m_disposed) + throw new ObjectDisposedException(this.ToString()); + } + + protected virtual void Dispose(bool disposing) { + if (disposing && !m_disposed) { + m_disposed = true; + + EventHandler temp = Disposed; + if (temp != null) + temp(this,EventArgs.Empty); + } + } + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void ReportObjectLeaks() { + Trace.TraceWarning("The object is marked as disposable but isn't disposed properly: {0}", this); + } + + ~Disposable() { + Dispose(false); + ReportObjectLeaks(); + } + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/IServiceLocator.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Implab { + public interface IServiceLocator: IServiceProvider { + T GetService<T>(); + bool TryGetService<T>(out T service); + } +}
--- a/Implab/Implab.csproj Tue Apr 15 02:46:53 2014 +0400 +++ b/Implab/Implab.csproj Tue Apr 15 17:52:09 2014 +0400 @@ -32,24 +32,32 @@ <Reference Include="System" /> </ItemGroup> <ItemGroup> + <Compile Include="Component.cs" /> <Compile Include="Diagnostics\ConsoleTraceListener.cs" /> + <Compile Include="Diagnostics\EventText.cs" /> + <Compile Include="Diagnostics\IEventTextFormatter.cs" /> <Compile Include="Diagnostics\LogChannel.cs" /> <Compile Include="Diagnostics\LogicalOperation.cs" /> + <Compile Include="Diagnostics\TextFileListener.cs" /> + <Compile Include="Diagnostics\TextListenerBase.cs" /> <Compile Include="Diagnostics\TraceLog.cs" /> <Compile Include="Diagnostics\TraceContext.cs" /> <Compile Include="Diagnostics\TraceEvent.cs" /> <Compile Include="Diagnostics\TraceEventType.cs" /> + <Compile Include="Disposable.cs" /> <Compile Include="ICancellable.cs" /> <Compile Include="IProgressHandler.cs" /> <Compile Include="IProgressNotifier.cs" /> <Compile Include="IPromise.cs" /> <Compile Include="IPromiseBase.cs" /> + <Compile Include="IServiceLocator.cs" /> <Compile Include="ITaskController.cs" /> <Compile Include="ManagedPromise.cs" /> <Compile Include="Parallels\DispatchPool.cs" /> <Compile Include="Parallels\ArrayTraits.cs" /> <Compile Include="Parallels\MTQueue.cs" /> <Compile Include="Parallels\WorkerPool.cs" /> + <Compile Include="ServiceLocator.cs" /> <Compile Include="TaskController.cs" /> <Compile Include="ProgressInitEventArgs.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
--- a/Implab/Parallels/AsyncPool.cs Tue Apr 15 02:46:53 2014 +0400 +++ b/Implab/Parallels/AsyncPool.cs Tue Apr 15 17:52:09 2014 +0400 @@ -14,10 +14,10 @@ public static Promise<T> Invoke<T>(Func<T> func) { var p = new Promise<T>(); - var caller = TraceContext.Current; + var caller = TraceContext.Snapshot(); ThreadPool.QueueUserWorkItem(param => { - TraceLog.Transfer(caller); + TraceContext.Transfer(caller); try { p.Resolve(func()); } catch(Exception e) { @@ -31,10 +31,10 @@ public static Promise<T> InvokeNewThread<T>(Func<T> func) { var p = new Promise<T>(); - var caller = TraceContext.Current; + var caller = TraceContext.Snapshot(); var worker = new Thread(() => { - TraceLog.Transfer(caller); + TraceContext.Transfer(caller); try { p.Resolve(func()); } catch (Exception e) {
--- a/Implab/Parallels/WorkerPool.cs Tue Apr 15 02:46:53 2014 +0400 +++ b/Implab/Parallels/WorkerPool.cs Tue Apr 15 17:52:09 2014 +0400 @@ -42,15 +42,16 @@ var promise = new Promise<T>(); - var caller = TraceContext.Current; + var caller = TraceContext.Snapshot(); EnqueueTask(delegate() { - TraceLog.Transfer(caller); - try { - promise.Resolve(task()); - } catch (Exception e) { - promise.Reject(e); - } + caller.Invoke(delegate() { + try { + promise.Resolve(task()); + } catch (Exception e) { + promise.Reject(e); + } + }); }); return promise;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/ServiceLocator.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Implab { + /// <summary> + /// Коллекция сервисов, позволяет регистрировать и получать сервисы. + /// </summary> + public class ServiceLocator: Component, IServiceLocator, IServiceProvider { + // запись об сервисе + struct ServiceEntry { + public object service; // сервис + public bool shared; // признак того, что сервис НЕ нужно освобождать + public Func<object> activator; // активатор сервиса при первом обращении + public List<Type> associated; // ссылки на текущую запись + public Type origin; // ссылка на оригинальную запись о сервисе + } + + // словарь существующих сервисов + Dictionary<Type, ServiceEntry> m_services = new Dictionary<Type,ServiceEntry>(); + + /// <summary> + /// Получает объект предоставляющий сервис <typeparamref name="T"/>. + /// </summary> + /// <typeparam name="T">Тип запрашиваемого сервиса</typeparam> + /// <returns>Объект, реализующий сервис</returns> + /// <exception cref="KeyNotFoundException">Сервис не зарегистрирован</exception> + public T GetService<T>() { + return (T)GetService(typeof(T)); + } + + + /// <summary> + /// Пытается получить указанный сервис, в случае, если компонента не предоставляет требуемый сервис + /// не возникает исключений. + /// </summary> + /// <typeparam name="T">Тип требуемого сервиса.</typeparam> + /// <param name="service">Объект реализующий сервис, или <c>default(T)</c> если такового нет.</param> + /// <returns><c>true</c> - сервис найден, <c>false</c> - сервис не зарегистрирован.</returns> + public bool TryGetService<T>(out T service) { + AssertNotDisposed(); + + try { + service = GetService<T>(); + return true; + } catch(KeyNotFoundException) { + service = default(T); + return false; + } + } + + /// <summary> + /// Получает объект предоставляющий сервис <paramref name="serviceType"/> + /// </summary> + /// <param name="serviceType">Тип запрашиваемого сервиса</param> + /// <returns>Объект, реализующий сервис</returns> + /// <exception cref="KeyNotFoundException">Сервис не зарегистрирован</exception> + public object GetService(Type serviceType) { + if (serviceType == null) + throw new ArgumentNullException("serviceType"); + AssertNotDisposed(); + + ServiceEntry se; + if (!m_services.TryGetValue(serviceType, out se)) { + // ищем ближайщий объект, реализующий нужный сервис + Type pt = null; + foreach (var t in m_services.Keys) + if (serviceType.IsAssignableFrom(t) && (pt == null || t.IsAssignableFrom(pt))) + pt = t; + if (pt == null) + throw new ApplicationException(String.Format("{0} doesn't provide {1} service",this,serviceType)); + + var pe = m_services[pt]; + + // найденная запись может ссылаться на оригинальную запись с сервисом + if(pe.origin != null) { + pt = pe.origin; + pe = m_services[pt]; + } + + // добавляем список с обратными ссылками + if (pe.associated == null) + pe.associated = new List<Type>(); + + pe.associated.Add(serviceType); + + // обновляем родительскую запись + m_services[pt] = pe; + + // создаем запись со ссылкой + se = new ServiceEntry { + service = pe.service, + origin = pt, + shared = true // предотвращаем множественные попытки освобождения + }; + + m_services[serviceType] = se; + } + + // запись содержит в себе информацию о сервисе + if (se.service != null) + return se.service; + + // текущая запись является ссылкой + if (se.origin != null) { + se.service = GetService(se.origin); + m_services[serviceType] = se; + return se.service; + } + + // текущая запись не является ссылкой и не имеет информации о сервисе + // она должна сожержать информацию об активации + if (se.activator != null) { + se.service = se.activator(); + + m_services[serviceType] = se; + + return se.service; + } + + throw new Exception("Unable to create a service instance"); + } + + /// <summary> + /// Регистрирует фабрику для активации сервиса по первому требованию. + /// </summary> + /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam> + /// <param name="activator">Фабрика для создания/получения объекта, предоставляющего сервис.</param> + /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception> + /// <remarks>При освобождении сервис-локатора, сервисы полученные в результате активации также будут освобождены.</remarks> + public void Register<T>(Func<T> activator) { + if (activator == null) + throw new ArgumentNullException("activator"); + + AssertNotDisposed(); + + Unregister(typeof(T)); + + m_services[typeof(T)] = new ServiceEntry { + activator = () => activator() as object + }; + } + + /// <summary> + /// Регистрирует объект, предоставляющий сервис. + /// </summary> + /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam> + /// <param name="service">Объект, предоставляющий сервис.</param> + /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception> + /// <remarks>Сервис-локатором не управляет временем жизни объекта для зарегистрированного сервиса.</remarks> + public void Register<T>(T service) { + Register(service, true); + } + + /// <summary> + /// Регистрирует объект, предоставляющий сервис. + /// </summary> + /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam> + /// <param name="service">Объект, предоставляющий сервис.</param> + /// <param name="shared">Признак того, что объект является разделяемым и сервис-локатор не должен его освобождать.</param> + /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception> + public void Register<T>(T service, bool shared) { + if (service == null) + throw new ArgumentNullException("service"); + + AssertNotDisposed(); + + Unregister(typeof(T)); + + m_services[typeof(T)] = new ServiceEntry { service = service, shared = shared }; + } + + public void Unregister(Type serviceType) { + if (serviceType == null) + throw new ArgumentNullException("serviceType"); + + AssertNotDisposed(); + + ServiceEntry se; + if (m_services.TryGetValue(serviceType, out se)) { + // освобождаем ресурсы + if (se.service != null && !se.shared) + ((IDisposable)se.service).Dispose(); + m_services.Remove(serviceType); + + // убираем связанные записи + if (se.associated != null) + foreach (var item in se.associated) + m_services.Remove(item); + } + } + + /// <summary> + /// Освобождает зарегистрированные сервисы (которые требуется освобоить). + /// </summary> + /// <param name="disposing">Призанак того, что нужно освободить ресурсы.</param> + protected override void Dispose(bool disposing) { + if (disposing) { + + foreach (var entry in m_services.Values) + if (!entry.shared && entry.service is IDisposable) + ((IDisposable)entry.service).Dispose(); + + } + base.Dispose(disposing); + } + } +} \ No newline at end of file