Skip to main content

Synchronized Hashtable that, unlike hashtable created using the native Hashtable.Synchronized method - synchronizes reads from the underlying hashtable in addition to writes.

/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections;
using System.Collections.Specialized;
using Spring.Util;

namespace Spring.Collections
{
    /// <summary>
    /// Synchronized <see cref="Hashtable"/> that, unlike hashtable created
    /// using <see cref="Hashtable.Synchronized"/> method, synchronizes
    /// reads from the underlying hashtable in addition to writes.
    /// </summary>
    /// <remarks>
    /// <p>
    /// In addition to synchronizing reads, this implementation also fixes
    /// IEnumerator/ICollection issue described at
    /// http://msdn.microsoft.com/en-us/netframework/aa570326.aspx
    /// (search for SynchronizedHashtable for issue description), by implementing
    /// <see cref="IEnumerator"/> interface explicitly, and returns thread safe enumerator
    /// implementations as well.
    /// </p>
    /// <p>
    /// This class should be used whenever a truly synchronized <see cref="Hashtable"/>
    /// is needed.
    /// </p>
    /// </remarks>
    /// <author>Aleksandar Seovic</author>
    [Serializable]
    public class SynchronizedHashtable : IDictionary, ICollection, IEnumerable, ICloneable
    {
        private readonly bool _ignoreCase;
        private readonly Hashtable _table;

        /// <summary>
        /// Initializes a new instance of <see cref="SynchronizedHashtable"/>
        /// </summary>
        public SynchronizedHashtable()
            : this(new Hashtable(), false)
        { }

        /// <summary>
        /// Initializes a new instance of <see cref="SynchronizedHashtable"/>
        /// </summary>
        public SynchronizedHashtable(bool ignoreCase)
            : this(new Hashtable(), ignoreCase)
        { }

        /// <summary>
        /// Initializes a new instance of <see cref="SynchronizedHashtable"/>, copying initial entries from <param name="dictionary"/>
        /// handling keys depending on <param name="ignoreCase"/>.
        /// </summary>
        public SynchronizedHashtable(IDictionary dictionary, bool ignoreCase)
        {
            AssertUtils.ArgumentNotNull(dictionary, "dictionary");
            this._table = (ignoreCase) ? CollectionsUtil.CreateCaseInsensitiveHashtable(dictionary) : new Hashtable(dictionary);
            this._ignoreCase = ignoreCase;
        }

        /// <summary>
        /// Creates a <see cref="SynchronizedHashtable"/> instance that
        /// synchronizes access to the underlying <see cref="Hashtable"/>.
        /// </summary>
        /// <param name="other">the hashtable to be synchronized</param>
        protected SynchronizedHashtable(Hashtable other)
        {
            AssertUtils.ArgumentNotNull(other, "other");
            this._table = other;
        }

        /// <summary>
        /// Creates a <see cref="SynchronizedHashtable"/> wrapper that synchronizes
        /// access to the passed <see cref="Hashtable"/>.
        /// </summary>
        /// <param name="other">the hashtable to be synchronized</param>
        public static SynchronizedHashtable Wrap(Hashtable other)
        {
            return new SynchronizedHashtable(other);
        }

        ///<summary>
        ///Gets a value indicating whether the <see cref="T:System.Collections.IDictionary"></see> object is read-only.
        ///</summary>
        ///<returns>
        ///true if the <see cref="T:System.Collections.IDictionary"></see> object is read-only; otherwise, false.
        ///</returns>
        public bool IsReadOnly
        {
            get
            {
                lock (SyncRoot)
                {
                    return _table.IsReadOnly;
                }
            }
        }

        ///<summary>
        ///Gets a value indicating whether the <see cref="T:System.Collections.IDictionary"></see> object has a fixed size.
        ///</summary>
        ///<returns>
        ///true if the <see cref="T:System.Collections.IDictionary"></see> object has a fixed size; otherwise, false.
        ///</returns>
        public bool IsFixedSize
        {
            get
            {
                lock (SyncRoot)
                {
                    return _table.IsFixedSize;
                }
            }
        }

