You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2016/03/21 13:23:53 UTC

ignite git commit: IGNITE-1957: .NET: Binary marshaller now use handles for arrays, collections and dictionaries. This closes #302.

Repository: ignite
Updated Branches:
  refs/heads/master 69f526a48 -> cadc61fa8


IGNITE-1957: .NET: Binary marshaller now use handles for arrays, collections and dictionaries. This closes #302.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/cadc61fa
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/cadc61fa
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/cadc61fa

Branch: refs/heads/master
Commit: cadc61fa89df00d0c632328d0678e2b19d525e42
Parents: 69f526a
Author: Pavel Tupitsyn <pt...@gridgain.com>
Authored: Mon Mar 21 15:23:47 2016 +0300
Committer: vozerov-gridgain <vo...@gridgain.com>
Committed: Mon Mar 21 15:23:47 2016 +0300

----------------------------------------------------------------------
 .../Binary/BinarySelfTest.cs                    | 113 ++++++++++++++++
 .../Apache.Ignite.Core.csproj                   |   1 +
 .../Impl/Binary/BinaryHandleDictionary.cs       |  32 +++--
 .../Impl/Binary/BinaryReader.cs                 |  61 ++++-----
 .../Impl/Binary/BinaryReaderHandleDictionary.cs |   2 +-
 .../Impl/Binary/BinarySystemHandlers.cs         | 132 ++++++++++---------
 .../Impl/Binary/BinaryUtils.cs                  |  12 ++
 .../Impl/Binary/BinaryWriter.cs                 |  24 ++--
 .../Impl/Binary/ReferenceEqualityComparer.cs    |  45 +++++++
 .../Impl/Common/DelegateConverter.cs            |   4 +-
 10 files changed, 301 insertions(+), 125 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
index 0fcb792..41e327b 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs
@@ -29,10 +29,12 @@ namespace Apache.Ignite.Core.Tests.Binary
     using System.Diagnostics.CodeAnalysis;
     using System.IO;
     using System.Linq;
+    using System.Reflection;
     using Apache.Ignite.Core.Binary;
     using Apache.Ignite.Core.Common;
     using Apache.Ignite.Core.Impl.Binary;
     using Apache.Ignite.Core.Impl.Binary.IO;
+    using Apache.Ignite.Core.Impl.Common;
     using NUnit.Framework;
     using BinaryReader = Apache.Ignite.Core.Impl.Binary.BinaryReader;
     using BinaryWriter = Apache.Ignite.Core.Impl.Binary.BinaryWriter;
@@ -1256,6 +1258,87 @@ namespace Apache.Ignite.Core.Tests.Binary
             Assert.IsTrue(newOuter.RawInner == newOuter.RawInner.Outer.RawInner);
         }
 
