Click here to Skip to main content
15,898,010 members
Articles / Desktop Programming / WPF

RuntimeExtensionManagement

Rate me:
Please Sign up or sign in to vote.
5.00/5 (8 votes)
5 Nov 2012CPOL35 min read 20.6K   265   15  
Extend your objects at run-time and create really loosely-coupled applications.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Pfz.SerializationManagement.Binary.ItemSerializers;
using Pfz.SerializationManagement.Binary.Searchers;
using Pfz.Caching;

namespace Pfz.SerializationManagement.Binary
{
    /// <summary>
    /// This class is responsible for configuring the valid BinaryItemSerializers
    /// for the BinarySerializer class, be it in a global level or in a local level.
    /// </summary>
    public sealed class BinarySerializationManager:
        IDisposable
    {
        private static readonly object _lock = new object();
        private static Dictionary<Type, IBinaryItemSerializer> _staticItemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
        private static readonly Dictionary<Type, IBinaryItemSerializer> _staticManualItemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
        private static readonly WeakHashSet<IBinarySerializationChangesObserver> _firstLevelChangesObservers = new WeakHashSet<IBinarySerializationChangesObserver>();

        /// <summary>
        /// Registers all the built-in binary serializers.
        /// If you called the Pfz.DefaultConfiguration.Register() you don't need to call this method.
        /// </summary>
        static BinarySerializationManager()
        {
            GlobalRegisterSearcher(AssemblySerializerSearcher.Instance);
            GlobalRegisterSearcher(DictionarySerializerSearcher.Instance);
            GlobalRegisterSearcher(DoubleSerializerSearcher.Instance);
            GlobalRegisterSearcher(EnumSerializerSearcher.Instance);
            GlobalRegisterSearcher(EventInfoSerializerSearcher.Instance);
            GlobalRegisterSearcher(MethodInfoSerializerSearcher.Instance);
            GlobalRegisterSearcher(PairSerializerSearcher.Instance);
            GlobalRegisterSearcher(PropertyInfoSerializerSearcher.Instance);
            GlobalRegisterSearcher(ReferenceTypeArraySerializerSearcher.Instance);
            GlobalRegisterSearcher(SerializableInterfaceSerializerSearcher.Instance);
            GlobalRegisterSearcher(SerializableSerializerSearcher.Instance);
            GlobalRegisterSearcher(TypeSerializerSearcher.Instance);
            GlobalRegisterSearcher(ValueTypeArraySerializerSearcher.Instance);

            GlobalRegister(BooleanArraySerializer.Instance);
            GlobalRegister(BooleanSerializer.Instance);
            GlobalRegister(ByteArraySerializer.Instance);
            GlobalRegister(ByteSerializer.Instance);
            GlobalRegister(DateTimeSerializer.Instance);
            GlobalRegister(DBNullSerializer.Instance);
            GlobalRegister(Int32Serializer.Instance);
            GlobalRegister(Int64Serializer.Instance);
            GlobalRegister(StringSerializer.Instance);
        }

        /// <summary>
        /// Registers an item serializer that will be available to all threads by default.
        /// </summary>
        public static void GlobalRegister(IBinaryItemSerializer itemSerializer)
        {
            if (itemSerializer == null)
                throw new ArgumentNullException("itemSerializer");

            var forType = itemSerializer.ForType;
            if (forType == null)
                throw new ArgumentException("itemSerializer.ForType is null.", "itemSerializer");

            lock (_lock)
            {
                _staticItemSerializers[forType] = itemSerializer;
                _staticManualItemSerializers[forType] = itemSerializer;
            }

            _GlobalNotifyChange();
        }

        internal static readonly HashSet<Type> _globalDefaultDataTypes = new HashSet<Type>();
        /// <summary>
        /// This method registers that a given data-type is considered "default", so an
        /// initial definition of its existence is not needed in the generated binary
        /// serializations.
        /// Note that registering a new default data-type or global data-type breaks
        /// compatibility with previous serializations and should only be
        /// used if reduced size is more important than future compatibility.
        /// </summary>
        public static void GlobalRegisterDefaultDataType(Type dataType)
        {
            if (dataType == null)
                throw new ArgumentNullException("dataType");

            lock (_lock)
                _globalDefaultDataTypes.Add(dataType);
        }

        /// <summary>
        /// Registers the given ItemSerializer and also makes it a default data-type.
        /// </summary>
        public static void GlobalRegisterDefaultItemSerializer(IBinaryItemSerializer itemSerializer)
        {
            GlobalRegister(itemSerializer);
            GlobalRegisterDefaultDataType(itemSerializer.ForType);
        }

        private static IBinaryItemSerializer _TryGetGlobal(Type dataType, BinaryItemSerializerSearchArgs args)
        {
            lock (_lock)
            {
                {
                    IBinaryItemSerializer result;
                    if (_staticItemSerializers.TryGetValue(dataType, out result))
                        return result;

                    if (_staticManualItemSerializers.TryGetValue(dataType, out result))
                    {
                        _staticItemSerializers.Add(dataType, result);
                        return result;
                    }
                }

                var searchers = _GetOrderedSearchers(_globalSearchers, ref _orderedGlobalSearchers);
                if (searchers.Length > 0)
                {
                    args.ExecutingManager = null;
                    foreach (var searcher in searchers)
                    {
                        searcher.Search(args);

                        if (args._itemSerializerSet)
                        {
                            if (!args._usedLocalData)
                                _staticItemSerializers.Add(dataType, args._itemSerializer);

                            return args._itemSerializer;
                        }
                    }
                }
            }

            return null;
        }

        private static readonly Dictionary<IBinaryItemSerializerSearcher, int> _globalSearchers = new Dictionary<IBinaryItemSerializerSearcher, int>();
        private static IBinaryItemSerializerSearcher[] _orderedGlobalSearchers;

        private static IBinaryItemSerializerSearcher[] _GetOrderedSearchers(Dictionary<IBinaryItemSerializerSearcher, int> searchers, ref IBinaryItemSerializerSearcher[] orderedSearchers)
        {
            var result = orderedSearchers;

            if (result == null)
            {
                var query =
                    from
                        pair
                    in
                        searchers
                    orderby
                        pair.Value descending
                    select
                        pair.Key;

                result = query.ToArray();
                orderedSearchers = result;
            }

            return result;
        }

        /// <summary>
        /// Registers a binary item serializer searcher that is valid for all threads, using
        /// its default priority.
        /// </summary>
        public static void GlobalRegisterSearcher(IBinaryItemSerializerSearcher searcher)
        {
            if (searcher == null)
                throw new ArgumentNullException("searcher");

            int priority = searcher.DefaultPriority;
            _GlobalRegisterSearcher(searcher, priority);
        }

        /// <summary>
        /// Registers a binary item serializer searcher that is valid for all threads, using
        /// the given priority.
        /// </summary>
        public static void GlobalRegisterSearcher(IBinaryItemSerializerSearcher searcher, int priority)
        {
            if (searcher == null)
                throw new ArgumentNullException("searcher");

            _GlobalRegisterSearcher(searcher, priority);
        }
        private static void _GlobalRegisterSearcher(IBinaryItemSerializerSearcher searcher, int priority)
        {
            lock (_lock)
            {
                _globalSearchers[searcher] = priority;
                _orderedGlobalSearchers = null;

                _GlobalClearNonManualRegistrations();
            }

            _GlobalNotifyChange();
        }

        private static void _GlobalClearNonManualRegistrations()
        {
            _staticItemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
        }
        /// <summary>
        /// Notifies that a global change has happened.
        /// You should call this if, for some reason, a searcher may now generate
        /// different results but you didn't actually register or unregister a searcher.
        /// </summary>
        public static void GlobalNotifyChange()
        {
            lock (_lock)
                _GlobalClearNonManualRegistrations();

            _GlobalNotifyChange();
        }

        private static void _GlobalNotifyChange()
        {
            /*var globalObservers = _allChangesObservers.ToList();
            foreach (var observer in globalObservers)
                observer.Changed(null);*/

            var observers = _firstLevelChangesObservers.ToList();
            foreach (var observer in observers)
                observer.Changed();
        }


        private sealed class _ParentChangesObserver:
            IBinarySerializationChangesObserver
        {
            private readonly BinarySerializationManager _manager;
            internal _ParentChangesObserver(BinarySerializationManager manager)
            {
                _manager = manager;
            }

            public void Changed()
            {
                _manager._mustInvalidate = true;
                _manager._NotifyChange();
            }
        }
        private readonly _ParentChangesObserver _parentChangesObserver;
        private readonly Dictionary<Type, IBinaryItemSerializer> _itemSerializers = new Dictionary<Type, IBinaryItemSerializer>();
        private readonly Dictionary<Type, IBinaryItemSerializer> _manualItemSerializers = new Dictionary<Type, IBinaryItemSerializer>();

        /// <summary>
        /// Creates a new Manager based on the actual local instance manager.
        /// Note that this instance is not immediately made the thread default one
        /// (you must use the UseAsLocalInstance method to do that).
        /// </summary>
        public BinarySerializationManager()
        {
            _parent = GetLocalInstance();
            _parentChangesObserver = new _ParentChangesObserver(this);
            _parent.RegisterChangesObserver(_parentChangesObserver);
        }

        /// <summary>
        /// Creates a new instance based on the given parent.
        /// Note that giving a parent of null is different than calling the parameterless
        /// constructor. To get the same result you should use the GetLocalInstance() instance
        /// as the parent.
        /// So, with a null parent, if it is not isolated it will be tied to the global
        /// configuration. It it is isolated, then it starts empty.
        /// </summary>
        public BinarySerializationManager(BinarySerializationManager parent, bool isIsolated = false)
        {
            if (parent != null && isIsolated)
                throw new ArgumentException("A BinarySerializationManager can't have a parent and be isolated.");

            _parent = parent;
            _isIsolated = isIsolated;

            if (parent != null)
            {
                _parentChangesObserver = new _ParentChangesObserver(this);
                parent.RegisterChangesObserver(_parentChangesObserver);
            }
            else
            if (!isIsolated)
            {
                _parentChangesObserver = new _ParentChangesObserver(this);
                _firstLevelChangesObservers.Add(_parentChangesObserver);
            }
        }

        /// <summary>
        /// Stops this serialization manager from generating notifications.
        /// </summary>
        public void Dispose()
        {
            if (_parent != null)
                _parent.UnregisterChangesObserver(_parentChangesObserver);
            else
            if (!_isIsolated)
                _firstLevelChangesObservers.Remove(_parentChangesObserver);
        }

        private readonly BinarySerializationManager _parent;
        /// <summary>
        /// Gets the parent of this manager.
        /// For items not configured in this manager, a request to the parent (if any)
        /// is done.
        /// </summary>
        public BinarySerializationManager Parent
        {
            get
            {
                return _parent;
            }
        }

        private readonly bool _isIsolated;
        /// <summary>
        /// Gets a value indicating if this manager is isolated.
        /// By default, even when the managers don't have a parent, they are tied to the
        /// global configuration. When they are isolated, they ignore even those global
        /// configurations.
        /// </summary>
        public bool IsIsolated
        {
            get
            {
                return _isIsolated;
            }
        }

        [ThreadStatic]
        internal static BinarySerializationManager _localInstance;
        /// <summary>
        /// Gets the thread-specific BinarySerializationManager.
        /// With it you may ask for a display name or register thread specific ones.
        /// </summary>
        public static BinarySerializationManager GetLocalInstance()
        {
            var result = _localInstance;

            if (result == null)
            {
                result = new BinarySerializationManager(null);
                _localInstance = result;
            }

            return result;
        }

        /// <summary>
        /// Uses this manager as the default local instance.
        /// Call it with a using clause, so the old one is restored at the end.
        /// </summary>
        public UseBinarySerializationManagerAsLocalInstance UseAsLocalInstance()
        {
            var oldInstance = GetLocalInstance();
            return new UseBinarySerializationManagerAsLocalInstance(this, oldInstance);
        }

        /// <summary>
        /// Registers an itemSerializer that is valid locally.
        /// </summary>
        public void Register(IBinaryItemSerializer itemSerializer)
        {
            if (itemSerializer == null)
                throw new ArgumentNullException("itemSerializer");

            var forType = itemSerializer.ForType;
            if (forType == null)
                throw new ArgumentException("itemSerializer.ForType is null.", "itemSerializer");

            _itemSerializers[forType] = itemSerializer;
            _manualItemSerializers[forType] = itemSerializer;

            NotifyChange();
        }

        internal HashSet<Type> _defaultDataTypes = new HashSet<Type>();
        /// <summary>
        /// This method registers that a given data-type is considered "default", so an
        /// initial definition of its existence is not needed in the generated binary
        /// serializations.
        /// Note that registering a new default data-type or global data-type breaks
        /// compatibility with previous serializations and should only be
        /// used if reduced size is more important than future compatibility.
        /// </summary>
        public void RegisterDefaultDataType(Type dataType)
        {
            if (dataType == null)
                throw new ArgumentNullException("dataType");

            _defaultDataTypes.Add(dataType);
        }

        /// <summary>
        /// Registers the given itemSerializer and also makes it
        /// a default one.
        /// </summary>
        public void RegisterDefaultItemSerializer(IBinaryItemSerializer itemSerializer)
        {
            Register(itemSerializer);
            RegisterDefaultDataType(itemSerializer.ForType);
        }

        private bool _mustInvalidate;
        internal IBinaryItemSerializer _TryGet(BinarySerializer sender, Type dataType, BinaryItemSerializerSearchArgs args)
        {
            if (_mustInvalidate)
            {
                _mustInvalidate = false;
                _itemSerializers.Clear();
            }

            {
                IBinaryItemSerializer result;
                if (_itemSerializers.TryGetValue(dataType, out result))
                    return result;

                if (_manualItemSerializers.TryGetValue(dataType, out result))
                {
                    _itemSerializers[dataType] = result;
                    return result;
                }
            }

            if (args == null)
                args = new BinaryItemSerializerSearchArgs(sender, dataType);

            var searchers = _GetOrderedSearchers(_searchers, ref _orderedSearchers);
            if (searchers.Length > 0)
            {
                args.ExecutingManager = this;

                foreach (var searcher in searchers)
                {
                    searcher.Search(args);

                    if (args._itemSerializerSet)
                        break;
                }
            }

            if (!args._itemSerializerSet)
            {
                if (_isIsolated)
                    return null;

                if (_parent != null)
                    args._itemSerializer = _parent._TryGet(sender, dataType, args);
                else
                    args._itemSerializer = _TryGetGlobal(dataType, args);
            }

            if (!args._usedLocalData)
                _itemSerializers.Add(dataType, args._itemSerializer);

            return args._itemSerializer;
        }

        /// <summary>
        /// Creates a new Serializer bound to a given stream.
        /// It is your responsability to call the stream.Close or stream.Flush.
        /// Subsequent serializations/deserializations use the already found
        /// serializers by index (like a compression). So, the items serialized
        /// by the same serializer must be deserialized, in order, by the same
        /// deserializer.
        /// </summary>
        public BinarySerializer CreateSerializer(Stream stream, object userContext = null)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            return new BinarySerializer(this, stream, userContext);
        }