        ///<summary>
        ///Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"></see> is synchronized (thread safe).
        ///</summary>
        ///<returns>
        ///true if access to the <see cref="T:System.Collections.ICollection"></see> is synchronized (thread safe); otherwise, false.
        ///</returns>
        public bool IsSynchronized
        {
            get { return true; }
        }

        ///<summary>
        ///Gets an <see cref="T:System.Collections.ICollection"></see> object containing the keys of the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</summary>
        ///<returns>
        ///An <see cref="T:System.Collections.ICollection"></see> object containing the keys of the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</returns>
        public ICollection Keys
        {
            get
            {
                lock (SyncRoot)
                {
                    return _table.Keys;
                }
            }
        }

        ///<summary>
        ///Gets an <see cref="T:System.Collections.ICollection"></see> object containing the values in the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</summary>
        ///<returns>
        ///An <see cref="T:System.Collections.ICollection"></see> object containing the values in the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</returns>
        public ICollection Values
        {
            get
            {
                lock (SyncRoot)
                {
                    return _table.Values;
                }
            }
        }

        ///<summary>
        ///Gets an object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"></see>.
        ///</summary>
        ///<returns>
        ///An object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"></see>.
        ///</returns>
        public object SyncRoot
        {
            get { return _table.SyncRoot; }
        }

        ///<summary>
        ///Gets the number of elements contained in the <see cref="T:System.Collections.ICollection"></see>.
        ///</summary>
        ///<returns>
        ///The number of elements contained in the <see cref="T:System.Collections.ICollection"></see>.
        ///</returns>
        public int Count
        {
            get
            {
                lock (SyncRoot)
                {
                    return _table.Count;
                }
            }
        }

        ///<summary>
        ///Adds an element with the provided key and value to the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</summary>
        ///<param name="value">The <see cref="T:System.Object"></see> to use as the value of the element to add. </param>
        ///<param name="key">The <see cref="T:System.Object"></see> to use as the key of the element to add. </param>
        ///<exception cref="T:System.ArgumentException">An element with the same key already exists in the <see cref="T:System.Collections.IDictionary"></see> object. </exception>
        ///<exception cref="T:System.ArgumentNullException">key is null. </exception>
        ///<exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IDictionary"></see> is read-only.-or- The <see cref="T:System.Collections.IDictionary"></see> has a fixed size. </exception><filterpriority>2</filterpriority>
        public void Add(object key, object value)
        {
            lock (SyncRoot)
            {
                _table.Add(key, value);
            }
        }

        ///<summary>
        ///Removes all elements from the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</summary>
        ///<exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IDictionary"></see> object is read-only. </exception><filterpriority>2</filterpriority>
        public void Clear()
        {
            lock (SyncRoot)
            {
                _table.Clear();
            }
        }

        ///<summary>
        ///Creates a new object that is a copy of the current instance.
        ///</summary>
        ///<returns>
        ///A new object that is a copy of this instance.
        ///</returns>
        public object Clone()
        {
            lock (SyncRoot)
            {
                return new SynchronizedHashtable(this, _ignoreCase);
            }
        }

        ///<summary>
        ///Determines whether the <see cref="T:System.Collections.IDictionary"></see> object contains an element with the specified key.
        ///</summary>
        ///<returns>
        ///true if the <see cref="T:System.Collections.IDictionary"></see> contains an element with the key; otherwise, false.
        ///</returns>
        ///<param name="key">The key to locate in the <see cref="T:System.Collections.IDictionary"></see> object.</param>
        ///<exception cref="T:System.ArgumentNullException">key is null. </exception><filterpriority>2</filterpriority>
        public bool Contains(object key)
        {
            lock (SyncRoot)
            {
                return _table.Contains(key);
            }
        }

        ///<summary>
        /// Returns, whether this <see cref="IDictionary"/> contains an entry with the specified <paramref name="key"/>.
        ///</summary>
        ///<param name="key">The key to look for</param>
        ///<returns><see lang="true"/>, if this <see cref="IDictionary"/> contains an entry with this <paramref name="key"/></returns>
        public bool ContainsKey(object key)
        {
            lock (SyncRoot)
            {
                return _table.ContainsKey(key);
            }
        }