+        [Test]
+        public void TestHandlesCollections()
+        {
+            var marsh = new Marshaller(new BinaryConfiguration
+            {
+                TypeConfigurations = new[]
+                {
+                    new BinaryTypeConfiguration(typeof (HandleCollection))
+                }
+            });
+
+            // Collection in collection dependency loop
+            var collection = new ArrayList {1, 2};
+            collection.Add(collection);
+
+            var collectionRaw = new ArrayList(collection);
+            collectionRaw.Add(collectionRaw);
+
+            var collectionObj = new ArrayList(collectionRaw);
+            collectionObj.Add(collectionObj);
+
+            var dict = new Hashtable { { 1, 1 }, { 2, 2 } };
+            dict.Add(3, dict);
+
+            var arr = collectionObj.ToArray();
+            arr[1] = arr;
+
+            object entry = new DictionaryEntry(1, 2);
+            var dictionaryEntryValSetter = DelegateConverter.CompileFieldSetter(typeof (DictionaryEntry)
+                .GetField("_value", BindingFlags.Instance | BindingFlags.NonPublic));
+            dictionaryEntryValSetter(entry, entry);  // modify boxed copy to create reference loop
+
+            var data = new HandleCollection
+            {
+                Collection = collection,
+                CollectionRaw = collectionRaw,
+                Object = collectionObj,
+                Dictionary = dict,
+                Array = arr,
+                DictionaryEntry = (DictionaryEntry) entry
+            };
+
+            var res = marsh.Unmarshal<HandleCollection>(marsh.Marshal(data));
+
+            var resCollection = (ArrayList) res.Collection;
+            Assert.AreEqual(collection[0], resCollection[0]);
+            Assert.AreEqual(collection[1], resCollection[1]);
+            Assert.AreSame(resCollection, resCollection[2]);
+
+            var resCollectionRaw = (ArrayList) res.CollectionRaw;
+            Assert.AreEqual(collectionRaw[0], resCollectionRaw[0]);
+            Assert.AreEqual(collectionRaw[1], resCollectionRaw[1]);
+            Assert.AreSame(resCollection, resCollectionRaw[2]);
+            Assert.AreSame(resCollectionRaw, resCollectionRaw[3]);
+
+            var resCollectionObj = (ArrayList) res.Object;
+            Assert.AreEqual(collectionObj[0], resCollectionObj[0]);
+            Assert.AreEqual(collectionObj[1], resCollectionObj[1]);
+            Assert.AreSame(resCollection, resCollectionObj[2]);
+            Assert.AreSame(resCollectionRaw, resCollectionObj[3]);
+            Assert.AreSame(resCollectionObj, resCollectionObj[4]);
+
+            var resDict = (Hashtable) res.Dictionary;
+            Assert.AreEqual(1, resDict[1]);
+            Assert.AreEqual(2, resDict[2]);
+            Assert.AreSame(resDict, resDict[3]);
+
+            var resArr = res.Array;
+            Assert.AreEqual(arr[0], resArr[0]);
+            Assert.AreSame(resArr, resArr[1]);
+            Assert.AreSame(resCollection, resArr[2]);
+            Assert.AreSame(resCollectionRaw, resArr[3]);
+            Assert.AreSame(resCollectionObj, resArr[4]);
+
+            var resEntry = res.DictionaryEntry;
+            var innerEntry = (DictionaryEntry) resEntry.Value;
+            Assert.AreEqual(1, resEntry.Key);
+            Assert.AreEqual(1, innerEntry.Key);
+            Assert.IsTrue(ReferenceEquals(innerEntry.Value, ((DictionaryEntry) innerEntry.Value).Value));
+        }
+
         ///
         /// <summary>Test KeepSerialized property</summary>
         ///
@@ -2186,6 +2269,36 @@ namespace Apache.Ignite.Core.Tests.Binary
             }
         }
 
