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