        ///<summary>
        /// Returns, whether this <see cref="IDictionary"/> contains an entry with the specified <paramref name="value"/>.
        ///</summary>
        ///<param name="value">The value to look for</param>
        ///<returns><see lang="true"/>, if this <see cref="IDictionary"/> contains an entry with this <paramref name="value"/></returns>
        public bool ContainsValue(object value)
        {
            lock (SyncRoot)
            {
                return _table.ContainsValue(value);
            }
        }

        ///<summary>
        ///Copies the elements of the <see cref="T:System.Collections.ICollection"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
        ///</summary>
        ///<param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing. </param>
        ///<param name="index">The zero-based index in array at which copying begins. </param>
        ///<exception cref="T:System.ArgumentNullException">array is null. </exception>
        ///<exception cref="T:System.ArgumentException">The type of the source <see cref="T:System.Collections.ICollection"></see> cannot be cast automatically to the type of the destination array. </exception>
        ///<exception cref="T:System.ArgumentOutOfRangeException">index is less than zero. </exception>
        ///<exception cref="T:System.ArgumentException">array is multidimensional.-or- index is equal to or greater than the length of array.-or- The number of elements in the source <see cref="T:System.Collections.ICollection"></see> is greater than the available space from index to the end of the destination array. </exception><filterpriority>2</filterpriority>
        public void CopyTo(Array array, int index)
        {
            lock (SyncRoot)
            {
                _table.CopyTo(array, index);
            }
        }

        ///<summary>
        ///Returns an <see cref="T:System.Collections.IDictionaryEnumerator"></see> object for the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</summary>
        ///<returns>
        ///An <see cref="T:System.Collections.IDictionaryEnumerator"></see> object for the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</returns>
        public IDictionaryEnumerator GetEnumerator()
        {
            lock (SyncRoot)
            {
                return new SynchronizedDictionaryEnumerator(SyncRoot, _table.GetEnumerator());
            }
        }

        ///<summary>
        ///Removes the element with the specified key from the <see cref="T:System.Collections.IDictionary"></see> object.
        ///</summary>
        ///<param name="key">The key of the element to remove. </param>
        ///<exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.IDictionary"></see> object is read-only.-or- The <see cref="T:System.Collections.IDictionary"></see> has a fixed size. </exception>
        ///<exception cref="T:System.ArgumentNullException">key is null. </exception><filterpriority>2</filterpriority>
        public void Remove(object key)
        {
            lock (SyncRoot)
            {
                _table.Remove(key);
            }
        }

        ///<summary>
        ///Returns an enumerator that iterates through a collection.
        ///</summary>
        ///<returns>
        ///An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
        ///</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            lock (SyncRoot)
            {
                return new SynchronizedEnumerator(SyncRoot, ((IEnumerable)_table).GetEnumerator());
            }
        }

        ///<summary>
        ///Gets or sets the element with the specified key.
        ///</summary>
        ///<returns>
        ///The element with the specified key.
        ///</returns>
        ///<param name="key">The key of the element to get or set. </param>
        ///<exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.IDictionary"></see> object is read-only.-or- The property is set, key does not exist in the collection, and the <see cref="T:System.Collections.IDictionary"></see> has a fixed size. </exception>
        ///<exception cref="T:System.ArgumentNullException">key is null. </exception><filterpriority>2</filterpriority>
        public object this[object key]
        {
            get
            {
                lock (SyncRoot)
                {
                    return _table[key];
                }
            }
            set
            {
                lock (SyncRoot)
                {
                    _table[key] = value;
                }
            }
        }
    }
}

// ----

//
// SynchronizedEnumerator
// source: https://github.com/spring-projects/spring-net/blob/master/src/Spring/Spring.Core/Collections/SynchronizedEnumerator.cs
//

using System.Collections;