        private readonly Dictionary<IBinaryItemSerializerSearcher, int> _searchers = new Dictionary<IBinaryItemSerializerSearcher, int>();
        private IBinaryItemSerializerSearcher[] _orderedSearchers;

        /// <summary>
        /// Registers a binary-item serialization searcher using its default priority.
        /// </summary>
        public void RegisterSearcher(IBinaryItemSerializerSearcher searcher)
        {
            if (searcher == null)
                throw new ArgumentNullException("searcher");

            int priority = searcher.DefaultPriority;
            _RegisterSearcher(searcher, priority);
        }

        /// <summary>
        /// Registers a binary-item serialization searcher using the given priority.
        /// </summary>
        public void RegisterSearcher(IBinaryItemSerializerSearcher searcher, int priority)
        {
            if (searcher == null)
                throw new ArgumentNullException("searcher");

            _RegisterSearcher(searcher, priority);
        }

        private void _RegisterSearcher(IBinaryItemSerializerSearcher searcher, int priority)
        {
            _searchers[searcher] = priority;
            _orderedSearchers = null;

            _mustInvalidate = true;
            NotifyChange();
        }


        private readonly WeakHashSet<IBinarySerializationChangesObserver> _changesObservers = new WeakHashSet<IBinarySerializationChangesObserver>();
        /// <summary>
        /// Registers an observer to be notified of changes to this
        /// BinarySerializationManager or any of its parent.
        /// Note 1: that changes made to global configuration generate the
        /// notification on the thread that made the change, independent
        /// if this is a local instance of another thread (if it can be affected,
        /// it will be notified).
        /// Note 2: This is a weak registration and, so, if there are no other
        /// references to the registered observer, it will be collected at
        /// the next collection, even if that's not what you want.
        /// </summary>
        public void RegisterChangesObserver(IBinarySerializationChangesObserver observer)
        {
            if (observer == null)
                throw new ArgumentNullException("observer");

            _changesObservers.Add(observer);
        }

        /// <summary>
        /// Unregisters a previously registered change observer.
        /// </summary>
        public void UnregisterChangesObserver(IBinarySerializationChangesObserver observer)
        {
            if (observer == null)
                throw new ArgumentNullException("observer");

            _changesObservers.Remove(observer);
        }

        /// <summary>
        /// Notifies change observer that a change happened.
        /// You should use this method if a searcher can now generate different
        /// results but you are not actually registering or unregistering a searcher.
        /// </summary>
        public void NotifyChange()
        {
            /*var observers = _allChangesObservers.ToList();
            foreach (var observer in observers)
                observer.Changed(this);*/

            _NotifyChange();
        }

        private void _NotifyChange()
        {
            var observers = _changesObservers.ToList();
            foreach (var observer in observers)
                observer.Changed();
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions