view Implab/Components/ServiceLocator.cs @ 238:bdfdba6b645b v2

fixed unpredictable Safe.Dispose behaviour
author cin
date Fri, 01 Dec 2017 01:28:56 +0300
parents 240aa6994018
children
line wrap: on
line source

using System;
using System.Collections.Generic;

namespace Implab.Components {
    /// <summary>
    /// Коллекция сервисов, позволяет регистрировать и получать сервисы.
    /// </summary>
    public class ServiceLocator: Disposable, IServiceLocator, IServiceProvider {
        // запись о сервисе
        class ServiceEntry : IDisposable {
            public object service; // сервис
            public bool shared; // признак того, что сервис НЕ нужно освобождать
            public Func<object> activator; // активатор сервиса при первом обращении
            public Action<object> cleanup; // функция для очистки сервиса
            public List<Type> associated; // ссылки на текущую запись
            public Type origin; // ссылка на оригинальную запись о сервисе

            #region IDisposable implementation

            public void Dispose() {
                if (shared)
                    return;
                if (cleanup != null) {
                    if (service != null)
                        cleanup(service);
                } else
                    Safe.Dispose(service);
            }

            #endregion
        }

        // словарь существующих сервисов
        readonly 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>() {
            object result;
            if (TryGetService(typeof(T), out result))
                return (T)result;
            throw new ApplicationException (String.Format ("{0} doesn't provide {1} service", this, 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) {
            object result;
            if (TryGetService(typeof(T), out result)) {
                service = (T)result;
                return true;
            }
            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) {
            object result;
            if (TryGetService(serviceType, out result))
                return result;
			throw new ApplicationException (String.Format ("{0} doesn't provide {1} service", this, serviceType));
		}

		/// <summary>
		/// Пытается получить требуемый сервис или совместимый с ним.
		/// </summary>
		/// <returns><c>true</c>, если сервис был найден, <c>false</c> в противном случае..</returns>
		/// <param name="serviceType">Тип запрашиваемого сервиса.</param>
		/// <param name="service">Искомый сервис.</param>
		public virtual bool TryGetService(Type serviceType, out object service) {
            Safe.ArgumentNotNull(serviceType, "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) {
					// нет нужного сервиса
					service = null;
					return false;
				}

                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) {
				service = se.service;
				return true;
			}

            // текущая запись является ссылкой
            if (se.origin != null) {
                se.service = GetService(se.origin);
                m_services[serviceType] = se;
				service = se.service;
				return true;
            }

            // текущая запись не является ссылкой и не имеет информации о сервисе
            // она должна сожержать информацию об активации
            if (se.activator != null) {
                se.service = se.activator();

                m_services[serviceType] = se;

				service = se.service;
				return true;
            }

			service = null;
			return false;
        }

        /// <summary>
        /// Регистрирует фабрику для активации сервиса по первому требованию.
        /// </summary>
        /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam>
        /// <param name="activator">Фабрика для создания/получения объекта, предоставляющего сервис.</param>
        /// <param name = "cleanup">Метод для освобождения экземпляра сервиса, будет вызыван при освобождении сервис-локатора.</param>
        /// <remarks>При освобождении сервис-локатора, сервисы полученные в результате активации также будут освобождены.</remarks>
        public void Register<T>(Func<T> activator, Action<T> cleanup) {
            Safe.ArgumentNotNull(activator, "activator");

            AssertNotDisposed();

            Unregister(typeof(T));
            
            var serviceEntry = new ServiceEntry();
            serviceEntry.activator = () => activator();
            if (cleanup != null)
                serviceEntry.cleanup = instance => cleanup((T)instance);
            m_services[typeof(T)] = serviceEntry;
        }

        public void Register<T>(Func<T> activator) {
            Register(activator, null);
        }

        /// <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>
        public void Register<T>(T service, bool shared) {
            Safe.ArgumentNotNull(service, "service");

            AssertNotDisposed();

            Unregister(typeof(T));
            
            m_services[typeof(T)] = new ServiceEntry { service = service, shared = shared };
        }

        public void Unregister(Type serviceType) {
            Safe.ArgumentNotNull(serviceType, "serviceType");

            AssertNotDisposed();
            
            ServiceEntry se;
            if (m_services.TryGetValue(serviceType, out se)) {
                if (se.origin != null) {
                    var pe = m_services[se.origin];
                    pe.associated.Remove(serviceType);
                }
                // освобождаем ресурсы
                se.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)
                    entry.Dispose();

            }
            base.Dispose(disposing);
        }
    }
}