namespace Spring.Collections
{
    /// <summary>
    /// Synchronized <see cref="IEnumerator"/> that should be returned by synchronized
    /// collections in order to ensure that the enumeration is thread safe.
    /// </summary>
    /// <author>Aleksandar Seovic</author>
    internal class SynchronizedEnumerator : IEnumerator
    {
        protected object syncRoot;
        protected IEnumerator enumerator;

        public SynchronizedEnumerator(object syncRoot, IEnumerator enumerator)
        {
            this.syncRoot = syncRoot;
            this.enumerator = enumerator;
        }

        public bool MoveNext()
        {
            lock (syncRoot)
            {
                return enumerator.MoveNext();
            }
        }

        public void Reset()
        {
            lock (syncRoot)
            {
                enumerator.Reset();
            }
        }

        public object Current
        {
            get
            {
                lock (syncRoot)
                {
                    return enumerator.Current;
                }
            }
        }
    }
}

// ----

//
// Case Insensitive Hashtable
// source: https://github.com/spring-projects/spring-net/blob/master/src/Spring/Spring.Core/Collections/CaseInsensitiveHashtable.cs
//

using System;
using System.Collections;
using System.Globalization;
using System.Runtime.Serialization;
using Spring.Util;

namespace Spring.Collections
{
    /// <summary>
    /// Provides a performance improved hashtable with case-insensitive (string-only! based) key handling.
    /// </summary>
    /// <author>Erich Eichinger</author>
    [Serializable]
    public class CaseInsensitiveHashtable : Hashtable
    {
        private readonly CultureInfo _culture;

        /// <summary>
        /// Creates a case-insensitive hashtable using <see cref="CultureInfo.CurrentCulture"/>.
        /// </summary>
        public CaseInsensitiveHashtable()
            : this(null, CultureInfo.CurrentCulture)
        {}

        /// <summary>
        /// Creates a case-insensitive hashtable using the given <see cref="CultureInfo"/>.
        /// </summary>
        /// <param name="culture">the <see cref="CultureInfo"/> to calculate the hashcode</param>
        public CaseInsensitiveHashtable(CultureInfo culture)
            : this(null, culture)
        {}

        /// <summary>
        /// Creates a case-insensitive hashtable using the given <see cref="CultureInfo"/>, initially
        /// populated with entries from another dictionary.
        /// </summary>
        /// <param name="d">the dictionary to copy entries from</param>
        /// <param name="culture">the <see cref="CultureInfo"/> to calculate the hashcode</param>
        public CaseInsensitiveHashtable(IDictionary d, CultureInfo culture)
        {
            AssertUtils.ArgumentNotNull(culture, "culture");
            _culture = culture;

            if (d != null)
            {
                IDictionaryEnumerator enumerator = d.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    this.Add(enumerator.Key, enumerator.Value);
                }
            }
        }

        /// <summary>
        /// Initializes a new, empty instance of the <see cref="T:System.Collections.Hashtable"></see> class that is serializable using the specified <see cref="T:System.Runtime.Serialization.SerializationInfo"></see> and <see cref="T:System.Runtime.Serialization.StreamingContext"></see> objects.
        /// </summary>
        /// <param name="context">A <see cref="T:System.Runtime.Serialization.StreamingContext"></see> object containing the source and destination of the serialized stream associated with the <see cref="T:System.Collections.Hashtable"></see>. </param>
        /// <param name="info">A <see cref="T:System.Runtime.Serialization.SerializationInfo"></see> object containing the information required to serialize the <see cref="T:System.Collections.Hashtable"></see> object.</param>
        /// <exception cref="T:System.ArgumentNullException">info is null. </exception>
        protected CaseInsensitiveHashtable(SerializationInfo info, StreamingContext context) : base(info, context)
        {
            string cultureName = info.GetString("_cultureName");
            _culture = new CultureInfo(cultureName);
            AssertUtils.ArgumentNotNull(_culture, "Culture");
        }

