Przeglądaj źródła

Keeping entries in Join and GroupJoin ordered by event arrival.

Bart De Smet 11 lat temu
rodzic
commit
0aaf10dfa9

+ 3 - 3
Rx.NET/Source/Common.targets

@@ -115,7 +115,7 @@
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(BuildTarget)' == 'PLLITE' ">
-    <DefineConstants>$(DefineConstants);NO_RXINTERFACES;NO_SERIALIZABLE;NO_REMOTING;NO_SEMAPHORE;NO_STOPWATCH;NO_CDS;PLIB;PLIB_LITE;NO_THREAD;USE_TASKEX;CRIPPLED_REFLECTION;NO_WEAKREFOFT</DefineConstants>    
+    <DefineConstants>$(DefineConstants);NO_RXINTERFACES;NO_SERIALIZABLE;NO_REMOTING;NO_SEMAPHORE;NO_STOPWATCH;NO_CDS;PLIB;PLIB_LITE;NO_THREAD;USE_TASKEX;CRIPPLED_REFLECTION;NO_WEAKREFOFT;NO_SORTEDDICTIONARY</DefineConstants>    
     <TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
       <!-- 
@@ -174,7 +174,7 @@
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(BuildTarget)' == 'SL5' ">
-    <DefineConstants>$(DefineConstants);NO_RXINTERFACES;USE_SL_DISPATCHER;NO_SERIALIZABLE;NO_REMOTING;NO_SEMAPHORE;NO_STOPWATCH;NO_CDS;NO_TASK_DELAY;NO_WEAKREFOFT</DefineConstants>
+    <DefineConstants>$(DefineConstants);NO_RXINTERFACES;USE_SL_DISPATCHER;NO_SERIALIZABLE;NO_REMOTING;NO_SEMAPHORE;NO_STOPWATCH;NO_CDS;NO_TASK_DELAY;NO_WEAKREFOFT;NO_SORTEDDICTIONARY</DefineConstants>
     <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
     <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
     <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>
@@ -184,7 +184,7 @@
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(BuildTarget)' == 'WP7' ">
-    <DefineConstants>$(DefineConstants);USE_SL_DISPATCHER;NO_SERIALIZABLE;NO_REMOTING;NO_CDS;NO_TLS;NO_VARIANCE;NO_TPL;NO_HASHSET;NO_SEMAPHORE;NO_LARGEARITY;NO_EXPRESSIONVISITOR;NO_LAZY;NO_WEAKTABLE;NO_INTERLOCKED_64;WINDOWSPHONE7;NO_WEAKREFOFT</DefineConstants>
+    <DefineConstants>$(DefineConstants);USE_SL_DISPATCHER;NO_SERIALIZABLE;NO_REMOTING;NO_CDS;NO_TLS;NO_VARIANCE;NO_TPL;NO_HASHSET;NO_SEMAPHORE;NO_LARGEARITY;NO_EXPRESSIONVISITOR;NO_LAZY;NO_WEAKTABLE;NO_INTERLOCKED_64;WINDOWSPHONE7;NO_WEAKREFOFT;NO_SORTEDDICTIONARY</DefineConstants>
     <TargetFrameworkProfile>WindowsPhone71</TargetFrameworkProfile>
     <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>

+ 788 - 0
Rx.NET/Source/System.Reactive.Linq/Reactive/Internal/SortedDictionary.cs