+        public class HandleCollection : IBinarizable
+        {
+            public ICollection Collection { get; set; }
+            public IDictionary Dictionary { get; set; }
+            public DictionaryEntry DictionaryEntry { get; set; }
+            public ICollection CollectionRaw { get; set; }
+            public object Object { get; set; }
+            public object[] Array { get; set; }
+
+            public void WriteBinary(IBinaryWriter writer)
+            {
+                writer.WriteCollection("col", Collection);
+                writer.WriteDictionary("dict", Dictionary);
+                writer.WriteObject("dictEntry", DictionaryEntry);
+                writer.WriteObject("obj", Object);
+                writer.WriteArray("arr", Array);
+                writer.GetRawWriter().WriteCollection(CollectionRaw);
+            }
+
+            public void ReadBinary(IBinaryReader reader)
+            {
+                Collection = reader.ReadCollection("col");
+                Dictionary = reader.ReadDictionary("dict");
+                DictionaryEntry = reader.ReadObject<DictionaryEntry>("dictEntry");
+                Object = reader.ReadObject<object>("obj");
+                Array = reader.ReadArray<object>("arr");
+                CollectionRaw = reader.GetRawReader().ReadCollection();
+            }
+        }
+
         public class PropertyType
         {
             public int Field1;

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
index dedf084..bfedce9 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj
@@ -194,6 +194,7 @@
     <Compile Include="IIgnite.cs" />
     <Compile Include="Impl\Binary\BinaryEnum.cs" />
     <Compile Include="Impl\Binary\BinaryObjectSchemaSerializer.cs" />
+    <Compile Include="Impl\Binary\ReferenceEqualityComparer.cs" />
     <Compile Include="Impl\Binary\JavaTypes.cs" />
     <Compile Include="Impl\Cache\CacheAffinityImpl.cs" />
     <Compile Include="Impl\Cache\CacheEntry.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryHandleDictionary.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryHandleDictionary.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryHandleDictionary.cs
index 3f39bcc..08e17ca 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryHandleDictionary.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryHandleDictionary.cs
@@ -50,22 +50,28 @@ namespace Apache.Ignite.Core.Impl.Binary
         /** Third value. */
         private TV _val3;
 
+        /** Comparer. */
+        private readonly IEqualityComparer<TK> _comparer;
+
         /// <summary>
         /// Constructor with initial key-value pair.
         /// </summary>
         /// <param name="key">Key.</param>
         /// <param name="val">Value.</param>
+        /// <param name="comparer">The comparer.</param>
         [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors"),
          SuppressMessage("ReSharper", "DoNotCallOverridableMethodsInConstructor")]
-        public BinaryHandleDictionary(TK key, TV val)
+        public BinaryHandleDictionary(TK key, TV val, IEqualityComparer<TK> comparer)
         {
-            Debug.Assert(!Equals(key, EmptyKey));
-
             _key1 = key;
             _val1 = val;
 
             _key2 = EmptyKey;
             _key3 = EmptyKey;
+
+            _comparer = comparer ?? EqualityComparer<TK>.Default;
+
+            Debug.Assert(!_comparer.Equals(key, EmptyKey));
         }
 
         /// <summary>
@@ -75,9 +81,9 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// <param name="val">Value.</param>
         public void Add(TK key, TV val)
         {
-            Debug.Assert(!Equals(key, EmptyKey));
+            Debug.Assert(!_comparer.Equals(key, EmptyKey));
 
-            if (Equals(_key2, EmptyKey))
+            if (_comparer.Equals(_key2, EmptyKey))
             {
                 _key2 = key;
                 _val2 = val;
@@ -85,7 +91,7 @@ namespace Apache.Ignite.Core.Impl.Binary
                 return;
             }
 
-            if (Equals(_key3, EmptyKey))
+            if (_comparer.Equals(_key3, EmptyKey))
             {
                 _key3 = key;
                 _val3 = val;
@@ -94,7 +100,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             }
 
             if (_dict == null)
-                _dict = new Dictionary<TK, TV>(InitialSize);
+                _dict = new Dictionary<TK, TV>(InitialSize, _comparer);
 
             _dict[key] = val;
         }
@@ -107,23 +113,23 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// <returns>True if key was found.</returns>
         public bool TryGetValue(TK key, out TV val)
         {
-            Debug.Assert(!Equals(key, EmptyKey));
+            Debug.Assert(!_comparer.Equals(key, EmptyKey));
 
-            if (Equals(key, _key1))
+            if (_comparer.Equals(key, _key1))
             {
                 val = _val1;
 
                 return true;
             }
 
-            if (Equals(key, _key2))
+            if (_comparer.Equals(key, _key2))
             {
                 val = _val2;
 
                 return true;
             }
 
-            if (Equals(key, _key3))
+            if (_comparer.Equals(key, _key3))
             {
                 val = _val3;
 
@@ -167,10 +173,10 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// <param name="val">Value.</param>
         private void AddIfAbsent(TK key, TV val)
         {
-            if (Equals(key, EmptyKey))
+            if (_comparer.Equals(key, EmptyKey))
                 return;
 
-            if (Equals(key, _key1) || Equals(key, _key2) || Equals(key, _key3))
+            if (_comparer.Equals(key, _key1) || _comparer.Equals(key, _key2) || _comparer.Equals(key, _key3))
                 return;
 
             if (_dict == null || !_dict.ContainsKey(key))

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
index 21c1642..1403410 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReader.cs
@@ -874,7 +874,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         /// <param name="pos">Position.</param>
         /// <param name="obj">Object.</param>
-        private void AddHandle(int pos, object obj)
+        internal void AddHandle(int pos, object obj)
         {
             if (_hnds == null)
                 _hnds = new BinaryReaderHandleDictionary(pos, obj);
@@ -905,35 +905,6 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
-        /// Determines whether header at current position is HDR_NULL.
-        /// </summary>
-        private bool IsNotNullHeader(byte expHdr)
-        {
-            var hdr = ReadByte();
-            
-            if (hdr == BinaryUtils.HdrNull)
-                return false;
-
-            if (expHdr != hdr)
-                throw new BinaryObjectException(string.Format("Invalid header on deserialization. " +
-                                                          "Expected: {0} but was: {1}", expHdr, hdr));
-
-            return true;
-        }
-
-        /// <summary>
-        /// Seeks the field by name, reads header and returns true if field is present and header is not null.
-        /// </summary>
-        private bool SeekField(string fieldName, byte expHdr)
-        {
-            if (!SeekField(fieldName)) 
-                return false;
-
-            // Expected read order, no need to seek.
-            return IsNotNullHeader(expHdr);
-        }
-
-        /// <summary>
         /// Seeks the field by name.
         /// </summary>
         private bool SeekField(string fieldName)
@@ -971,7 +942,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         private T ReadField<T>(string fieldName, Func<IBinaryStream, T> readFunc, byte expHdr)
         {
-            return SeekField(fieldName, expHdr) ? readFunc(Stream) : default(T);
+            return SeekField(fieldName) ? Read(readFunc, expHdr) : default(T);
         }
 
         /// <summary>
@@ -979,7 +950,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         private T ReadField<T>(string fieldName, Func<BinaryReader, T> readFunc, byte expHdr)
         {
-            return SeekField(fieldName, expHdr) ? readFunc(this) : default(T);
+            return SeekField(fieldName) ? Read(readFunc, expHdr) : default(T);
         }
 
         /// <summary>
@@ -987,7 +958,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         private T ReadField<T>(string fieldName, Func<T> readFunc, byte expHdr)
         {
-            return SeekField(fieldName, expHdr) ? readFunc() : default(T);
+            return SeekField(fieldName) ? Read(readFunc, expHdr) : default(T);
         }
 
         /// <summary>
@@ -995,7 +966,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         private T Read<T>(Func<BinaryReader, T> readFunc, byte expHdr)
         {
-            return IsNotNullHeader(expHdr) ? readFunc(this) : default(T);
+            return Read(() => readFunc(this), expHdr);
         }
 
         /// <summary>
@@ -1003,7 +974,27 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         private T Read<T>(Func<IBinaryStream, T> readFunc, byte expHdr)
         {
-            return IsNotNullHeader(expHdr) ? readFunc(Stream) : default(T);
+            return Read(() => readFunc(Stream), expHdr);
+        }
+
+        /// <summary>
+        /// Reads header and invokes specified func if the header is not null.
+        /// </summary>
+        private T Read<T>(Func<T> readFunc, byte expHdr)
+        {
+            var hdr = ReadByte();
+
+            if (hdr == BinaryUtils.HdrNull)
+                return default(T);
+
+            if (hdr == BinaryUtils.HdrHnd)
+                return ReadHandleObject<T>(Stream.Position - 1);
+
+            if (expHdr != hdr)
+                throw new BinaryObjectException(string.Format("Invalid header on deserialization. " +
+                                                          "Expected: {0} but was: {1}", expHdr, hdr));
+
+            return readFunc();
         }
 
         /// <summary>

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderHandleDictionary.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderHandleDictionary.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderHandleDictionary.cs
index c145e7f..8a9a466 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderHandleDictionary.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReaderHandleDictionary.cs
@@ -28,7 +28,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// <param name="key">Key.</param>
         /// <param name="val">Value.</param>
         public BinaryReaderHandleDictionary(int key, object val)
-            : base(key, val)
+            : base(key, val, null)
         {
             // No-op.
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
index 36e324d..89925dd 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs
@@ -26,24 +26,14 @@ namespace Apache.Ignite.Core.Impl.Binary
     using Apache.Ignite.Core.Impl.Binary.IO;
     using Apache.Ignite.Core.Impl.Common;
 
-    /// <summary>
-    /// Write delegate.
-    /// </summary>
-    /// <param name="writer">Write context.</param>
-    /// <param name="obj">Object to write.</param>
-    internal delegate void BinarySystemWriteDelegate(BinaryWriter writer, object obj);
-
     /**
      * <summary>Collection of predefined handlers for various system types.</summary>
      */
     internal static class BinarySystemHandlers
     {
         /** Write handlers. */
-        private static volatile Dictionary<Type, BinarySystemWriteDelegate> _writeHandlers =
-            new Dictionary<Type, BinarySystemWriteDelegate>();
-
-        /** Mutex for write handlers update. */
-        private static readonly object WriteHandlersMux = new object();
+        private static readonly CopyOnWriteConcurrentDictionary<Type, BinarySystemWriteHandler> WriteHandlers =
+            new CopyOnWriteConcurrentDictionary<Type, BinarySystemWriteHandler>();
 
         /** Read handlers. */
         private static readonly IBinarySystemReader[] ReadHandlers = new IBinarySystemReader[255];
@@ -171,32 +161,30 @@ namespace Apache.Ignite.Core.Impl.Binary
         /// </summary>
         /// <param name="type"></param>
         /// <returns></returns>
-        public static BinarySystemWriteDelegate GetWriteHandler(Type type)
+        public static BinarySystemWriteHandler GetWriteHandler(Type type)
         {
-            BinarySystemWriteDelegate res;
-
-            var writeHandlers0 = _writeHandlers;
-
-            // Have we ever met this type?
-            if (writeHandlers0 != null && writeHandlers0.TryGetValue(type, out res))
-                return res;
-
-            // Determine write handler for type and add it.
-            res = FindWriteHandler(type);
+            return WriteHandlers.GetOrAdd(type, t =>
+            {
+                bool supportsHandles;
 
-            if (res != null)
-                AddWriteHandler(type, res);
+                var handler = FindWriteHandler(t, out supportsHandles);
 
-            return res;
+                return handler == null ? null : new BinarySystemWriteHandler(handler, supportsHandles);
+            });
         }
 
         /// <summary>
         /// Find write handler for type.
         /// </summary>
         /// <param name="type">Type.</param>
-        /// <returns>Write handler or NULL.</returns>
-        private static BinarySystemWriteDelegate FindWriteHandler(Type type)
+        /// <param name="supportsHandles">Flag indicating whether returned delegate supports handles.</param>
+        /// <returns>
+        /// Write handler or NULL.
+        /// </returns>
+        private static Action<BinaryWriter, object> FindWriteHandler(Type type, out bool supportsHandles)
         {
+            supportsHandles = false;
+
             // 1. Well-known types.
             if (type == typeof(string))
                 return WriteString;
@@ -210,9 +198,15 @@ namespace Apache.Ignite.Core.Impl.Binary
                 return WriteBinary;
             if (type == typeof (BinaryEnum))
                 return WriteBinaryEnum;
+            if (type.IsEnum)
+                return WriteEnum;
+
+            // All types below can be written as handles.
+            supportsHandles = true;
+
             if (type == typeof (ArrayList))
                 return WriteArrayList;
-            if (type == typeof(Hashtable))
+            if (type == typeof (Hashtable))
                 return WriteHashtable;
 
             if (type.IsArray)
@@ -258,14 +252,11 @@ namespace Apache.Ignite.Core.Impl.Binary
                     return WriteEnumArray;
                 
                 // Object array.
-                if (elemType == typeof (object) || elemType == typeof(IBinaryObject) || elemType == typeof(BinaryObject))
+                if (elemType == typeof (object) || elemType == typeof (IBinaryObject) ||
+                    elemType == typeof (BinaryObject))
                     return WriteArray;
             }
 
-            if (type.IsEnum)
-                // We know how to write enums.
-                return WriteEnum;
-
             if (type.IsSerializable)
                 return WriteSerializable;
 
@@ -294,36 +285,6 @@ namespace Apache.Ignite.Core.Impl.Binary
         }
 
         /// <summary>
-        /// Add write handler for type.
-        /// </summary>
-        /// <param name="type"></param>
-        /// <param name="handler"></param>
-        private static void AddWriteHandler(Type type, BinarySystemWriteDelegate handler)
-        {
-            lock (WriteHandlersMux)
-            {
-                if (_writeHandlers == null)
-                {
-                    Dictionary<Type, BinarySystemWriteDelegate> writeHandlers0 = 
-                        new Dictionary<Type, BinarySystemWriteDelegate>();
-
-                    writeHandlers0[type] = handler;
-
-                    _writeHandlers = writeHandlers0;
-                }
-                else if (!_writeHandlers.ContainsKey(type))
-                {
-                    Dictionary<Type, BinarySystemWriteDelegate> writeHandlers0 =
-                        new Dictionary<Type, BinarySystemWriteDelegate>(_writeHandlers);
-
-                    writeHandlers0[type] = handler;
-
-                    _writeHandlers = writeHandlers0;
-                }
-            }
-        }
-
-        /// <summary>
         /// Reads an object of predefined type.
         /// </summary>
         public static bool TryReadSystemType<T>(byte typeId, BinaryReader ctx, out T res)
@@ -818,4 +779,47 @@ namespace Apache.Ignite.Core.Impl.Binary
             }
         }
     }
+
+    /// <summary>
+    /// Write delegate + handles flag.
+    /// </summary>
+    internal class BinarySystemWriteHandler
+    {
+        /** */
+        private readonly Action<BinaryWriter, object> _writeAction;
+
+        /** */
+        private readonly bool _supportsHandles;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BinarySystemWriteHandler"/> class.
+        /// </summary>
+        /// <param name="writeAction">The write action.</param>
+        /// <param name="supportsHandles">Handles flag.</param>
+        public BinarySystemWriteHandler(Action<BinaryWriter, object> writeAction, bool supportsHandles = false)
+        {
+            Debug.Assert(writeAction != null);
+
+            _writeAction = writeAction;
+            _supportsHandles = supportsHandles;
+        }
+
+        /// <summary>
+        /// Writes object to a specified writer.
+        /// </summary>
+        /// <param name="writer">The writer.</param>
+        /// <param name="obj">The object.</param>
+        public void Write(BinaryWriter writer, object obj)
+        {
+            _writeAction(writer, obj);
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether this handler supports handles.
+        /// </summary>
+        public bool SupportsHandles
+        {
+            get { return _supportsHandles; }
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
index b73a6c4..4142d60 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs
@@ -1123,6 +1123,8 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             var stream = ctx.Stream;
 
+            var pos = stream.Position;
+
             if (typed)
                 stream.ReadInt();
 
@@ -1130,6 +1132,8 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             var vals = new T[len];
 
+            ctx.AddHandle(pos - 1, vals);
+
             for (int i = 0; i < len; i++)
                 vals[i] = ctx.Deserialize<T>();
 
@@ -1209,6 +1213,8 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             IBinaryStream stream = ctx.Stream;
 
+            int pos = stream.Position;
+
             int len = stream.ReadInt();
 
             byte colType = ctx.Stream.ReadByte();
@@ -1225,6 +1231,8 @@ namespace Apache.Ignite.Core.Impl.Binary
             else
                 res = factory.Invoke(len);
 
+            ctx.AddHandle(pos - 1, res);
+
             if (adder == null)
                 adder = (col, elem) => ((ArrayList) col).Add(elem);
 
@@ -1286,6 +1294,8 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             IBinaryStream stream = ctx.Stream;
 
+            int pos = stream.Position;
+
             int len = stream.ReadInt();
 
             // Skip dictionary type as we can do nothing with it here.
@@ -1293,6 +1303,8 @@ namespace Apache.Ignite.Core.Impl.Binary
 
             var res = factory == null ? new Hashtable(len) : factory.Invoke(len);
 
+            ctx.AddHandle(pos - 1, res);
+
             for (int i = 0; i < len; i++)
             {
                 object key = ctx.Deserialize<object>();

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
index 47bc2b6..1ac98c4 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs
@@ -907,13 +907,7 @@ namespace Apache.Ignite.Core.Impl.Binary
         {
             WriteFieldId(fieldName, BinaryUtils.TypeArray);
 
-            if (val == null)
-                WriteNullField();
-            else
-            {
-                _stream.WriteByte(BinaryUtils.TypeArray);
-                BinaryUtils.WriteArray(val, this);
-            }
+            WriteArray(val);
         }
 
         /// <summary>
@@ -936,6 +930,9 @@ namespace Apache.Ignite.Core.Impl.Binary
                 WriteNullRawField();
             else
             {
+                if (WriteHandle(_stream.Position, val))
+                    return;
+
                 _stream.WriteByte(BinaryUtils.TypeArray);
                 BinaryUtils.WriteArray(val, this);
             }
@@ -963,6 +960,9 @@ namespace Apache.Ignite.Core.Impl.Binary
                 WriteNullField();
             else
             {
+                if (WriteHandle(_stream.Position, val))
+                    return;
+
                 WriteByte(BinaryUtils.TypeCollection);
                 BinaryUtils.WriteCollection(val, this);
             }
@@ -990,6 +990,9 @@ namespace Apache.Ignite.Core.Impl.Binary
                 WriteNullField();
             else
             {
+                if (WriteHandle(_stream.Position, val))
+                    return;
+
                 WriteByte(BinaryUtils.TypeDictionary);
                 BinaryUtils.WriteDictionary(val, this);
             }
@@ -1194,7 +1197,10 @@ namespace Apache.Ignite.Core.Impl.Binary
                 if (handler == null)  // We did our best, object cannot be marshalled.
                     throw new BinaryObjectException("Unsupported object type [type=" + type + ", object=" + obj + ']');
                 
-                handler(this, obj);
+                if (handler.SupportsHandles && WriteHandle(_stream.Position, obj))
+                    return;
+
+                handler.Write(this, obj);
             }
         }
 
@@ -1326,7 +1332,7 @@ namespace Apache.Ignite.Core.Impl.Binary
             if (_hnds == null)
             {
                 // Cache absolute handle position.
-                _hnds = new BinaryHandleDictionary<object, long>(obj, pos);
+                _hnds = new BinaryHandleDictionary<object, long>(obj, pos, ReferenceEqualityComparer<object>.Instance);
 
                 return false;
             }

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReferenceEqualityComparer.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReferenceEqualityComparer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReferenceEqualityComparer.cs
new file mode 100644
index 0000000..8038d6b
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReferenceEqualityComparer.cs
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+namespace Apache.Ignite.Core.Impl.Binary
+{
+    using System.Collections.Generic;
+    using System.Runtime.CompilerServices;
+
+    /// <summary>
+    /// Comparer that uses ReferenceEquals.
+    /// </summary>
+    internal class ReferenceEqualityComparer<T> : IEqualityComparer<T>
+    {
+        /// <summary>
+        /// Default instance.
+        /// </summary>
+        public static readonly ReferenceEqualityComparer<T> Instance = new ReferenceEqualityComparer<T>();
+
+        /** <inheritdoc /> */
+        public bool Equals(T x, T y)
+        {
+            return ReferenceEquals(x, y);
+        }
+
+        /** <inheritdoc /> */
+        public int GetHashCode(T obj)
+        {
+            return RuntimeHelpers.GetHashCode(obj);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/cadc61fa/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
index fa19a9e..00bda16 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
@@ -214,12 +214,10 @@ namespace Apache.Ignite.Core.Impl.Common
             Debug.Assert(field.DeclaringType != null);   // non-static
 
             var targetParam = Expression.Parameter(typeof(object));
-            var targetParamConverted = Expression.Convert(targetParam, field.DeclaringType);
-
             var valParam = Expression.Parameter(typeof(object));
             var valParamConverted = Expression.Convert(valParam, field.FieldType);
 
-            var assignExpr = Expression.Call(GetWriteFieldMethod(field), targetParamConverted, valParamConverted);
+            var assignExpr = Expression.Call(GetWriteFieldMethod(field), targetParam, valParamConverted);
 
             return Expression.Lambda<Action<object, object>>(assignExpr, targetParam, valParam).Compile();
         }