        ///<summary>
        ///Implements the <see cref="ISerializable"></see> interface and returns the data needed to serialize the <see cref="CaseInsensitiveHashtable"></see>.
        ///</summary>
        ///
        ///<param name="context">A <see cref="StreamingContext"></see> object containing the source and destination of the serialized stream associated with the <see cref="CaseInsensitiveHashtable"></see>. </param>
        ///<param name="info">A <see cref="SerializationInfo"></see> object containing the information required to serialize the <see cref="CaseInsensitiveHashtable"></see>. </param>
        ///<exception cref="System.ArgumentNullException">info is null. </exception>
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("_cultureName", _culture.Name);
        }

        /// <summary>
        /// Calculate the hashcode of the given string key, using the configured culture.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        protected override int GetHash(object key)
        {
            if (!(key is string)) return key.GetHashCode();
            return _culture.TextInfo.ToLower((string) key).GetHashCode();
        }

        /// <summary>
        /// Compares two keys
        /// </summary>
        protected override bool KeyEquals(object item, object key)
        {
            if (!(key is string))
            {
                return Equals(item,key);
            }
            return 0==_culture.CompareInfo.Compare((string) item, (string) key, CompareOptions.IgnoreCase);
        }

        /// <summary>
        /// Creates a shallow copy of the current instance.
        /// </summary>
        public override object Clone()
        {
            return new CaseInsensitiveHashtable(this, _culture );
        }
    }
}

// ----

using System.Collections;

namespace Spring.Collections
{
    /// <summary>
    /// Synchronized <see cref="IDictionaryEnumerator"/> that should be returned by synchronized
    /// dictionary implementations in order to ensure that the enumeration is thread safe.
    /// </summary>
    /// <author>Aleksandar Seovic</author>
    internal class SynchronizedDictionaryEnumerator : SynchronizedEnumerator, IDictionaryEnumerator
    {
        public SynchronizedDictionaryEnumerator(object syncRoot, IDictionaryEnumerator enumerator)
            : base(syncRoot, enumerator)
        {
        }

        protected IDictionaryEnumerator Enumerator
        {
            get { return (IDictionaryEnumerator) enumerator; }
        }

        public object Key
        {
            get
            {
                lock (syncRoot)
                {
                    return Enumerator.Key;
                }
            }
        }

        public object Value
        {
            get
            {
                lock (syncRoot)
                {
                    return Enumerator.Value;
                }
            }
        }

        public DictionaryEntry Entry
        {
            get
            {
                lock (syncRoot)
                {
                    return Enumerator.Entry;
                }
            }
        }
    }
}

// ----

//
// Unit Tests
// source: https://github.com/spring-projects/spring-net/blob/master/test/Spring/Spring.Core.Tests/Collections/SynchronizedHashtableTests.cs

using System;
using System.Collections;
using NUnit.Framework;

namespace Spring.Collections
{
    [TestFixture]
    public class SynchronizedHashtableTests
    {
        [Test]
        public void BehavesLikeHashtable()
        {
            SynchronizedHashtable st = new SynchronizedHashtable();
            st.Add("key", "value");
            Assert.AreEqual("value", st["key"]);
            st["key"] = "value2";
            Assert.AreEqual("value2", st["key"]);
            st["key2"] = "value3";
            Assert.AreEqual("value3", st["key2"]);

            try
            {
                st.Add("key", "value4");
                Assert.Fail();
            }
            catch(ArgumentException) {}

            Assert.AreEqual(2, st.Count);
        }

        [Test]
        public void InitializeFromOtherCopiesValues()
        {
            Hashtable ht = new Hashtable();
            ht["key"] = "value";
            ht["key2"] = "value2";

            SynchronizedHashtable st = new SynchronizedHashtable(ht, false);
            Assert.AreEqual(2, st.Count);
            ht.Remove("key");
            Assert.AreEqual(1, ht.Count);
            Assert.AreEqual(2, st.Count);
        }

        [Test]
        public void DefaultsToCaseSensitive()
        {
            SynchronizedHashtable st = new SynchronizedHashtable();
            st.Add("key","value");
            st.Add("KEY","value");
            Assert.AreEqual(2, st.Count);
        }