@@ -0,0 +1,788 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
+
+#if NO_SORTEDDICTIONARY
+
+using System;
+using System.Diagnostics;
+    
+//
+// Code ported from SortedDictionary.cs and SortedSet.cs in the BCL.
+// Unused portions have been removed for brevity.
+//
+
+namespace System.Collections.Generic
+{
+    class SortedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
+    {
+        private readonly TreeSet<KeyValuePair<TKey, TValue>> _set;
+
+        public SortedDictionary()
+        {
+            _set = new TreeSet<KeyValuePair<TKey, TValue>>(new KeyValuePairComparer());
+        }
+
+        public void Add(TKey key, TValue value)
+        {
+            _set.Add(new KeyValuePair<TKey, TValue>(key, value));
+        }
+
+        public int Count
+        {
+            get
+            {
+                return _set.Count;
+            }
+        }
+
+        public bool Remove(TKey key)
+        {
+            return _set.Remove(new KeyValuePair<TKey, TValue>(key, default(TValue)));
+        }
+
+        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+        {
+            return new Enumerator(this);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
+        {
+            private readonly TreeSet<KeyValuePair<TKey, TValue>>.Enumerator _treeEnum;
+
+            internal Enumerator(SortedDictionary<TKey, TValue> dictionary)
+            {
+                _treeEnum = dictionary._set.GetEnumerator();
+            }
+
+            public bool MoveNext()
+            {
+                return _treeEnum.MoveNext();
+            }
+
+            public void Dispose()
+            {
+                _treeEnum.Dispose();
+            }
+
+            public KeyValuePair<TKey, TValue> Current
+            {
+                get
+                {
+                    return _treeEnum.Current;
+                }
+            }
+
+            object IEnumerator.Current
+            {
+                get
+                {
+                    return new KeyValuePair<TKey, TValue>(Current.Key, Current.Value);
+                }
+            }
+
+            internal void Reset()
+            {
+                _treeEnum.Reset();
+            }
+
+            void IEnumerator.Reset()
+            {
+                _treeEnum.Reset();
+            }
+        }
+
+        internal class KeyValuePairComparer : Comparer<KeyValuePair<TKey, TValue>>
+        {
+            private readonly IComparer<TKey> _keyComparer = Comparer<TKey>.Default;
+
+            public override int Compare(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
+            {
+                return _keyComparer.Compare(x.Key, y.Key);
+            }
+        }
+
+        internal class TreeSet<T> : SortedSet<T>
+        {
+            public TreeSet(IComparer<T> comparer)
+                : base(comparer)
+            {
+            }
+
+            internal override bool AddIfNotPresent(T item)
+            {
+                var ret = base.AddIfNotPresent(item);
+
+                if (!ret)
+                {
+                    throw new ArgumentException("Duplicate entry added.");
+                }
+
+                return ret;
+            }
+        }
+    }
+
+    class SortedSet<T> : IEnumerable<T>
+    {
+        private readonly IComparer<T> _comparer;
+        private Node _root;
+        private int _count;
+        private int _version;
+
+        public SortedSet(IComparer<T> comparer)
+        {
+            _comparer = comparer;
+        }
+
+        public int Count
+        {
+            get
+            {
+                return _count;
+            }
+        }
+
+        public bool Add(T item)
+        {
+            return AddIfNotPresent(item);
+        }
+
+        internal virtual bool AddIfNotPresent(T item)
+        {
+            if (_root == null)
+            {
+                _root = new Node(item, false);
+                _count = 1;
+                _version++;
+                return true;
+            }
+
+            //
+            // Search for a node at bottom to insert the new node. 
+            // If we can guarantee the node we found is not a 4-node, it would be easy to do insertion.
+            // We split 4-nodes along the search path.
+            // 
+            Node current = _root;
+            Node parent = null;
+            Node grandParent = null;
+            Node greatGrandParent = null;
+
+            //
+            // Even if we don't actually add to the set, we may be altering its structure (by doing rotations
+            // and such). so update version to disable any enumerators/subsets working on it.
+            //
+            _version++;
+
+            int order = 0;
+            while (current != null)
+            {
+                order = _comparer.Compare(item, current.Item);
+                if (order == 0)
+                {
+                    // We could have changed root node to red during the search process.
+                    // We need to set it to black before we return.
+                    _root.IsRed = false;
+                    return false;
+                }
+
+                // split a 4-node into two 2-nodes                
+                if (Is4Node(current))
+                {
+                    Split4Node(current);
+                    // We could have introduced two consecutive red nodes after split. Fix that by rotation.
+                    if (IsRed(parent))
+                    {
+                        InsertionBalance(current, ref parent, grandParent, greatGrandParent);
+                    }
+                }
+
+                greatGrandParent = grandParent;
+                grandParent = parent;
+                parent = current;
+                current = (order < 0) ? current.Left : current.Right;
+            }
+
+            Debug.Assert(parent != null, "Parent node cannot be null here!");
+            
+            var node = new Node(item);
+            if (order > 0)
+            {
+                parent.Right = node;
+            }
+            else
+            {
+                parent.Left = node;
+            }
+
+            //
+            // The new node will be red, so we will need to adjust the colors if parent node is also red.
+            //
+            if (parent.IsRed)
+            {
+                InsertionBalance(node, ref parent, grandParent, greatGrandParent);
+            }
+
+            //
+            // Root node is always black.
+            //
+            _root.IsRed = false;
+            ++_count;
+            return true;
+        }
+
+        public bool Remove(T item)
+        {
+            return DoRemove(item); // hack so it can be made non-virtual
+        }
+
+        internal virtual bool DoRemove(T item)
+        {
+            if (_root == null)
+            {
+                return false;
+            }
+
+            // Search for a node and then find its succesor. 
+            // Then copy the item from the succesor to the matching node and delete the successor. 
+            // If a node doesn't have a successor, we can replace it with its left child (if not empty.) 
+            // or delete the matching node.
+            // 
+            // In top-down implementation, it is important to make sure the node to be deleted is not a 2-node.
+            // Following code will make sure the node on the path is not a 2 Node. 
+
+            //
+            // Even if we don't actually remove from the set, we may be altering its structure (by doing rotations
+            // and such). so update version to disable any enumerators/subsets working on it.
+            //
+            _version++;
+
+            Node current = _root;
+            Node parent = null;
+            Node grandParent = null;
+            Node match = null;
+            Node parentOfMatch = null;
+            bool foundMatch = false;
+            while (current != null)
+            {
+                if (Is2Node(current))
+                { // fix up 2-Node
+                    if (parent == null)
+                    {   // current is root. Mark it as red
+                        current.IsRed = true;
+                    }
+                    else
+                    {
+                        Node sibling = GetSibling(current, parent);
+                        if (sibling.IsRed)
+                        {
+                            // If parent is a 3-node, flip the orientation of the red link. 
+                            // We can acheive this by a single rotation        
+                            // This case is converted to one of other cased below.
+                            Debug.Assert(!parent.IsRed, "parent must be a black node!");
+                            if (parent.Right == sibling)
+                            {
+                                RotateLeft(parent);
+                            }
+                            else
+                            {
+                                RotateRight(parent);
+                            }
+
+                            parent.IsRed = true;
+                            sibling.IsRed = false;    // parent's color
+                            // sibling becomes child of grandParent or root after rotation. Update link from grandParent or root
+                            ReplaceChildOfNodeOrRoot(grandParent, parent, sibling);
+                            // sibling will become grandParent of current node 
+                            grandParent = sibling;
+                            if (parent == match)
+                            {
+                                parentOfMatch = sibling;
+                            }
+
+                            // update sibling, this is necessary for following processing
+                            sibling = (parent.Left == current) ? parent.Right : parent.Left;
+                        }
+                        Debug.Assert(sibling != null || sibling.IsRed == false, "sibling must not be null and it must be black!");
+
+                        if (Is2Node(sibling))
+                        {
+                            Merge2Nodes(parent, current, sibling);
+                        }
+                        else
+                        {
+                            // current is a 2-node and sibling is either a 3-node or a 4-node.
+                            // We can change the color of current to red by some rotation.
+                            TreeRotation rotation = RotationNeeded(parent, current, sibling);
+                            Node newGrandParent = null;
+                            switch (rotation)
+                            {
+                                case TreeRotation.RightRotation:
+                                    Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!");
+                                    Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!");
+                                    sibling.Left.IsRed = false;
+                                    newGrandParent = RotateRight(parent);
+                                    break;
+                                case TreeRotation.LeftRotation:
+                                    Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!");
+                                    Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!");
+                                    sibling.Right.IsRed = false;
+                                    newGrandParent = RotateLeft(parent);
+                                    break;
+
+                                case TreeRotation.RightLeftRotation:
+                                    Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!");
+                                    Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!");
+                                    newGrandParent = RotateRightLeft(parent);
+                                    break;
+
+                                case TreeRotation.LeftRightRotation:
+                                    Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!");
+                                    Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!");
+                                    newGrandParent = RotateLeftRight(parent);
+                                    break;
+                            }
+
+                            newGrandParent.IsRed = parent.IsRed;
+                            parent.IsRed = false;
+                            current.IsRed = true;
+                            ReplaceChildOfNodeOrRoot(grandParent, parent, newGrandParent);
+                            if (parent == match)
+                            {
+                                parentOfMatch = newGrandParent;
+                            }
+                            grandParent = newGrandParent;
+                        }
+                    }
+                }
+
+                // we don't need to compare any more once we found the match
+                int order = foundMatch ? -1 : _comparer.Compare(item, current.Item);
+                if (order == 0)
+                {
+                    // save the matching node
+                    foundMatch = true;
+                    match = current;
+                    parentOfMatch = parent;
+                }
+
+                grandParent = parent;
+                parent = current;
+
+                if (order < 0)
+                {
+                    current = current.Left;
+                }
+                else
+                {
+                    current = current.Right;       // continue the search in  right sub tree after we find a match
+                }
+            }
+
+            // move successor to the matching node position and replace links
+            if (match != null)
+            {
+                ReplaceNode(match, parentOfMatch, parent, grandParent);
+                --_count;
+            }
+
+            if (_root != null)
+            {
+                _root.IsRed = false;
+            }
+
+            return foundMatch;
+        }
+
+        public Enumerator GetEnumerator()
+        {
+            return new Enumerator(this);
+        }
+
+        IEnumerator<T> IEnumerable<T>.GetEnumerator()
+        {
+            return new Enumerator(this);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return new Enumerator(this);
+        }
+
+        private static Node GetSibling(Node node, Node parent)
+        {
+            if (parent.Left == node)
+            {
+                return parent.Right;
+            }
+            return parent.Left;
+        }
+
+        // After calling InsertionBalance, we need to make sure current and parent up-to-date.
+        // It doesn't matter if we keep grandParent and greatGrantParent up-to-date 
+        // because we won't need to split again in the next node.
+        // By the time we need to split again, everything will be correctly set.
+        //  
+        private void InsertionBalance(Node current, ref Node parent, Node grandParent, Node greatGrandParent)
+        {
+            Debug.Assert(grandParent != null, "Grand parent cannot be null here!");
+            bool parentIsOnRight = (grandParent.Right == parent);
+            bool currentIsOnRight = (parent.Right == current);
+
+            Node newChildOfGreatGrandParent;
+            if (parentIsOnRight == currentIsOnRight)
+            { // same orientation, single rotation
+                newChildOfGreatGrandParent = currentIsOnRight ? RotateLeft(grandParent) : RotateRight(grandParent);
+            }
+            else
+            {  // different orientaton, double rotation
+                newChildOfGreatGrandParent = currentIsOnRight ? RotateLeftRight(grandParent) : RotateRightLeft(grandParent);
+                // current node now becomes the child of greatgrandparent 
+                parent = greatGrandParent;
+            }
+            // grand parent will become a child of either parent of current.
+            grandParent.IsRed = true;
+            newChildOfGreatGrandParent.IsRed = false;
+
+            ReplaceChildOfNodeOrRoot(greatGrandParent, grandParent, newChildOfGreatGrandParent);
+        }
+
+        private static bool Is2Node(Node node)
+        {
+            Debug.Assert(node != null, "node cannot be null!");
+            return IsBlack(node) && IsNullOrBlack(node.Left) && IsNullOrBlack(node.Right);
+        }
+
+        private static bool Is4Node(Node node)
+        {
+            return IsRed(node.Left) && IsRed(node.Right);
+        }
+
+        private static bool IsBlack(Node node)
+        {
+            return (node != null && !node.IsRed);
+        }
+
+        private static bool IsNullOrBlack(Node node)
+        {
+            return (node == null || !node.IsRed);
+        }
+
+        private static bool IsRed(Node node)
+        {
+            return (node != null && node.IsRed);
+        }
+
+        private static void Merge2Nodes(Node parent, Node child1, Node child2)
+        {
+            Debug.Assert(IsRed(parent), "parent must be be red");
+            // combing two 2-nodes into a 4-node
+            parent.IsRed = false;
+            child1.IsRed = true;
+            child2.IsRed = true;
+        }
+
+        // Replace the child of a parent node. 
+        // If the parent node is null, replace the root.        
+        private void ReplaceChildOfNodeOrRoot(Node parent, Node child, Node newChild)
+        {
+            if (parent != null)
+            {
+                if (parent.Left == child)
+                {
+                    parent.Left = newChild;
+                }
+                else
+                {
+                    parent.Right = newChild;
+                }
+            }
+            else
+            {
+                _root = newChild;
+            }
+        }
+
+        // Replace the matching node with its succesor.
+        private void ReplaceNode(Node match, Node parentOfMatch, Node successor, Node parentOfSuccessor)
+        {
+            if (successor == match)
+            {  // this node has no successor, should only happen if right child of matching node is null.
+                Debug.Assert(match.Right == null, "Right child must be null!");
+                successor = match.Left;
+            }
+            else
+            {
+                Debug.Assert(parentOfSuccessor != null, "parent of successor cannot be null!");
+                Debug.Assert(successor.Left == null, "Left child of succesor must be null!");
+                Debug.Assert((successor.Right == null && successor.IsRed) || (successor.Right.IsRed && !successor.IsRed), "Succesor must be in valid state");
+                if (successor.Right != null)
+                {
+                    successor.Right.IsRed = false;
+                }
+
+                if (parentOfSuccessor != match)
+                {   // detach succesor from its parent and set its right child
+                    parentOfSuccessor.Left = successor.Right;
+                    successor.Right = match.Right;
+                }
+
+                successor.Left = match.Left;
+            }
+
+            if (successor != null)
+            {
+                successor.IsRed = match.IsRed;
+            }
+
+            ReplaceChildOfNodeOrRoot(parentOfMatch, match, successor);
+        }
+
+        internal void UpdateVersion()
+        {
+            ++_version;
+        }
+
+        private static Node RotateLeft(Node node)
+        {
+            var x = node.Right;
+            node.Right = x.Left;
+            x.Left = node;
+
+            return x;
+        }
+
+        private static Node RotateLeftRight(Node node)
+        {
+            var child = node.Left;
+            var grandChild = child.Right;
+
+            node.Left = grandChild.Right;
+            grandChild.Right = node;
+            child.Right = grandChild.Left;
+            grandChild.Left = child;
+
+            return grandChild;
+        }
+
+        private static Node RotateRight(Node node)
+        {
+            var x = node.Left;
+            node.Left = x.Right;
+            x.Right = node;
+
+            return x;
+        }
+
+        private static Node RotateRightLeft(Node node)
+        {
+            var child = node.Right;
+            var grandChild = child.Left;
+
+            node.Right = grandChild.Left;
+            grandChild.Left = node;
+            child.Left = grandChild.Right;
+            grandChild.Right = child;
+
+            return grandChild;
+        }
+
+        private static TreeRotation RotationNeeded(Node parent, Node current, Node sibling)
+        {
+            Debug.Assert(IsRed(sibling.Left) || IsRed(sibling.Right), "sibling must have at least one red child");
+            if (IsRed(sibling.Left))
+            {
+                if (parent.Left == current)
+                {
+                    return TreeRotation.RightLeftRotation;
+                }
+
+                return TreeRotation.RightRotation;
+            }
+            else
+            {
+                if (parent.Left == current)
+                {
+                    return TreeRotation.LeftRotation;
+                }
+
+                return TreeRotation.LeftRightRotation;
+            }
+        }
+
+        private static void Split4Node(Node node)
+        {
+            node.IsRed = true;
+            node.Left.IsRed = false;
+            node.Right.IsRed = false;
+        }
+
+        internal class Node
+        {
+            public bool IsRed;
+            public T Item;
+            public Node Left;
+            public Node Right;
+
+            public Node(T item)
+            {
+                // The default color will be red, we never need to create a black node directly.                
+                Item = item;
+                IsRed = true;
+            }
+
+            public Node(T item, bool isRed)
+            {
+                // The default color will be red, we never need to create a black node directly.                
+                Item = item;
+                IsRed = isRed;
+            }
+        }
+
+        public struct Enumerator : IEnumerator<T>
+        {
+            private readonly SortedSet<T> _tree;
+            private int _version;
+
+            private readonly Stack<Node> _stack;
+            private Node _current;
+
+            internal Enumerator(SortedSet<T> set)
+            {
+                _tree = set;
+                _version = _tree._version;
+
+                // 2lg(n + 1) is the maximum height
+                _stack = new Stack<Node>(2 * (int)SortedSet<T>.log2(set.Count + 1));
+                _current = null;
+                Intialize();
+            }
+
+            private void Intialize()
+            {
+                _current = null;
+
+                Node node = _tree._root;
+                Node next = null, other = null;
+
+                while (node != null)
+                {
+                    next = node.Left;
+                    other = node.Right;
+
+                    _stack.Push(node);
+                    node = next;
+                }
+            }
+
+            public bool MoveNext()
+            {
+                if (_version != _tree._version)
+                {
+                    throw new InvalidOperationException("Collection has changed during enumeration.");
+                }
+
+                if (_stack.Count == 0)
+                {
+                    _current = null;
+                    return false;
+                }
+
+                _current = _stack.Pop();
+
+                Node node = _current.Right;
+                Node next = null, other = null;
+
+                while (node != null)
+                {
+                    next = node.Left;
+                    other = node.Right;
+
+                    _stack.Push(node);
+                    node = next;
+                }
+
+                return true;
+            }
+
+            public void Dispose()
+            {
+            }
+
+            public T Current
+            {
+                get
+                {
+                    if (_current != null)
+                    {
+                        return _current.Item;
+                    }
+
+                    return default(T);
+                }
+            }
+
+            object IEnumerator.Current
+            {
+                get
+                {
+                    if (_current == null)
+                    {
+                        throw new InvalidOperationException("Collection has changed during enumeration.");
+                    }
+
+                    return _current.Item;
+                }
+            }
+
+            internal bool NotStartedOrEnded
+            {
+                get
+                {
+                    return _current == null;
+                }
+            }
+
+            internal void Reset()
+            {
+                if (_version != _tree._version)
+                {
+                    throw new InvalidOperationException("Collection has changed during enumeration.");
+                }
+
+                _stack.Clear();
+                Intialize();
+            }
+
+            void IEnumerator.Reset()
+            {
+                Reset();
+            }
+        }
+
+        private static int log2(int value)
+        {
+            int c = 0;
+
+            while (value > 0)
+            {
+                c++;
+                value >>= 1;
+            }
+
+            return c;
+        }
+    }
+
+    internal enum TreeRotation
+    {
+        LeftRotation = 1,
+        RightRotation = 2,
+        RightLeftRotation = 3,
+        LeftRightRotation = 4,
+    }
+}
+#endif

+ 4 - 4
Rx.NET/Source/System.Reactive.Linq/Reactive/Linq/Observable/GroupJoin.cs

@@ -47,10 +47,10 @@ namespace System.Reactive.Linq.ObservableImpl
             private RefCountDisposable _refCount;
 
             private int _leftID;
-            private Dictionary<int, IObserver<TRight>> _leftMap;
+            private SortedDictionary<int, IObserver<TRight>> _leftMap;
 
             private int _rightID;
-            private Dictionary<int, TRight> _rightMap;
+            private SortedDictionary<int, TRight> _rightMap;
 
             public IDisposable Run()
             {
@@ -61,12 +61,12 @@ namespace System.Reactive.Linq.ObservableImpl
                 var leftSubscription = new SingleAssignmentDisposable();
                 _group.Add(leftSubscription);
                 _leftID = 0;
-                _leftMap = new Dictionary<int, IObserver<TRight>>();
+                _leftMap = new SortedDictionary<int, IObserver<TRight>>();
 
                 var rightSubscription = new SingleAssignmentDisposable();
                 _group.Add(rightSubscription);
                 _rightID = 0;
-                _rightMap = new Dictionary<int, TRight>();
+                _rightMap = new SortedDictionary<int, TRight>();
 
                 leftSubscription.Disposable = _parent._left.SubscribeSafe(new LeftObserver(this, leftSubscription));
                 rightSubscription.Disposable = _parent._right.SubscribeSafe(new RightObserver(this, rightSubscription));

+ 4 - 4
Rx.NET/Source/System.Reactive.Linq/Reactive/Linq/Observable/Join.cs

@@ -46,11 +46,11 @@ namespace System.Reactive.Linq.ObservableImpl
 
             private bool _leftDone;
             private int _leftID;
-            private Dictionary<int, TLeft> _leftMap;
+            private SortedDictionary<int, TLeft> _leftMap;
             
             private bool _rightDone;
             private int _rightID;
-            private Dictionary<int, TRight> _rightMap;
+            private SortedDictionary<int, TRight> _rightMap;
 
             public IDisposable Run()
             {
@@ -61,13 +61,13 @@ namespace System.Reactive.Linq.ObservableImpl
                 _group.Add(leftSubscription);
                 _leftDone = false;
                 _leftID = 0;
-                _leftMap = new Dictionary<int, TLeft>();
+                _leftMap = new SortedDictionary<int, TLeft>();
 
                 var rightSubscription = new SingleAssignmentDisposable();
                 _group.Add(rightSubscription);
                 _rightDone = false;
                 _rightID = 0;
-                _rightMap = new Dictionary<int, TRight>();
+                _rightMap = new SortedDictionary<int, TRight>();
 
                 leftSubscription.Disposable = _parent._left.SubscribeSafe(new LeftObserver(this, leftSubscription));
                 rightSubscription.Disposable = _parent._right.SubscribeSafe(new RightObserver(this, rightSubscription));

+ 1 - 0
Rx.NET/Source/System.Reactive.Linq/System.Reactive.Linq.csproj

@@ -38,6 +38,7 @@
     <Compile Include="Reactive\Internal\ReflectionUtils.cs" />
     <Compile Include="Reactive\Internal\HashSet.cs" />
     <Compile Include="Reactive\Internal\Lookup.cs" />
+    <Compile Include="Reactive\Internal\SortedDictionary.cs" />
     <Compile Include="Reactive\Linq\LocalQueryMethodImplementationTypeAttribute.cs" />
     <Compile Include="Reactive\Linq\Observable\Case.cs" />
     <Compile Include="Reactive\Linq\Observable\Collect.cs" />

+ 36 - 36
Rx.NET/Source/Tests.System.Reactive/Tests/Linq/ObservableStandardQueryOperatorTest.cs

@@ -8044,12 +8044,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -8132,12 +8132,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -8214,12 +8214,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -8302,12 +8302,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -8383,12 +8383,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -8465,12 +8465,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -8643,8 +8643,8 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
-                OnNext(712, "6man")
+                OnNext(712, "6man"),
+                OnNext(712, "7man")
             );
 
             AssertDurations(xs, xsd, 713);
@@ -8769,8 +8769,8 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
                 OnError<string>(722, ex)
@@ -8842,12 +8842,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnError<string>(725, ex)
             );
@@ -8918,8 +8918,8 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
                 OnError<string>(721, ex)
@@ -9236,12 +9236,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -9318,12 +9318,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -9400,12 +9400,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -9482,12 +9482,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -9557,12 +9557,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -9639,12 +9639,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnNext(732, "7wig"),
                 OnNext(732, "8wig"),
@@ -9722,8 +9722,8 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
-                OnNext(712, "6man")
+                OnNext(712, "6man"),
+                OnNext(712, "7man")
             );
 
             xs.Subscriptions.AssertEqual(
@@ -9848,8 +9848,8 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
                 OnError<string>(722, ex)
@@ -9921,12 +9921,12 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
-                OnNext(722, "7rat"),
                 OnNext(722, "6rat"),
+                OnNext(722, "7rat"),
                 OnNext(722, "8rat"),
                 OnError<string>(725, ex)
             );
@@ -9997,8 +9997,8 @@ namespace ReactiveTests.Tests
                 OnNext(310, "4cup"),
                 OnNext(702, "6tin"),
                 OnNext(710, "7tin"),
-                OnNext(712, "7man"),
                 OnNext(712, "6man"),
+                OnNext(712, "7man"),
                 OnNext(720, "8tin"),
                 OnNext(720, "8man"),
                 OnError<string>(721, ex)