/// <summary>
        ///     Initializes a new instance of the <see cref="InnerJoinReadOnlyObservableCollection{TOuter, TInner, TKey, TResult}"/> class.
        /// </summary>
        /// <param name="outerSource">The outer source.</param>
        /// <param name="innerSource">The inner source.</param>
        /// <param name="outerKeySelector">The outer key selector.</param>
        /// <param name="innerKeySelector">The inner key selector.</param>
        /// <param name="resultSelector">The result selector.</param>
        /// <param name="keyComparer">The key comparer.</param>
        /// <param name="outerComparer">The comparer of outer items.</param>
        /// <param name="innerComparer">The comparer of inner items.</param>
        /// <exception cref="ArgumentNullException">
        /// outerSource
        /// or
        /// innerSource
        /// or
        /// outerKeySelector
        /// or
        /// innerKeySelector
        /// or
        /// resultSelector
        /// </exception>
        public InnerJoinReadOnlyObservableCollection(
            IEnumerable <TOuter> outerSource, IEnumerable <TInner> innerSource,
            Func <TOuter, IObservable <TKey> > outerKeySelector, Func <TInner, IObservable <TKey> > innerKeySelector,
            Func <TOuter, TInner, IObservable <TResult> > resultSelector, IEqualityComparer <TKey> keyComparer = null,
            IEqualityComparer <TOuter> outerComparer = null, IEqualityComparer <TInner> innerComparer = null)
        {
            _outerSource      = outerSource ?? throw new ArgumentNullException(nameof(outerSource));
            _innerSource      = innerSource ?? throw new ArgumentNullException(nameof(innerSource));
            _outerKeySelector = outerKeySelector ?? throw new ArgumentNullException(nameof(outerKeySelector));
            _innerKeySelector = innerKeySelector ?? throw new ArgumentNullException(nameof(innerKeySelector));
            _resultSelector   = resultSelector ?? throw new ArgumentNullException(nameof(resultSelector));
            _keyComparer      = keyComparer ?? EqualityComparer <TKey> .Default;
            _innerComparer    = new KeyValuePairEqualityComparer <TInner>(innerComparer);
            _outerComparer    = new KeyValuePairEqualityComparer <TOuter>(outerComparer);

            _outerItems = new ObservableDictionary <TKey, ObservableItemContainer <TOuter, TKey> >(
                new Dictionary <TKey, ObservableItemContainer <TOuter, TKey> >(_keyComparer));
            _innerItems = new ObservableDictionary <TKey, ObservableItemContainer <TInner, TKey> >(
                new Dictionary <TKey, ObservableItemContainer <TInner, TKey> >(_keyComparer));
            _mergedItems = new ObservableDictionary <TKey, ObservableItemContainer <Tuple <TOuter, TInner>, TResult> >(
                new Dictionary <TKey, ObservableItemContainer <Tuple <TOuter, TInner>, TResult> >(_keyComparer));

            foreach (var item in _outerSource)
            {
                _outerItems.Add(CreateContainer(item, _outerKeySelector));
            }
            foreach (var item in _innerSource)
            {
                _innerItems.Add(CreateContainer(item, _innerKeySelector));
            }
            var merge = _outerItems.Values.Join(_innerItems.Values,
                                                outer => outer.Value, inner => inner.Value, Tuple.Create);

            foreach (var pair in merge)
            {
                _mergedItems.Add(pair.Item1.Value, Merge(pair.Item1.Item, pair.Item2.Item));
            }

            _outerItems.CollectionChanged         += OuterItemsOnCollectionChanged;
            _innerItems.CollectionChanged         += InnerItemsOnCollectionChanged;
            _mergedItems.Values.CollectionChanged += MergedItemsOnCollectionChanged;
            _mergedItems.Values.PropertyChanged   += (sender, args) => OnPropertyChanged(args);

            if (_innerSource is INotifyCollectionChanged innerNotifier)
            {
                CollectionChangedEventManager.AddListener(innerNotifier, this);
            }
            if (_outerSource is INotifyCollectionChanged outerNotifier)
            {
                CollectionChangedEventManager.AddListener(outerNotifier, this);
            }
        }
		public void GetHashCodeTest()
		{
			var testSamples = new Dictionary<String, String>
			{
				{
					"a0", "abc00"
				},
				{
					"b1", "abc12"
				},
				{
					"c2", "abc13"
				},
				{
					"d3", "abc14"
				}
			};

			var comparer = new KeyValuePairEqualityComparer<String, String>(StringComparer.Ordinal, StringComparer.OrdinalIgnoreCase);

			foreach (var testSample in testSamples)
			{
				{
					Assert.AreEqual(testSample.GetHashCode(), comparer.GetHashCode(testSample));
				}
			}
		}