        [Test]
        public void IgnoreCaseIgnoresCase()
        {
            SynchronizedHashtable st = new SynchronizedHashtable(true);
            st.Add("key", "value");
            Assert.AreEqual("value", st["KEY"]);
            st["KeY"] = "value2";
            Assert.AreEqual(1, st.Count);
            Assert.AreEqual("value2", st["key"]);

            try
            {
                st.Add("KEY", "value2");
                Assert.Fail();
            }
            catch (ArgumentException)
            {}

            Hashtable ht = new Hashtable();
            ht.Add("key","value");
            ht.Add("KEY","value");
            try
            {
                st = new SynchronizedHashtable(ht, true);
                Assert.Fail();
            }
            catch (ArgumentException) {}
        }

        [Test]
        public void WrapKeepsOriginalHashtableReference()
        {
            Hashtable ht = new Hashtable();
            ht["key"] = "value";
            ht["key2"] = "value2";

            SynchronizedHashtable st = SynchronizedHashtable.Wrap(ht);
            Assert.AreEqual(2, st.Count);
            ht.Remove("key");
            Assert.AreEqual(1, ht.Count);
            Assert.AreEqual(1, st.Count);
        }

        /// <summary>
        /// On my Notebook gives
        /// Normal Hashtable: 00:00:00.0937500
        /// Synced Hashtable: 00:00:00.9375000
        /// =locking *has* a peformance impact.
        /// </summary>
        [Test, Explicit]
        public void TestLockingPerformanceImpact()
        {
            StopWatch watch = new StopWatch();
            object[] buckets = new object[10];

            int iterations = 10000000;

            IDictionary testDict = new Hashtable();
            buckets[5] = "value";
            object testResult;

            using(watch.Start("Normal Hashtable: {0}"))
            for(int i=0;i<iterations;i++)
            {
                testResult = buckets[6];
                buckets[5] = "value 2";
            }

            testDict = new Hashtable();
            testDict.Add( "key", "value" );
            using(watch.Start("Synced Hashtable: {0}"))
            for(int i=0;i<iterations;i++)
            {
                lock(buckets)
                {
                    testResult = buckets[6];
                }
                lock(buckets)
                {
                    buckets[5] = "value 2";
                }
            }
        }

        /// <summary>
        /// On my Notebook gives
        /// Method returning exception: 00:00:00.0156250
        /// Method throwing exception: 00:00:02.1562500
        /// </summary>
        [Test, Explicit]
        public void TestPeformanceImpactOfThrowingExceptions()
        {
            StopWatch watch = new StopWatch();

            int iterations = 1000000;

            using(watch.Start("Method returning exception: {0}"))
            for(int i=0;i<iterations;i++)
            {
                Exception ex = MethodReturnExceptionInformation();
            }

            using(watch.Start("Method throwing exception: {0}"))
            for(int i=0;i<iterations;i++)
            {
                Exception ex;
                try
                {
                    MethodThrowingException();
                }
                catch (Exception innerEx)
                {
                    ex = innerEx;
                }
            }

        }

        private void MethodThrowingException()
        {
            throw new InvalidOperationException( "da message" );
        }

        private Exception MethodReturnExceptionInformation()
        {
            return new InvalidOperationException( "da message" );
        }
    }
}

// ----

//
// Example Usage
// source: https://github.com/spring-projects/spring-net/blob/master/src/Spring/Spring.Data.NHibernate5/Data/NHibernate/SimpleDelegatingSessionFactory.cs
//

using System.Collections;

using NHibernate;
using NHibernate.Cfg;
using NhCfg = NHibernate.Cfg;

using Spring.Collections;
using Spring.Threading;
using Spring.Data.Common;
using Spring.Context.Support;
using NHibernate.Engine;
using System;

namespace Spring.Data.NHibernate
{
    public class SimpleDelegatingSessionFactory : DelegatingSessionFactory
    {
        /// <summary>
        /// Connection string config element name
        /// </summary>
        public const string CONNECTION_STRING = "SimpleDelegatingSessionFactory.ConnectionString";

        /// <summary>
        /// Cache region prefix config element name
        /// </summary>
        public const string CACHE_REGION_PREFIX_STRING = "SimpleDelegatingSessionFactory.CacheRegionPrefix";

        private Configuration _configuration;

        private string _defaultConnectionString;

        private object _monitor = new object();

        private IDictionary _targetSessionFactories = new SynchronizedHashtable();

        /// <summary>
        /// public Constructor
        /// </summary>
        /// <param name="defaultConfiguration"></param>
        public SimpleDelegatingSessionFactory(Configuration defaultConfiguration)
        {
            if (defaultConfiguration == null)
            {
                throw new ArgumentException("Configuration cannot be null", "defaultConfiguration");
            }

            _configuration = defaultConfiguration;
            if (!_configuration.Properties.ContainsKey(NhCfg.Environment.ConnectionString))
            {
                throw new ArgumentException("Must specify connection string");
            }

            _defaultConnectionString = _configuration.Properties[NhCfg.Environment.ConnectionString] as string;
            if (_defaultConnectionString == null)
            {
                throw new ArgumentException("Connection string property must be of type string, not " +
                        _configuration.Properties[NhCfg.Environment.ConnectionString].GetType().FullName);
            }
        }

        public override ISessionFactory TargetSessionFactory
        {
            get
            {
                string connectionString = LogicalThreadContext.GetData(CONNECTION_STRING) as string;

                System.Diagnostics.Trace.WriteLine(String.Format("{0} = {1}", System.Threading.Thread.CurrentThread.GetHashCode(), connectionString));

                if (connectionString == null)
                {
                    connectionString = _defaultConnectionString;
                }

                lock (_monitor)
                {
                    if (!_targetSessionFactories.Contains(connectionString))
                    {
                        System.Diagnostics.Trace.WriteLine(System.Threading.Thread.CurrentThread.GetHashCode().ToString() + " = (created) ");

                        string originalPrefix = string.Empty;

                        if (_configuration.Properties.ContainsKey(NhCfg.Environment.CacheRegionPrefix))
                        {
                            originalPrefix = _configuration.Properties[NhCfg.Environment.CacheRegionPrefix];
                        }

                        string cacheRegionPrefix = LogicalThreadContext.GetData(CACHE_REGION_PREFIX_STRING) as string;

                        if (!string.IsNullOrEmpty(cacheRegionPrefix))
                        {
                            _configuration.Properties[NhCfg.Environment.CacheRegionPrefix] = originalPrefix + cacheRegionPrefix;
                            System.Diagnostics.Trace.WriteLine(String.Format("{0} = (cache region prefix) {1}", System.Threading.Thread.CurrentThread.GetHashCode(), cacheRegionPrefix));

                        }

                        _configuration.Properties[NhCfg.Environment.ConnectionString] = connectionString;
                        ISessionFactory sessionFactory = _configuration.BuildSessionFactory();

                        LocalSessionFactoryObject.DbProviderWrapper dbProviderWrapper = ((ISessionFactoryImplementor)sessionFactory).ConnectionProvider as LocalSessionFactoryObject.DbProviderWrapper;
                        if (dbProviderWrapper != null)
                        {
                            dbProviderWrapper.DbProvider = (IDbProvider)ContextRegistry.GetContext().GetObject("DbProvider");
                        }

                        //Reset the Cache Region Prefix to the original value
                        // This is so other cache region prefixes are not appended together.
                        _configuration.Properties[NhCfg.Environment.CacheRegionPrefix] = originalPrefix;

                        _targetSessionFactories[connectionString] = sessionFactory;
                    }
                    else
                        System.Diagnostics.Trace.WriteLine(System.Threading.Thread.CurrentThread.GetHashCode().ToString() + " =  (cached) ");

                    ISessionFactory factory = _targetSessionFactories[connectionString] as ISessionFactory;

                    System.Diagnostics.Trace.WriteLine(String.Format("{0} =  {1}", System.Threading.Thread.CurrentThread.GetHashCode(), connectionString));

                    return factory;
                }
            }
        }
    }
}