From ba4016a4f604254bafe27cbdca0109bbeee90dc2 Mon Sep 17 00:00:00 2001 From: orck-adrouin Date: Wed, 23 Dec 2015 10:15:55 -0500 Subject: [PATCH 1/2] Initial refactoring of NetSerializer - No longer requires the classes to be decorated with [Serializable] - Classes from the System.* and Microsoft.* namespaces without [Serializable] are not supported and will not be serialized - Types to serialize are automatically detected. - This is achieved by adding the type's name to the serialized stream instead of the 'TypeId' value - Fields are still serialized in the same order as NetSerializer so client and server must still have the same version. - Throws an exception when a circular reference is detected instead of generating a stack overflow - A new dictionary serializer was created to: - Allow inheriting from a Dictionary<,> - Serialize the comparer - This new serializer requires a parameter less constructor - New serializers: - Hashset - Hashtable --- .gitignore | 237 +++++ .../TypeSerializers/ObjectSerializer.cs | 74 -- PrimitiveTest/PrimitiveTest.csproj | 4 +- PrimitiveTest/Program.cs | 17 +- README | 283 +----- .../Exceptions/CircularReferenceException.cs | 13 + ...PublicParameterlessConstructorException.cs | 11 + .../Exceptions/TypeNotFoundException.cs | 13 + {NetSerializer => Serialization}/Helpers.cs | 51 +- .../ITypeSerializer.cs | 2 +- .../NetSerializer.nuspec | 0 .../Primitives.cs | 4 +- Serialization/Properties/AssemblyInfo.cs | 32 + Serialization/ReferenceWatcher.cs | 84 ++ .../Serialization.csproj | 20 +- .../Serialization.csproj.DotSettings | 2 + .../Serializer.cs | 108 ++- Serialization/SerializerKey.snk | Bin 0 -> 596 bytes {NetSerializer => Serialization}/TypeData.cs | 2 +- .../TypeDictionary.cs | 2 +- .../TypeIDList.cs | 5 +- .../TypeSerializers/ArraySerializer.cs | 5 +- .../TypeSerializers/DictionarySerializer.cs | 5 +- .../TypeSerializers/EnumSerializer.cs | 5 +- .../TypeSerializers/GenericSerializer.cs | 14 +- .../TypeSerializers/HashsetSerializer.cs | 159 ++++ .../TypeSerializers/HashtableSerializer.cs | 61 ++ .../TypeSerializers/NoOpSerializer.cs | 14 +- .../TypeSerializers/NullableSerializer.cs | 2 +- .../TypeSerializers/ObjectSerializer.cs | 155 ++++ .../TypeSerializers/PrimitivesSerializer.cs | 4 +- .../UniversalDictionarySerializer.cs | 203 +++++ .../Properties/AssemblyInfo.cs | 16 +- Serializer.Tests/Serialization.Tests.csproj | 102 +++ Serializer.Tests/SerializerTests.cs | 814 ++++++++++++++++++ .../TestData/CircularReference.cs | 11 + .../TestData/ClassWithClassProperty.cs | 10 + .../ClassWithDifferentAccessModifiers.cs | 34 + .../TestData/ClassWithIEnumerable.cs | 11 + .../ClassWithoutSerializableAttribute.cs | 7 + .../TestData/CustomDictionaries.cs | 41 + Serializer.Tests/TestData/EnumForTesting.cs | 12 + Serializer.Tests/TestData/GenericBaseClass.cs | 40 + Serializer.Tests/TestData/HierarchyClasses.cs | 32 + Serializer.Tests/TestData/TestStruct.cs | 10 + NetSerializer.sln => Serializer.sln | 8 +- Test/CustomSerializers.cs | 4 +- Test/Program.cs | 4 +- Test/SerializerSpecimen.cs | 6 +- Test/Test.csproj | 4 +- Test/Tester.cs | 8 +- 51 files changed, 2315 insertions(+), 450 deletions(-) delete mode 100644 NetSerializer/TypeSerializers/ObjectSerializer.cs create mode 100644 Serialization/Exceptions/CircularReferenceException.cs create mode 100644 Serialization/Exceptions/RequiredPublicParameterlessConstructorException.cs create mode 100644 Serialization/Exceptions/TypeNotFoundException.cs rename {NetSerializer => Serialization}/Helpers.cs (74%) rename {NetSerializer => Serialization}/ITypeSerializer.cs (97%) rename {NetSerializer => Serialization}/NetSerializer.nuspec (100%) rename {NetSerializer => Serialization}/Primitives.cs (99%) create mode 100644 Serialization/Properties/AssemblyInfo.cs create mode 100644 Serialization/ReferenceWatcher.cs rename NetSerializer/NetSerializer.csproj => Serialization/Serialization.csproj (77%) create mode 100644 Serialization/Serialization.csproj.DotSettings rename {NetSerializer => Serialization}/Serializer.cs (84%) create mode 100644 Serialization/SerializerKey.snk rename {NetSerializer => Serialization}/TypeData.cs (98%) rename {NetSerializer => Serialization}/TypeDictionary.cs (99%) rename {NetSerializer => Serialization}/TypeIDList.cs (92%) rename {NetSerializer => Serialization}/TypeSerializers/ArraySerializer.cs (98%) rename {NetSerializer => Serialization}/TypeSerializers/DictionarySerializer.cs (97%) rename {NetSerializer => Serialization}/TypeSerializers/EnumSerializer.cs (92%) rename {NetSerializer => Serialization}/TypeSerializers/GenericSerializer.cs (87%) create mode 100644 Serialization/TypeSerializers/HashsetSerializer.cs create mode 100644 Serialization/TypeSerializers/HashtableSerializer.cs rename {NetSerializer => Serialization}/TypeSerializers/NoOpSerializer.cs (79%) rename {NetSerializer => Serialization}/TypeSerializers/NullableSerializer.cs (98%) create mode 100644 Serialization/TypeSerializers/ObjectSerializer.cs rename {NetSerializer => Serialization}/TypeSerializers/PrimitivesSerializer.cs (95%) create mode 100644 Serialization/TypeSerializers/UniversalDictionarySerializer.cs rename {NetSerializer => Serializer.Tests}/Properties/AssemblyInfo.cs (72%) create mode 100644 Serializer.Tests/Serialization.Tests.csproj create mode 100644 Serializer.Tests/SerializerTests.cs create mode 100644 Serializer.Tests/TestData/CircularReference.cs create mode 100644 Serializer.Tests/TestData/ClassWithClassProperty.cs create mode 100644 Serializer.Tests/TestData/ClassWithDifferentAccessModifiers.cs create mode 100644 Serializer.Tests/TestData/ClassWithIEnumerable.cs create mode 100644 Serializer.Tests/TestData/ClassWithoutSerializableAttribute.cs create mode 100644 Serializer.Tests/TestData/CustomDictionaries.cs create mode 100644 Serializer.Tests/TestData/EnumForTesting.cs create mode 100644 Serializer.Tests/TestData/GenericBaseClass.cs create mode 100644 Serializer.Tests/TestData/HierarchyClasses.cs create mode 100644 Serializer.Tests/TestData/TestStruct.cs rename NetSerializer.sln => Serializer.sln (74%) diff --git a/.gitignore b/.gitignore index 0b34234..90cb24e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,240 @@ obj save packages + +########## + + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ + diff --git a/NetSerializer/TypeSerializers/ObjectSerializer.cs b/NetSerializer/TypeSerializers/ObjectSerializer.cs deleted file mode 100644 index 64eda23..0000000 --- a/NetSerializer/TypeSerializers/ObjectSerializer.cs +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2015 Tomi Valkeinen - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace NetSerializer -{ - sealed class ObjectSerializer : IStaticTypeSerializer - { - public bool Handles(Type type) - { - return type == typeof(object); - } - - public IEnumerable GetSubtypes(Type type) - { - return new Type[0]; - } - - public MethodInfo GetStaticWriter(Type type) - { - return typeof(ObjectSerializer).GetMethod("Serialize", BindingFlags.Static | BindingFlags.Public); - } - - public MethodInfo GetStaticReader(Type type) - { - return typeof(ObjectSerializer).GetMethod("Deserialize", BindingFlags.Static | BindingFlags.Public); - } - - public static void Serialize(Serializer serializer, Stream stream, object ob) - { - if (ob == null) - { - Primitives.WritePrimitive(stream, (uint)0); - return; - } - - var type = ob.GetType(); - - SerializeDelegate del; - - uint id = serializer.GetTypeIdAndSerializer(type, out del); - - Primitives.WritePrimitive(stream, id); - - del(serializer, stream, ob); - } - - public static void Deserialize(Serializer serializer, Stream stream, out object ob) - { - uint id; - - Primitives.ReadPrimitive(stream, out id); - - if (id == 0) - { - ob = null; - return; - } - - var del = serializer.GetDeserializeTrampolineFromId(id); - del(serializer, stream, out ob); - } - } -} diff --git a/PrimitiveTest/PrimitiveTest.csproj b/PrimitiveTest/PrimitiveTest.csproj index 3ab136a..15616af 100644 --- a/PrimitiveTest/PrimitiveTest.csproj +++ b/PrimitiveTest/PrimitiveTest.csproj @@ -40,9 +40,9 @@ - + {85A11D07-8D18-42D5-ACCF-EF9744EFE825} - NetSerializer + Serialization diff --git a/PrimitiveTest/Program.cs b/PrimitiveTest/Program.cs index 1c614d7..9bae5f3 100644 --- a/PrimitiveTest/Program.cs +++ b/PrimitiveTest/Program.cs @@ -4,6 +4,7 @@ using System.Text; using System.IO; using System.Diagnostics; +using Orckestra.Serialization; namespace PrimitiveTest { @@ -29,10 +30,10 @@ static void Main(string[] args) for (int i = 0; i < loops; ++i) { - NetSerializer.Primitives.WritePrimitive(stream, v1); - NetSerializer.Primitives.WritePrimitive(stream, v2); - NetSerializer.Primitives.WritePrimitive(stream, v3); - NetSerializer.Primitives.WritePrimitive(stream, v4); + Primitives.WritePrimitive(stream, v1); + Primitives.WritePrimitive(stream, v2); + Primitives.WritePrimitive(stream, v3); + Primitives.WritePrimitive(stream, v4); } sw.Stop(); @@ -55,10 +56,10 @@ static void Main(string[] args) for (int i = 0; i < loops; ++i) { - NetSerializer.Primitives.ReadPrimitive(stream, out v1); - NetSerializer.Primitives.ReadPrimitive(stream, out v2); - NetSerializer.Primitives.ReadPrimitive(stream, out v3); - NetSerializer.Primitives.ReadPrimitive(stream, out v4); + Primitives.ReadPrimitive(stream, out v1); + Primitives.ReadPrimitive(stream, out v2); + Primitives.ReadPrimitive(stream, out v3); + Primitives.ReadPrimitive(stream, out v4); } sw.Stop(); diff --git a/README b/README index 407585c..9ac7a29 100644 --- a/README +++ b/README @@ -1,268 +1,15 @@ -NetSerializer - A fast, simple serializer for .Net - -NetSerializer is a simple and very fast serializer for .Net languages. It is -the fastest serializer I have found for my use cases. - -The main pros of NetSerializer are: - -- Excellent for network serialization -- Supports classes, structs, enums, interfaces, abstract classes -- No versioning or other extra information is serialized, only pure data -- No type IDs for primitive types, structs or sealed classes, so less data to - be sent -- No dynamic type lookup for primitive types, structs or sealed classes, so - deserialization is faster -- No extra attributes needed (like DataContract/Member), just add the standard - [Serializable] -- Thread safe without locks -- The data is written to the stream and read from the stream directly, without - the need for temporary buffers or large buffers - -The simpleness of NetSerializer has a drawback which must be considered by the -user: no versioning or other meta information is sent, which means that the -sender and the receiver have to have the same versions of the types being -serialized. This means that it's a bad idea to save the serialized data for -longer periods of time, as a version upgrade could make the data -non-deserializable. For this reason I think the best (and perhaps only) use -for NetSerializer is for sending data over network, between a client and a -server which have verified version compatibility when the connection is made. - -Supported Types - -NetSerializer supports serializing the following types: - -- All primitive types (Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, - Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single) -- Strings -- Enums -- Single dimensional arrays -- Structs and classes marked as [Serializable]. Note that NetSerializer does - _not_ support ISerializable, so not all classes marked with [Serializable] - are supported. However, NetSerializer has special support for the following - ISerializable classes: -- Dictionary -- DateTime - -NetSerializer also supports creating custom serializers. Custom serializers can -be used to serialize types not directly supported by NetSerializer. - -Usage - -Usage is simple. The types to be serialized need to be marked with the standard -[Serializable]. You can also use [NonSerialized] for fields you don't want to -serialize. Nothing else needs to be done for the types to be serialized. - -Then you need to initialize NetSerializer by giving it a list of types you will -be serializing. NetSerializer will scan through the given types, and -recursively add all the types used by the given types, and create -(de)serialization code. - -Initialization: - - var ser = new Serializer(types); - -Serializing: - - ser.Serialize(stream, ob); - -Deserializing: - - (YourType)ser.Deserialize(stream); - - -Performance - -Below is a performance comparison between NetSerializer and protobuf-net. -Protobuf-net is a fast Protocol Buffers compatible serializer, which was the -best serializer I could find out there when I considered the serializer for -my use case. - -The tests create an array of N items of particular type, created with random -data. The items are then serialized, and this is repeated M times for the same -dataset. NetSerializer can also serialize types directly, without writing any -meta information or boxing of value types. These tests are marked with -"(direct)". - -The table lists the time it takes run the test, the number of GC collections -(per generation) that happened during the test, and the size of the -outputted serialized data (when available). - -There are three tests: - -- MemStream Serialize - serializes an array of objects to a memory stream. - -- MemStream Deserialize - deserializes the stream created with MemStream - Serialize test. - -- NetTest - uses two threads, of which the first one serializes objects and - sends them over a local socket, and the second one receive the data and - deserialize the objects. Note that the size is not available for NetTest, as - tracking the sent data is not trivial. However, the dataset is the same as - with MemStream, an so is the size of the data. - -The details of the tests can be found from the source code. The tests were run -on a 64bit Windows 10 laptop. - - | time (ms) | GC coll. | size (B) | -== 100 LargeStruct x 30000 == -NetSerializer | MemStream Serialize | 526 | 91 0 0 | 1859 | -NetSerializer | MemStream Deserialize | 439 | 91 0 0 | | -NetSerializer | NetTest | 751 | 184 0 0 | | -protobuf-net | MemStream Serialize | 987 | 381 0 0 | 2151 | -protobuf-net | MemStream Deserialize | 1586 | 183 0 0 | | -protobuf-net | NetTest | 2151 | 566 0 0 | | - -== 100 LargeStruct x 30000 (direct) == -NetSerializer | MemStream Serialize | 463 | 0 0 0 | 1759 | -NetSerializer | MemStream Deserialize | 413 | 0 0 0 | | -NetSerializer | NetTest | 661 | 0 0 0 | | - -== 100 Guid x 50000 == -NetSerializer | MemStream Serialize | 733 | 101 0 0 | 1964 | -NetSerializer | MemStream Deserialize | 509 | 101 0 0 | | -NetSerializer | NetTest | 939 | 204 0 0 | | -protobuf-net | MemStream Serialize | 4487 | 890 0 0 | 2100 | -protobuf-net | MemStream Deserialize | 4503 | 279 0 0 | | -protobuf-net | NetTest | 5803 | 1178 0 0 | | - -== 100 Guid x 50000 (direct) == -NetSerializer | MemStream Serialize | 642 | 0 0 0 | 1876 | -NetSerializer | MemStream Deserialize | 499 | 0 0 0 | | -NetSerializer | NetTest | 819 | 0 0 0 | | - -== 100 Int32 x 100000 == -NetSerializer | MemStream Serialize | 617 | 152 0 0 | 461 | -NetSerializer | MemStream Deserialize | 426 | 152 0 0 | | -NetSerializer | NetTest | 742 | 306 0 0 | | -protobuf-net | MemStream Serialize | 8778 | 1527 0 0 | 648 | -protobuf-net | MemStream Deserialize | 9078 | 560 14 0 | | -protobuf-net | NetTest | 11416 | 2103 1 0 | | - -== 100 Int32 x 100000 (direct) == -NetSerializer | MemStream Serialize | 468 | 0 0 0 | 361 | -NetSerializer | MemStream Deserialize | 428 | 0 0 0 | | -NetSerializer | NetTest | 580 | 0 0 0 | | - -== 100 U8Message x 100000 == -NetSerializer | MemStream Serialize | 389 | 0 0 0 | 200 | -NetSerializer | MemStream Deserialize | 991 | 152 0 0 | | -NetSerializer | NetTest | 1137 | 152 0 0 | | -protobuf-net | MemStream Serialize | 3007 | 966 0 0 | 527 | -protobuf-net | MemStream Deserialize | 5745 | 152 0 0 | | -protobuf-net | NetTest | 6925 | 1122 0 0 | | - -== 100 U8Message x 100000 (direct) == -NetSerializer | MemStream Serialize | 281 | 0 0 0 | 100 | -NetSerializer | MemStream Deserialize | 1047 | 152 0 0 | | -NetSerializer | NetTest | 1156 | 152 0 0 | | - -== 100 S16Message x 100000 == -NetSerializer | MemStream Serialize | 512 | 0 0 0 | 341 | -NetSerializer | MemStream Deserialize | 1110 | 152 0 0 | | -NetSerializer | NetTest | 1347 | 152 0 0 | | -protobuf-net | MemStream Serialize | 3204 | 966 0 0 | 844 | -protobuf-net | MemStream Deserialize | 5986 | 152 0 0 | | -protobuf-net | NetTest | 7325 | 1122 0 0 | | - -== 100 S32Message x 100000 == -NetSerializer | MemStream Serialize | 593 | 0 0 0 | 461 | -NetSerializer | MemStream Deserialize | 1207 | 152 0 0 | | -NetSerializer | NetTest | 1475 | 152 0 0 | | -protobuf-net | MemStream Serialize | 3371 | 966 0 0 | 828 | -protobuf-net | MemStream Deserialize | 6066 | 152 0 0 | | -protobuf-net | NetTest | 7535 | 1120 0 0 | | - -== 100 S64Message x 100000 == -NetSerializer | MemStream Serialize | 699 | 0 0 0 | 542 | -NetSerializer | MemStream Deserialize | 1313 | 152 0 0 | | -NetSerializer | NetTest | 1603 | 152 0 0 | | -protobuf-net | MemStream Serialize | 3512 | 966 0 0 | 809 | -protobuf-net | MemStream Deserialize | 6268 | 152 0 0 | | -protobuf-net | NetTest | 7672 | 1124 0 0 | | - -== 100 DecimalMessage x 50000 == -NetSerializer | MemStream Serialize | 648 | 127 0 0 | 1360 | -NetSerializer | MemStream Deserialize | 934 | 101 0 0 | | -NetSerializer | NetTest | 1292 | 229 0 0 | | -protobuf-net | MemStream Serialize | 2716 | 610 0 0 | 2022 | -protobuf-net | MemStream Deserialize | 4220 | 101 0 0 | | -protobuf-net | NetTest | 5052 | 713 0 0 | | - -== 100 NullableDecimalMessage x 100000 == -NetSerializer | MemStream Serialize | 441 | 7 0 0 | 238 | -NetSerializer | MemStream Deserialize | 1156 | 254 0 0 | | -NetSerializer | NetTest | 1340 | 262 0 0 | | -protobuf-net | MemStream Serialize | 4033 | 973 0 0 | 353 | -protobuf-net | MemStream Deserialize | 6727 | 254 0 0 | | -protobuf-net | NetTest | 8203 | 1233 0 0 | | - -== 100 PrimitivesMessage x 10000 == -NetSerializer | MemStream Serialize | 703 | 25 0 0 | 5286 | -NetSerializer | MemStream Deserialize | 748 | 76 0 0 | | -NetSerializer | NetTest | 1010 | 102 0 0 | | -protobuf-net | MemStream Serialize | 725 | 96 0 0 | 7290 | -protobuf-net | MemStream Deserialize | 1089 | 50 0 0 | | -protobuf-net | NetTest | 1348 | 148 0 0 | | - -== 10 DictionaryMessage x 1000 == -NetSerializer | MemStream Serialize | 1310 | 75 0 0 | 86187 | -NetSerializer | MemStream Deserialize | 1955 | 109 54 0 | | -NetSerializer | NetTest | 2521 | 135 67 0 | | -protobuf-net | MemStream Serialize | 1713 | 413 0 0 | 142035 | -protobuf-net | MemStream Deserialize | 3576 | 233 116 0 | | -protobuf-net | NetTest | 4693 | 494 247 11 | | - -== 100 ComplexMessage x 10000 == -NetSerializer | MemStream Serialize | 410 | 0 0 0 | 2838 | -NetSerializer | MemStream Deserialize | 608 | 100 0 0 | | -NetSerializer | NetTest | 812 | 100 0 0 | | -protobuf-net | MemStream Serialize | 838 | 96 0 0 | 5087 | -protobuf-net | MemStream Deserialize | 1902 | 100 0 0 | | -protobuf-net | NetTest | 2195 | 198 0 0 | | - -== 100 StringMessage x 20000 == -NetSerializer | MemStream Serialize | 447 | 0 0 0 | 4886 | -NetSerializer | MemStream Deserialize | 655 | 182 0 0 | | -NetSerializer | NetTest | 903 | 182 0 0 | | -protobuf-net | MemStream Serialize | 1043 | 193 0 0 | 5085 | -protobuf-net | MemStream Deserialize | 3973 | 182 0 0 | | -protobuf-net | NetTest | 2428 | 378 0 0 | | - -== 100 StructMessage x 20000 == -NetSerializer | MemStream Serialize | 528 | 0 0 0 | 2455 | -NetSerializer | MemStream Deserialize | 681 | 117 0 0 | | -NetSerializer | NetTest | 909 | 117 0 0 | | -protobuf-net | MemStream Serialize | 1369 | 274 0 0 | 3622 | -protobuf-net | MemStream Deserialize | 4297 | 280 0 0 | | -protobuf-net | NetTest | 2752 | 557 0 0 | | - -== 100 BoxedPrimitivesMessage x 20000 == -NetSerializer | MemStream Serialize | 708 | 0 0 0 | 1723 | -NetSerializer | MemStream Deserialize | 570 | 223 0 0 | | -NetSerializer | NetTest | 862 | 223 0 0 | | - -== 10000 ByteArrayMessage x 1 == -NetSerializer | MemStream Serialize | 736 | 1 1 1 | 498085311 | -NetSerializer | MemStream Deserialize | 325 | 58 29 1 | | -NetSerializer | NetTest | 889 | 58 27 0 | | -protobuf-net | MemStream Serialize | 1329 | 341 6 3 | 498151945 | -protobuf-net | MemStream Deserialize | 418 | 58 29 1 | | -protobuf-net | NetTest | 1696 | 172 40 2 | | - -== 1000 IntArrayMessage x 1 == -NetSerializer | MemStream Serialize | 1533 | 0 0 0 | 177278871 | -NetSerializer | MemStream Deserialize | 1147 | 2 1 0 | | -NetSerializer | NetTest | 1821 | 3 1 0 | | -protobuf-net | MemStream Serialize | 1849 | 79 4 2 | 283510795 | -protobuf-net | MemStream Deserialize | 1720 | 28 3 0 | | -protobuf-net | NetTest | 2620 | 88 5 2 | | - -== 10 TriDimArrayCustomSerializersMessage x 100 == -NetSerializer | MemStream Serialize | 1246 | 0 0 0 | 1601277 | -NetSerializer | MemStream Deserialize | 1175 | 30 27 25 | | -NetSerializer | NetTest | 1844 | 43 41 38 | | - -As can be seen from the tests, NetSerializer is clearly faster and has smaller -memory footprint in about all of the cases. For example, the tests with -ComplexMessages show NetSerializer's MemStream Serialize cause zero garbage -collections, even though more than 20MB of data is being serialized. +This project is a fork of [NetSerializer](https://github.com/tomba/netserializer) and add the following functionality: + +- No longer requires the classes to be decorated with [Serializable] + - Classes from the System.* and Microsoft.* namespaces without [Serializable] are not supported and will not be serialized +- Types to serialize are automatically detected. + - This is achieved by adding the type's name to the serialized stream instead of the 'TypeId' value + - Fields are still serialized in the same order as NetSerializer so client and server must still have the same version. An exception will be thrown if a class was modified since it was serialized (e.g.: different number of fields). +- Throws an exception when a circular reference is detected instead of generating a stack overflow +- A new dictionary serializer was created to: + - Allow inheriting from a Dictionary<,> + - Serialize the comparer + - This new serializer requires a parameter less constructor +- New serializers: + - Hashset + - Hashtable \ No newline at end of file diff --git a/Serialization/Exceptions/CircularReferenceException.cs b/Serialization/Exceptions/CircularReferenceException.cs new file mode 100644 index 0000000..3e6b6ed --- /dev/null +++ b/Serialization/Exceptions/CircularReferenceException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Orckestra.Serialization +{ + public class CircularReferenceException : Exception + { + public CircularReferenceException() + : base() {} + + public CircularReferenceException(string message) + : base(message) {} + } +} \ No newline at end of file diff --git a/Serialization/Exceptions/RequiredPublicParameterlessConstructorException.cs b/Serialization/Exceptions/RequiredPublicParameterlessConstructorException.cs new file mode 100644 index 0000000..40e64ca --- /dev/null +++ b/Serialization/Exceptions/RequiredPublicParameterlessConstructorException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Orckestra.Serialization +{ + public class RequiredPublicParameterlessConstructorException : Exception + { + public RequiredPublicParameterlessConstructorException(Type type) + : base(String.Format("Type {0} requires a parameterless public contructor.", + type)) {} + } +} \ No newline at end of file diff --git a/Serialization/Exceptions/TypeNotFoundException.cs b/Serialization/Exceptions/TypeNotFoundException.cs new file mode 100644 index 0000000..b37e157 --- /dev/null +++ b/Serialization/Exceptions/TypeNotFoundException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Orckestra.Serialization +{ + public class TypeNotFoundException : Exception + { + public TypeNotFoundException() + : base() {} + + public TypeNotFoundException(string message) + : base(message) {} + } +} \ No newline at end of file diff --git a/NetSerializer/Helpers.cs b/Serialization/Helpers.cs similarity index 74% rename from NetSerializer/Helpers.cs rename to Serialization/Helpers.cs index 0f3c447..3c9c8f2 100644 --- a/NetSerializer/Helpers.cs +++ b/Serialization/Helpers.cs @@ -8,22 +8,61 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; -using System.Text; -namespace NetSerializer +namespace Orckestra.Serialization { static class Helpers { - public static IEnumerable GetFieldInfos(Type type) + public static void ValidateIfSerializable(this Type type) + { + if (typeof(MulticastDelegate).IsAssignableFrom(type)) + { + throw new NotSupportedException(String.Format("Delegate type {0} is not serializable.", type.FullName)); + } + if (typeof(Expression).IsAssignableFrom(type)) + { + throw new NotSupportedException(String.Format("Expression of type {0} is not serializable.", type.FullName)); + } + + if (type.Namespace != null + && (type.Namespace.StartsWith("System") || type.Namespace.StartsWith("Microsoft")) + && !type.IsDictionary()) + { + if (!type.IsSerializable) + { + throw new NotSupportedException(String.Format(".Net type {0} is not marked as Serializable", + type.FullName)); + } + + if (typeof(System.Runtime.Serialization.ISerializable).IsAssignableFrom(type)) + { + throw new NotSupportedException(String.Format("Cannot serialize {0}: ISerializable not supported", + type.FullName)); + } + } + } + + public static bool IsDictionary(this Type type) + { + if (!type.IsGenericType) + return false; + + var genTypeDef = type.GetGenericTypeDefinition(); + + return genTypeDef == typeof(Dictionary<,>); + } + + public static IEnumerable GetFieldInfos(Type type) { - Debug.Assert(type.IsSerializable); + type.ValidateIfSerializable(); + //Debug.Assert(type.IsSerializable); - var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) + var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) .Where(fi => (fi.Attributes & FieldAttributes.NotSerialized) == 0) .OrderBy(f => f.Name, StringComparer.Ordinal); diff --git a/NetSerializer/ITypeSerializer.cs b/Serialization/ITypeSerializer.cs similarity index 97% rename from NetSerializer/ITypeSerializer.cs rename to Serialization/ITypeSerializer.cs index 9dd5aaa..da7c5e1 100644 --- a/NetSerializer/ITypeSerializer.cs +++ b/Serialization/ITypeSerializer.cs @@ -11,7 +11,7 @@ using System.Reflection; using System.Reflection.Emit; -namespace NetSerializer +namespace Orckestra.Serialization { public interface ITypeSerializer { diff --git a/NetSerializer/NetSerializer.nuspec b/Serialization/NetSerializer.nuspec similarity index 100% rename from NetSerializer/NetSerializer.nuspec rename to Serialization/NetSerializer.nuspec diff --git a/NetSerializer/Primitives.cs b/Serialization/Primitives.cs similarity index 99% rename from NetSerializer/Primitives.cs rename to Serialization/Primitives.cs index 8204576..64c0430 100644 --- a/NetSerializer/Primitives.cs +++ b/Serialization/Primitives.cs @@ -7,13 +7,11 @@ */ using System; -using System.Linq; using System.IO; using System.Reflection; using System.Text; -using System.Collections.Generic; -namespace NetSerializer +namespace Orckestra.Serialization { public static class Primitives { diff --git a/Serialization/Properties/AssemblyInfo.cs b/Serialization/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4b97297 --- /dev/null +++ b/Serialization/Properties/AssemblyInfo.cs @@ -0,0 +1,32 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orckestra.Serializer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Serializer")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("46f876f8-4fa5-4d3b-9619-f47b03fa6c95")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Serialization/ReferenceWatcher.cs b/Serialization/ReferenceWatcher.cs new file mode 100644 index 0000000..5862ed0 --- /dev/null +++ b/Serialization/ReferenceWatcher.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace Orckestra.Serialization +{ + internal class ReferenceWatcher : IDisposable + { + [ThreadStatic] + private static ReferenceWatcher watcher; + [ThreadStatic] + private static HashSet trackedObjects; + + internal static ReferenceWatcher Current + { + get + { + return ReferenceWatcher.watcher; + } + } + + internal ReferenceWatcher() + { + if (Current != null) + { + throw new InvalidOperationException("Cannot instantiate a new ReferenceWatcher while there is an instance of ReferenceWatcher on the current thread."); + } + + Thread.BeginThreadAffinity(); + ReferenceWatcher.watcher = this; + ReferenceWatcher.trackedObjects = new HashSet(); + } + + private bool disposed = false; + public void Dispose() + { + if (disposed) + { + throw new ObjectDisposedException(typeof(ReferenceWatcher).FullName); + } + disposed = true; + ReferenceWatcher.watcher = null; + ReferenceWatcher.trackedObjects = null; + Thread.EndThreadAffinity(); + } + + internal void TrackObject(object obj) + { + if (obj == null) + { + return; + } + + Debug.Assert(obj.GetType() + .IsClass); + + if (trackedObjects.Contains(obj)) + { + throw new CircularReferenceException(string.Format("Object {0} has already been serialized possibly due to a circular reference.", + obj.GetType())); + } + trackedObjects.Add(obj); + } + + internal void RemoveObject(object obj) + { + if (obj == null) + { + return; + } + + Debug.Assert(obj.GetType() + .IsClass); + + if (!trackedObjects.Contains(obj)) + { + throw new InvalidOperationException(string.Format("Object {0} is not tracked by the ReferenceWatcher.", + obj.GetType())); + } + trackedObjects.Remove(obj); + } + } +} diff --git a/NetSerializer/NetSerializer.csproj b/Serialization/Serialization.csproj similarity index 77% rename from NetSerializer/NetSerializer.csproj rename to Serialization/Serialization.csproj index 7196cc3..b118235 100644 --- a/NetSerializer/NetSerializer.csproj +++ b/Serialization/Serialization.csproj @@ -8,8 +8,8 @@ {85A11D07-8D18-42D5-ACCF-EF9744EFE825} Library Properties - NetSerializer - NetSerializer + Orckestra.Serialization + Orckestra.Serialization v4.5 512 @@ -35,13 +35,26 @@ true false + + true + + + SerializerKey.snk + + + + + + + + @@ -56,6 +69,9 @@ + + + + \ No newline at end of file diff --git a/Serializer.Tests/SerializerTests.cs b/Serializer.Tests/SerializerTests.cs new file mode 100644 index 0000000..f308dcf --- /dev/null +++ b/Serializer.Tests/SerializerTests.cs @@ -0,0 +1,814 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Orckestra.Serialization; +using Serialization.Tests.TestData; + +namespace Serialization.Tests +{ + [TestClass] + public class SerializerTests + { + [TestMethod] + public void SerializePrimitives() + { + SerializePrimitive((byte) 1); + SerializePrimitive((sbyte) 1); + SerializePrimitive((Int32) 1); + SerializePrimitive((UInt32) 1); + SerializePrimitive((Int16) 1); + SerializePrimitive((UInt16) 1); + SerializePrimitive((Int64) 1); + SerializePrimitive((UInt64) 1); + SerializePrimitive((BigInteger) 1); + SerializePrimitive((Decimal) 1.1M); + SerializePrimitive((Double) 1.1D); + SerializePrimitive((Single) 1.1F); + SerializePrimitive(new StructForTesting + { + Value = 38 + }); + SerializePrimitive(EnumForTesting.Two); + SerializePrimitive("Test text"); + SerializePrimitive('c'); + SerializePrimitive(new Tuple(1, "a")); + SerializePrimitive(DateTime.UtcNow); + + SerializePrimitive((byte?)1); + SerializePrimitive((sbyte?)1); + SerializePrimitive((byte?)null); + SerializePrimitive((sbyte?)null); + SerializePrimitive((Int32?)1); + SerializePrimitive((UInt32?)1); + SerializePrimitive((Int32?)null); + SerializePrimitive((UInt32?)null); + SerializePrimitive((Int16?)1); + SerializePrimitive((UInt16?)1); + SerializePrimitive((Int16?)null); + SerializePrimitive((UInt16?)null); + SerializePrimitive((Int64?)1); + SerializePrimitive((UInt64?)1); + SerializePrimitive((Int64?)null); + SerializePrimitive((UInt64?)null); + SerializePrimitive((BigInteger?)1); + SerializePrimitive((BigInteger?)null); + SerializePrimitive((Decimal?)1.1M); + SerializePrimitive((Decimal?)1.1M); + SerializePrimitive((Double?)1.1D); + SerializePrimitive((Double?)null); + SerializePrimitive((Single?)1.1F); + SerializePrimitive((Single?)null); + SerializePrimitive((DateTime?)DateTime.UtcNow); + SerializePrimitive((DateTime?)null); + } + + private void SerializePrimitive(T value) + { + SerializePrimitiveValue(value); + SerializePrimitiveArray(value); + SerializePrimitiveList(value); + } + + private static void SerializePrimitiveValue(T value) + { + using (var memoryStream = new MemoryStream()) + { + Type targetType = typeof(T); + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + value); + memoryStream.Position = 0; + + T deserializedValue = (T) serialiser.Deserialize(memoryStream); + + Assert.AreEqual(value, + deserializedValue, + string.Format("Type {0} does not have the same value after being deserialized.", + targetType)); + } + } + + private static void SerializePrimitiveArray(T value) + { + using (var memoryStream = new MemoryStream()) + { + var array = new T[1]; + array[0] = value; + + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + array); + memoryStream.Position = 0; + + T[] deserializedValue = (T[])serialiser.Deserialize(memoryStream); + + CollectionAssert.AreEquivalent(array, + deserializedValue); + } + } + + private static void SerializePrimitiveList(T value) + { + using (var memoryStream = new MemoryStream()) + { + var list = new List + { + value + }; + + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + list); + memoryStream.Position = 0; + + List deserializedValue = (List)serialiser.Deserialize(memoryStream); + + CollectionAssert.AreEquivalent(list, + deserializedValue); + } + } + + [TestMethod] + public void SerializeTestClass() + { + ClassWithDifferentAccessModifiers classInstance = new ClassWithDifferentAccessModifiers + { + PublicFieldValue = 1, + InternalFieldValue = 3, + PublicPropertyValue = 4, + InternalPropertyValue = 6 + }; + classInstance.SetPrivateFieldValue(2); + classInstance.SetPrivatePropertyValue(5); + + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + classInstance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (ClassWithDifferentAccessModifiers)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(classInstance.PublicFieldValue, + deserializedValue.PublicFieldValue); + Assert.AreEqual(classInstance.GetPrivateFieldValue(), + deserializedValue.GetPrivateFieldValue()); + Assert.AreEqual(classInstance.InternalFieldValue, + deserializedValue.InternalFieldValue); + Assert.AreEqual(classInstance.PublicPropertyValue, + deserializedValue.PublicPropertyValue); + Assert.AreEqual(classInstance.GetPrivatePropertyValue(), + deserializedValue.GetPrivatePropertyValue()); + Assert.AreEqual(classInstance.InternalPropertyValue, + deserializedValue.InternalPropertyValue); + } + } + + [TestMethod] + [ExpectedException(typeof(CircularReferenceException))] + public void SerializeCircularReference() + { + var instance1 = new CircularReference(); + var instance2 = new CircularReference(); + instance1.Parent = instance2; + instance2.Child = instance1; + + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance1); + memoryStream.Position = 0; + + var deserializedValue = (CircularReference) serialiser.Deserialize(memoryStream); + + Assert.IsTrue(Object.ReferenceEquals(deserializedValue, + deserializedValue.Child)); + } + } + + [TestMethod] + public void SerializeTrackMultipleReference() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + var instance = new IntGenericBaseClass(123); + var list = new List + { + instance, + instance + }; + + serialiser.Serialize(memoryStream, + list); + memoryStream.Position = 0; + + var deserializedValue = (List) serialiser.Deserialize(memoryStream); + + Assert.AreEqual(list.Count, + deserializedValue.Count); + CollectionAssert.AreEquivalent(list, + deserializedValue); + } + } + + [TestMethod] + public void SerializeTrackSamePrimitiveMultipleTimes() + { + // this case exists to make sure that the ReferenceWatcher only tracks classes + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + var instance = 3; + var list = new List + { + instance, + instance + }; + + serialiser.Serialize(memoryStream, + list); + memoryStream.Position = 0; + + var deserializedValue = (List) serialiser.Deserialize(memoryStream); + + Assert.AreEqual(list.Count, + deserializedValue.Count); + CollectionAssert.AreEquivalent(list, + deserializedValue); + } + } + + [TestMethod] + public void SerializeClassWithoutSerializableAttribute() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new ClassWithoutSerializableAttribute + { + PublicPropertyValue = 4 + }; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (ClassWithoutSerializableAttribute) serialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.PublicPropertyValue, + deserializedValue.PublicPropertyValue); + } + } + + [TestMethod] + public void SerializeClassWithGenericBase() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new IntGenericBaseClass() + { + Value = 4 + }; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (IntGenericBaseClass)serialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Value, + deserializedValue.Value); + } + } + + [TestMethod] + public void SerializeGenericClass() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new GenericBaseClass() + { + Value = 4 + }; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (GenericBaseClass)serialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Value, + deserializedValue.Value); + } + } + + [TestMethod] + public void SerializeDictionaryStringObject() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new Dictionary() + { + {"Key1", 123}, + {"Key2", "abc"}, + {"Key3", new IntGenericBaseClass(3) }, + }; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (Dictionary)serialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Count, + deserializedValue.Count); + CollectionAssert.AreEquivalent(instance.Keys, + deserializedValue.Keys); + foreach (var kvp in instance) + { + Assert.AreEqual(kvp.Value, + deserializedValue[kvp.Key]); + } + } + } + + [TestMethod] + public void SerializeCustomDictionary() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new CustomDictionary + { + {"Key1", 123}, + {"Key2", "abc"}, + {"Key3", new IntGenericBaseClass(3) }, + }; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (CustomDictionary)serialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Count, + deserializedValue.Count); + CollectionAssert.AreEquivalent(instance.Keys, + deserializedValue.Keys); + foreach (var kvp in instance) + { + Assert.AreEqual(kvp.Value, + deserializedValue[kvp.Key]); + } + } + } + + [TestMethod] + public void SerializeCustomDictionaryWithComparer() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new CustomDictionary(StringComparer.CurrentCultureIgnoreCase) + { + {"Key1", 123}, + {"Key2", "abc"}, + {"Key3", new IntGenericBaseClass(3) }, + }; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (CustomDictionary)serialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Comparer.GetType(), + deserializedValue.Comparer.GetType()); + Assert.AreEqual(instance.Count, + deserializedValue.Count); + CollectionAssert.AreEquivalent(instance.Keys, + deserializedValue.Keys); + foreach (var kvp in instance) + { + Assert.AreEqual(kvp.Value, + deserializedValue[kvp.Key]); + } + } + } + + [TestMethod] + [ExpectedException(typeof(RequiredPublicParameterlessConstructorException))] + public void SerializeCustomDictionaryWithoutPublicParameterlessConstructor() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new CustomDictionaryWithoutPublicParameterlessConstructor(3) + { + {"Key1", 123}, + {"Key2", "abc"}, + {"Key3", new IntGenericBaseClass(3) }, + }; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (CustomDictionaryWithoutPublicParameterlessConstructor)serialiser.Deserialize(memoryStream); + } + } + + [TestMethod] + public void SerializeCustomDictionaryWithAdditionalProperties() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new CustomDictionaryWithAdditionalProperties + { + {"Key1", 123}, + {"Key2", "abc"}, + {"Key3", new IntGenericBaseClass(3) }, + }; + instance.SomeProperty = 849; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (CustomDictionaryWithAdditionalProperties)serialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.SomeProperty, + deserializedValue.SomeProperty); + Assert.AreEqual(instance.Count, + deserializedValue.Count); + CollectionAssert.AreEquivalent(instance.Keys, + deserializedValue.Keys); + foreach (var kvp in instance) + { + Assert.AreEqual(kvp.Value, + deserializedValue[kvp.Key]); + } + } + } + + [TestMethod] + public void SerializeCustomDictionaryWithAdditionalPropertiesAndGenerics() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new CustomDictionaryWithAdditionalPropertiesAndGenerics + { + {"Key1", 123}, + {"Key2", 456}, + {"Key3", 789}, + }; + instance.SomeProperty = 849; + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (CustomDictionaryWithAdditionalPropertiesAndGenerics)serialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.SomeProperty, + deserializedValue.SomeProperty); + Assert.AreEqual(instance.Count, + deserializedValue.Count); + CollectionAssert.AreEquivalent(instance.Keys, + deserializedValue.Keys); + foreach (var kvp in instance) + { + Assert.AreEqual(kvp.Value, + deserializedValue[kvp.Key]); + } + } + } + + [TestMethod] + public void SerializeTestClassWithAdditionalTypeRoots() + { + // this test exists to test the way that the type are written to the stream + // by default the NetSerializer writes the TypeId in the stream instead of the type name + // which is incompatible with our changes to automatically detect the root types + + ClassWithClassProperty classInstance = new ClassWithClassProperty + { + Value = new IntGenericBaseClass(123) + }; + + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(new[] + { + typeof(ClassWithClassProperty), + typeof(StructForTesting), + typeof(IntGenericBaseClass), + }); + + serialiser.Serialize(memoryStream, + classInstance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(new [] + { + typeof(IntGenericBaseClass), + typeof(StructForTesting), + typeof(ClassWithClassProperty), + }); + var deserializedValue = (ClassWithClassProperty)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(classInstance.Value.Value, + deserializedValue.Value.Value); + } + } + + [TestMethod] + public void SerializeListWithMultipleTypes() + { + var list = new List + { + new ChildIntHierarchy(123), + new ChildStringHierarchy("abc"), + }; + + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + + serialiser.Serialize(memoryStream, + list); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (List)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(list.Count, + deserializedValue.Count); + Assert.AreEqual(list.OfType().FirstOrDefault().Value, + deserializedValue.OfType().FirstOrDefault().Value); + Assert.AreEqual(list.OfType().FirstOrDefault().Value, + deserializedValue.OfType().FirstOrDefault().Value); + } + } + + [TestMethod] + public void SerializeObjectWithListAsIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + var instance = new ClassWithIEnumerable(); + instance.Items = new List + { + 1, + 2, + 3 + }; + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithHashsetAsIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + var instance = new ClassWithIEnumerable(); + instance.Items = new HashSet + { + 1, + 2, + 3 + }; + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithEnumProperty() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + var instance = new GenericBaseClass(); + instance.Value = EnumForTesting.Two; + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (GenericBaseClass)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Value, + deserializedValue.Value); + } + } + + [TestMethod] + public void SerializeHashtable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + var instance = new Hashtable + { + {1, 2}, + {"a", "b"}, + }; + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (Hashtable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Count, + deserializedValue.Count); + CollectionAssert.AreEquivalent(instance, + deserializedValue); + } + } + + [TestMethod] + public void SerializeObjectWithArrayAsIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + var instance = new ClassWithIEnumerable(); + instance.Items = new int[] + { + 1, + 2, + 3 + }; + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithDistinctIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + var instance = new ClassWithIEnumerable(); + instance.Items = new List + { + 1, + 1, + 2, + 3 + }.Distinct(); + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void SerializeSimpleFunc() + { + var testData = new List { 1, 2, 3, 4, 5, 6 }; + + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + System.Func instance = x => x > 3; + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (System.Func)deserialiser.Deserialize(memoryStream); + + Assert.IsNotNull(deserializedValue); + + Assert.AreEqual(testData.Count(x => instance(x)), + testData.Count(x => deserializedValue(x))); + } + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void SerializeSimpleExpression() + { + var testData = new List { 1, 2, 3, 4, 5, 6 }; + + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Orckestra.Serialization.Serializer(); + Expression> instance = x => x > 3; + + serialiser.Serialize(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Orckestra.Serialization.Serializer(); + var deserializedValue = (Expression>)deserialiser.Deserialize(memoryStream); + + Assert.IsNotNull(deserializedValue); + + Assert.AreEqual(testData.Count(instance.Compile()), + testData.Count(deserializedValue.Compile())); + } + } + + // + + + + + + + + + //[TestMethod] + //public void zzSerializeClassWithoutRootType() + //{ + // using (var memoryStream = new MemoryStream()) + // { + // var instance = new GenericBaseClass + // { + // Value = 4 + // }; + // var serialiser = new Serializer(); + + // serialiser.Serialize(memoryStream, + // instance); + // memoryStream.Position = 0; + + // var deserializedValue = (GenericBaseClass)serialiser.Deserialize(memoryStream); + + // Assert.AreEqual(instance.Value, + // deserializedValue.Value); + // } + //} + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/CircularReference.cs b/Serializer.Tests/TestData/CircularReference.cs new file mode 100644 index 0000000..d390a4c --- /dev/null +++ b/Serializer.Tests/TestData/CircularReference.cs @@ -0,0 +1,11 @@ +using System; + +namespace Serialization.Tests.TestData +{ + [Serializable] + public class CircularReference + { + public CircularReference Parent { get; set; } + public CircularReference Child { get; set; } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/ClassWithClassProperty.cs b/Serializer.Tests/TestData/ClassWithClassProperty.cs new file mode 100644 index 0000000..a4eacfc --- /dev/null +++ b/Serializer.Tests/TestData/ClassWithClassProperty.cs @@ -0,0 +1,10 @@ +using System; + +namespace Serialization.Tests.TestData +{ + [Serializable] + public class ClassWithClassProperty + { + public IntGenericBaseClass Value { get; set; } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/ClassWithDifferentAccessModifiers.cs b/Serializer.Tests/TestData/ClassWithDifferentAccessModifiers.cs new file mode 100644 index 0000000..5c8246e --- /dev/null +++ b/Serializer.Tests/TestData/ClassWithDifferentAccessModifiers.cs @@ -0,0 +1,34 @@ +using System; + +namespace Serialization.Tests.TestData +{ + [Serializable] + public class ClassWithDifferentAccessModifiers + { + public int PublicFieldValue; + private int PrivateFieldValue; + internal int InternalFieldValue; + + public int PublicPropertyValue { get; set; } + private int PrivatePropertyValue { get; set; } + internal int InternalPropertyValue { get; set; } + + public void SetPrivateFieldValue(int value) + { + PrivateFieldValue = value; + } + public int GetPrivateFieldValue() + { + return PrivateFieldValue; + } + + public void SetPrivatePropertyValue(int value) + { + PrivatePropertyValue = value; + } + public int GetPrivatePropertyValue() + { + return PrivatePropertyValue; + } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/ClassWithIEnumerable.cs b/Serializer.Tests/TestData/ClassWithIEnumerable.cs new file mode 100644 index 0000000..90c25db --- /dev/null +++ b/Serializer.Tests/TestData/ClassWithIEnumerable.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace Serialization.Tests.TestData +{ + [Serializable] + public class ClassWithIEnumerable + { + public IEnumerable Items { get; set; } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/ClassWithoutSerializableAttribute.cs b/Serializer.Tests/TestData/ClassWithoutSerializableAttribute.cs new file mode 100644 index 0000000..44d6e43 --- /dev/null +++ b/Serializer.Tests/TestData/ClassWithoutSerializableAttribute.cs @@ -0,0 +1,7 @@ +namespace Serialization.Tests.TestData +{ + public class ClassWithoutSerializableAttribute + { + public int PublicPropertyValue { get; set; } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/CustomDictionaries.cs b/Serializer.Tests/TestData/CustomDictionaries.cs new file mode 100644 index 0000000..ce918cd --- /dev/null +++ b/Serializer.Tests/TestData/CustomDictionaries.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace Serialization.Tests.TestData +{ + [Serializable] + public class CustomDictionary : Dictionary + { + public CustomDictionary() + : base() + { + + } + public CustomDictionary(IEqualityComparer comparer) + : base(comparer) + { + } + } + + [Serializable] + public class CustomDictionaryWithAdditionalProperties : Dictionary + { + public int SomeProperty { get; set; } + } + + [Serializable] + public class CustomDictionaryWithAdditionalPropertiesAndGenerics : Dictionary + { + public int SomeProperty { get; set; } + } + + [Serializable] + public class CustomDictionaryWithoutPublicParameterlessConstructor : Dictionary + { + public CustomDictionaryWithoutPublicParameterlessConstructor(int capacity) + : base(capacity) + { + + } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/EnumForTesting.cs b/Serializer.Tests/TestData/EnumForTesting.cs new file mode 100644 index 0000000..953977a --- /dev/null +++ b/Serializer.Tests/TestData/EnumForTesting.cs @@ -0,0 +1,12 @@ +using System; + +namespace Serialization.Tests.TestData +{ + [Serializable] + public enum EnumForTesting + { + Zero = 0, + One = 1, + Two = 2 + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/GenericBaseClass.cs b/Serializer.Tests/TestData/GenericBaseClass.cs new file mode 100644 index 0000000..f345a98 --- /dev/null +++ b/Serializer.Tests/TestData/GenericBaseClass.cs @@ -0,0 +1,40 @@ +using System; + +namespace Serialization.Tests.TestData +{ + [Serializable] + public class GenericBaseClass + { + public T Value { get; set; } + } + + [Serializable] + public class IntGenericBaseClass : GenericBaseClass + { + public IntGenericBaseClass() + { + + } + + public IntGenericBaseClass(int value) + { + Value = value; + } + + public override bool Equals(object obj) + { + var other = obj as IntGenericBaseClass; + if (other == null) + { + return false; + } + + return Value == other.Value; + } + + public override int GetHashCode() + { + return "IntGenericBaseClass".GetHashCode() + Value.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/HierarchyClasses.cs b/Serializer.Tests/TestData/HierarchyClasses.cs new file mode 100644 index 0000000..9a2d5fb --- /dev/null +++ b/Serializer.Tests/TestData/HierarchyClasses.cs @@ -0,0 +1,32 @@ +using System; + +namespace Serialization.Tests.TestData +{ + public interface IHierarchy + { + } + + [Serializable] + public class BaseHierarchy : IHierarchy + { + public T Value { get; set; } + } + + [Serializable] + public class ChildIntHierarchy : BaseHierarchy + { + public ChildIntHierarchy(int value) + { + Value = value; + } + } + + [Serializable] + public class ChildStringHierarchy : BaseHierarchy + { + public ChildStringHierarchy(string value) + { + Value = value; + } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/TestStruct.cs b/Serializer.Tests/TestData/TestStruct.cs new file mode 100644 index 0000000..35a820f --- /dev/null +++ b/Serializer.Tests/TestData/TestStruct.cs @@ -0,0 +1,10 @@ +using System; + +namespace Serialization.Tests.TestData +{ + [Serializable] + public struct StructForTesting + { + public int Value; + } +} diff --git a/NetSerializer.sln b/Serializer.sln similarity index 74% rename from NetSerializer.sln rename to Serializer.sln index 8be0ec5..81fa025 100644 --- a/NetSerializer.sln +++ b/Serializer.sln @@ -3,12 +3,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetSerializer", "NetSerializer\NetSerializer.csproj", "{85A11D07-8D18-42D5-ACCF-EF9744EFE825}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serialization", "Serialization\Serialization.csproj", "{85A11D07-8D18-42D5-ACCF-EF9744EFE825}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{DD1A3698-ED72-49C8-874F-F721EA8012B6}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrimitiveTest", "PrimitiveTest\PrimitiveTest.csproj", "{CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serialization.Tests", "Serializer.Tests\Serialization.Tests.csproj", "{306FE793-C33A-443B-94D2-A5EBD79C8D4A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}.Release|Any CPU.ActiveCfg = Release|Any CPU {CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}.Release|Any CPU.Build.0 = Release|Any CPU + {306FE793-C33A-443B-94D2-A5EBD79C8D4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {306FE793-C33A-443B-94D2-A5EBD79C8D4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {306FE793-C33A-443B-94D2-A5EBD79C8D4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {306FE793-C33A-443B-94D2-A5EBD79C8D4A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Test/CustomSerializers.cs b/Test/CustomSerializers.cs index 09de5ca..6ec2f40 100644 --- a/Test/CustomSerializers.cs +++ b/Test/CustomSerializers.cs @@ -1,10 +1,10 @@ -using NetSerializer; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; +using Orckestra.Serialization; namespace Test { diff --git a/Test/Program.cs b/Test/Program.cs index 0b5b0e7..b293ed0 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -5,7 +5,7 @@ using System.IO; using System.Diagnostics; using System.Threading; -using NS = NetSerializer; +using Orckestra.Serialization; namespace Test { @@ -18,7 +18,7 @@ static class Program static int NumThreads = 1; static bool ShareSerializer = false; - static NS.Serializer s_sharedSerializer; + static Serializer s_sharedSerializer; static void Main(string[] args) { diff --git a/Test/SerializerSpecimen.cs b/Test/SerializerSpecimen.cs index b5ba2df..d69314f 100644 --- a/Test/SerializerSpecimen.cs +++ b/Test/SerializerSpecimen.cs @@ -3,7 +3,7 @@ using System.IO; using System.Linq; using System.Text; -using NS = NetSerializer; +using Orckestra.Serialization; using PB = ProtoBuf; namespace Test @@ -21,9 +21,9 @@ interface ISerializerSpecimen class NetSerializerSpecimen : ISerializerSpecimen { - NS.Serializer m_serializer; + Serializer m_serializer; - public NetSerializerSpecimen(NS.Serializer serializer) + public NetSerializerSpecimen(Serializer serializer) { m_serializer = serializer; } diff --git a/Test/Test.csproj b/Test/Test.csproj index 3fa7098..fcf1e5d 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -57,9 +57,9 @@ - + {85A11D07-8D18-42D5-ACCF-EF9744EFE825} - NetSerializer + Serialization diff --git a/Test/Tester.cs b/Test/Tester.cs index b358f26..61d5bba 100644 --- a/Test/Tester.cs +++ b/Test/Tester.cs @@ -3,18 +3,18 @@ using System.Diagnostics; using System.Linq; using System.Text; -using NS = NetSerializer; +using Orckestra.Serialization; namespace Test { class Tester { - public static NS.Serializer CreateSerializer() + public static Serializer CreateSerializer() { var types = GetKnownTypes().ToArray(); var sw = Stopwatch.StartNew(); - var serializer = new NS.Serializer(types, new NS.ITypeSerializer[] { new TriDimArrayCustomSerializer() }); + var serializer = new Serializer(types, new ITypeSerializer[] { new TriDimArrayCustomSerializer() }); sw.Stop(); Console.WriteLine("Serializer.Initialize() in {0} ms", sw.ElapsedMilliseconds); @@ -31,7 +31,7 @@ public Tester() { } - public Tester(NS.Serializer serializer) + public Tester(Serializer serializer) { m_specimens.Add(new NetSerializerSpecimen(serializer)); From 6d114e5d6e4cdd15e6cb767eee6e01f74a75a1e6 Mon Sep 17 00:00:00 2001 From: orck-adrouin Date: Tue, 29 Dec 2015 16:03:33 -0500 Subject: [PATCH 2/2] - Rename project to OmniSerializer - Hash the class' definition to know if it changed since it was serialized - Make modifications of the new ConcurrentDictionary thread safe - Serializers: - New: ExpandoSerializer, EqualityComparerSerializer - UniversalDictionarySerializer was changed to require a parameterless public constructor - Prevent serialization of anonymous types --- .../DynamicClassSerializerTests.cs | 124 ++++ .../OmniSerializer.Dynamic.Tests.csproj | 26 +- .../Properties/AssemblyInfo.cs | 10 +- .../OmniSerializer.Tests.csproj | 119 ++++ .../Properties/AssemblyInfo.cs | 0 .../SerializerKey.snk | Bin .../SerializerTests.cs | 662 ++++++++++++++---- .../TestObjects}/CircularReference.cs | 2 +- .../TestObjects}/ClassWithClassProperty.cs | 2 +- .../ClassWithDifferentAccessModifiers.cs | 2 +- .../TestObjects/ClassWithDynamicProperty.cs | 9 + .../TestObjects}/ClassWithIEnumerable.cs | 2 +- .../ClassWithoutSerializableAttribute.cs | 2 +- .../TestObjects}/CustomDictionaries.cs | 8 +- .../TestObjects}/EnumForTesting.cs | 2 +- .../TestObjects}/GenericBaseClass.cs | 2 +- .../TestObjects}/HierarchyClasses.cs | 2 +- .../TestObjects/TestStruct.cs | 17 + Serializer.sln => OmniSerializer.sln | 10 +- ...onymousTypesCannotBeSerializedException.cs | 29 + ...stsInCurrentSerializationGraphException.cs | 35 + ...PublicParameterlessConstructorException.cs | 29 + .../Exceptions/TypeNotFoundException.cs | 34 + ...asModifiedSinceItWasSerializedException.cs | 33 + {Serialization => OmniSerializer}/Helpers.cs | 21 +- .../ITypeSerializer.cs | 2 +- .../NetSerializer.nuspec | 0 .../OmniSerializer.csproj | 24 +- .../OmniSerializer.csproj.DotSettings | 0 .../Primitives.cs | 2 +- OmniSerializer/Properties/AssemblyInfo.cs | 20 + OmniSerializer/Properties/SerializerKey.snk | Bin 0 -> 596 bytes OmniSerializer/ReferenceTracker.cs | 79 +++ .../Serialization.csproj.DotSettings | 2 + .../Serializer.cs | 341 ++++----- OmniSerializer/SerializerTestHelper.cs | 17 + {Serialization => OmniSerializer}/TypeData.cs | 6 +- .../TypeSerializers/ArraySerializer.cs | 2 +- .../BinaryFormatterSerializer.cs | 58 ++ .../TypeSerializers/DictionarySerializer.cs | 4 +- .../TypeSerializers/EnumSerializer.cs | 2 +- .../EqualityComparerSerializer.cs | 26 + .../TypeSerializers/ExpandoSerializer.cs | 76 ++ .../TypeSerializers/GenericSerializer.cs | 10 +- .../TypeSerializers/HashsetSerializer.cs | 4 +- .../TypeSerializers/HashtableSerializer.cs | 4 +- .../TypeSerializers/NoOpSerializer.cs | 2 +- .../TypeSerializers/NullableSerializer.cs | 2 +- .../TypeSerializers/ObjectSerializer.cs | 182 +++++ .../TypeSerializers/PrimitivesSerializer.cs | 2 +- .../TriDimArrayCustomSerializer.cs | 81 +++ .../UniversalDictionarySerializer.cs | 17 +- OmniSerializer/TypeWithHashCode.cs | 50 ++ PrimitiveTest/PrimitiveTest.csproj | 4 +- PrimitiveTest/Program.cs | 2 +- README | 18 +- .../Exceptions/CircularReferenceException.cs | 13 - ...PublicParameterlessConstructorException.cs | 11 - .../Exceptions/TypeNotFoundException.cs | 13 - Serialization/ReferenceWatcher.cs | 84 --- Serialization/TypeDictionary.cs | 178 ----- Serialization/TypeIDList.cs | 71 -- .../TypeSerializers/ObjectSerializer.cs | 155 ---- Serializer.Tests/TestData/TestStruct.cs | 10 - Test/Program.cs | 2 +- Test/SerializerSpecimen.cs | 4 +- Test/Test.csproj | 6 +- Test/Tester.cs | 4 +- ...zers.cs => TriDimArrayCustomSerializer.cs} | 2 +- 69 files changed, 1819 insertions(+), 955 deletions(-) create mode 100644 OmniSerializer.Dynamic.Tests/DynamicClassSerializerTests.cs rename Serializer.Tests/Serialization.Tests.csproj => OmniSerializer.Dynamic.Tests/OmniSerializer.Dynamic.Tests.csproj (81%) rename {Serialization => OmniSerializer.Dynamic.Tests}/Properties/AssemblyInfo.cs (77%) create mode 100644 OmniSerializer.Tests/OmniSerializer.Tests.csproj rename {Serializer.Tests => OmniSerializer.Tests}/Properties/AssemblyInfo.cs (100%) rename {Serialization => OmniSerializer.Tests}/SerializerKey.snk (100%) rename {Serializer.Tests => OmniSerializer.Tests}/SerializerTests.cs (51%) rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/CircularReference.cs (81%) rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/ClassWithClassProperty.cs (77%) rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/ClassWithDifferentAccessModifiers.cs (95%) create mode 100644 OmniSerializer.Tests/TestObjects/ClassWithDynamicProperty.cs rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/ClassWithIEnumerable.cs (80%) rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/ClassWithoutSerializableAttribute.cs (72%) rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/CustomDictionaries.cs (80%) rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/EnumForTesting.cs (75%) rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/GenericBaseClass.cs (94%) rename {Serializer.Tests/TestData => OmniSerializer.Tests/TestObjects}/HierarchyClasses.cs (92%) create mode 100644 OmniSerializer.Tests/TestObjects/TestStruct.cs rename Serializer.sln => OmniSerializer.sln (71%) create mode 100644 OmniSerializer/Exceptions/AnonymousTypesCannotBeSerializedException.cs create mode 100644 OmniSerializer/Exceptions/ObjectExistsInCurrentSerializationGraphException.cs create mode 100644 OmniSerializer/Exceptions/RequiredPublicParameterlessConstructorException.cs create mode 100644 OmniSerializer/Exceptions/TypeNotFoundException.cs create mode 100644 OmniSerializer/Exceptions/TypeWasModifiedSinceItWasSerializedException.cs rename {Serialization => OmniSerializer}/Helpers.cs (86%) rename {Serialization => OmniSerializer}/ITypeSerializer.cs (97%) rename {Serialization => OmniSerializer}/NetSerializer.nuspec (100%) rename Serialization/Serialization.csproj => OmniSerializer/OmniSerializer.csproj (76%) rename Serialization/Serialization.csproj.DotSettings => OmniSerializer/OmniSerializer.csproj.DotSettings (100%) rename {Serialization => OmniSerializer}/Primitives.cs (99%) create mode 100644 OmniSerializer/Properties/AssemblyInfo.cs create mode 100644 OmniSerializer/Properties/SerializerKey.snk create mode 100644 OmniSerializer/ReferenceTracker.cs create mode 100644 OmniSerializer/Serialization.csproj.DotSettings rename {Serialization => OmniSerializer}/Serializer.cs (54%) create mode 100644 OmniSerializer/SerializerTestHelper.cs rename {Serialization => OmniSerializer}/TypeData.cs (91%) rename {Serialization => OmniSerializer}/TypeSerializers/ArraySerializer.cs (98%) create mode 100644 OmniSerializer/TypeSerializers/BinaryFormatterSerializer.cs rename {Serialization => OmniSerializer}/TypeSerializers/DictionarySerializer.cs (97%) rename {Serialization => OmniSerializer}/TypeSerializers/EnumSerializer.cs (95%) create mode 100644 OmniSerializer/TypeSerializers/EqualityComparerSerializer.cs create mode 100644 OmniSerializer/TypeSerializers/ExpandoSerializer.cs rename {Serialization => OmniSerializer}/TypeSerializers/GenericSerializer.cs (92%) rename {Serialization => OmniSerializer}/TypeSerializers/HashsetSerializer.cs (97%) rename {Serialization => OmniSerializer}/TypeSerializers/HashtableSerializer.cs (94%) rename {Serialization => OmniSerializer}/TypeSerializers/NoOpSerializer.cs (97%) rename {Serialization => OmniSerializer}/TypeSerializers/NullableSerializer.cs (98%) create mode 100644 OmniSerializer/TypeSerializers/ObjectSerializer.cs rename {Serialization => OmniSerializer}/TypeSerializers/PrimitivesSerializer.cs (96%) create mode 100644 OmniSerializer/TypeSerializers/TriDimArrayCustomSerializer.cs rename {Serialization => OmniSerializer}/TypeSerializers/UniversalDictionarySerializer.cs (96%) create mode 100644 OmniSerializer/TypeWithHashCode.cs delete mode 100644 Serialization/Exceptions/CircularReferenceException.cs delete mode 100644 Serialization/Exceptions/RequiredPublicParameterlessConstructorException.cs delete mode 100644 Serialization/Exceptions/TypeNotFoundException.cs delete mode 100644 Serialization/ReferenceWatcher.cs delete mode 100644 Serialization/TypeDictionary.cs delete mode 100644 Serialization/TypeIDList.cs delete mode 100644 Serialization/TypeSerializers/ObjectSerializer.cs delete mode 100644 Serializer.Tests/TestData/TestStruct.cs rename Test/{CustomSerializers.cs => TriDimArrayCustomSerializer.cs} (98%) diff --git a/OmniSerializer.Dynamic.Tests/DynamicClassSerializerTests.cs b/OmniSerializer.Dynamic.Tests/DynamicClassSerializerTests.cs new file mode 100644 index 0000000..04a51dc --- /dev/null +++ b/OmniSerializer.Dynamic.Tests/DynamicClassSerializerTests.cs @@ -0,0 +1,124 @@ +using System; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OmniSerializer.Exceptions; + +namespace OmniSerializer.Tests +{ + // The purpose of this project is to test the deserialization process when an object has been modified. + // We need to used an unsigned Dll to do this dynamically without hardcoding a byte array . + + [TestClass] + [Serializable] + public class DynamicClassSerializerTests + { + private readonly string testAppDomainName = "TestDomain"; + + [TestMethod] + [ExpectedException(typeof(TypeWasModifiedSinceItWasSerializedException))] + public void DeserializeShouldThrowIfTheObjectHasChanged() + { + try + { + var serializedData = SerializeObjectWithOneField(); + SerializerTestHelper.ClearTypeDataMap(); + + using (var memoryStream = new MemoryStream(serializedData)) + { + var serialiser = new Serializer(); + var instance = (TestNs.TargetClass) serialiser.Deserialize(memoryStream); + Assert.AreEqual(123, + instance.field0); + } + } + finally + { + SerializerTestHelper.ClearTypeDataMap(); + } + } + + private byte[] SerializeObjectWithOneField() + { + var testDomain = CreateTestAppDomain(new string[0]); + testDomain.DoCallBack(new CrossAppDomainDelegate(() => + { + var type = CreateType(1); + //var instance = Activator.CreateInstance(AppDomain.CurrentDomain, type.Assembly.FullName, type.FullName); + var instance = Activator.CreateInstance(type, 1); + Assert.AreEqual(0, type.GetType().GetFields().Length); + + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + serialiser.SerializeObject(memoryStream, instance); + AppDomain.CurrentDomain.SetData("serializedData", memoryStream.ToArray()); + } + })); + + var serializedData = (byte[])testDomain.GetData("serializedData"); + AppDomain.Unload(testDomain); + + return serializedData; + } + + private AppDomain CreateTestAppDomain(string[] appDomainInitializerArguments) + { + return AppDomain.CreateDomain(testAppDomainName, + null, + new AppDomainSetup + { + ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase, + AppDomainInitializerArguments = appDomainInitializerArguments + }); + } + + private static Type CreateType(int numberOfFields) + { + AssemblyName assemblyName = new AssemblyName(typeof(TestNs.TargetClass).Assembly.FullName); + AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, + AssemblyBuilderAccess.RunAndCollect); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, + assemblyName.Name + ".dll"); + TypeBuilder typeBuilder = moduleBuilder.DefineType("TestNs.TargetClass", + TypeAttributes.Public); + + FieldBuilder fieldBuilder = typeBuilder.DefineField("field" + 0, + typeof(int), + FieldAttributes.Private); + + Type[] constructorArgs = { typeof(int) }; + ConstructorBuilder constructor = typeBuilder.DefineConstructor( + MethodAttributes.Public, CallingConventions.Standard, constructorArgs); + ILGenerator constructorIL = constructor.GetILGenerator(); + constructorIL.Emit(OpCodes.Ldarg_0); + ConstructorInfo superConstructor = typeof(Object).GetConstructor(new Type[0]); + constructorIL.Emit(OpCodes.Call, superConstructor); + constructorIL.Emit(OpCodes.Ldarg_0); + constructorIL.Emit(OpCodes.Ldarg_1); + constructorIL.Emit(OpCodes.Stfld, fieldBuilder); + constructorIL.Emit(OpCodes.Ret); + + // Create the MyMethod method. + MethodBuilder myMethodBuilder = typeBuilder.DefineMethod("MyMethod", + MethodAttributes.Public, typeof(int), null); + ILGenerator methodIL = myMethodBuilder.GetILGenerator(); + methodIL.Emit(OpCodes.Ldarg_0); + methodIL.Emit(OpCodes.Ldfld, fieldBuilder); + methodIL.Emit(OpCodes.Ret); + + + return typeBuilder.CreateType(); + } + } +} + +namespace TestNs +{ + public class TargetClass + { + public int field0; + public int field1; + } +} \ No newline at end of file diff --git a/Serializer.Tests/Serialization.Tests.csproj b/OmniSerializer.Dynamic.Tests/OmniSerializer.Dynamic.Tests.csproj similarity index 81% rename from Serializer.Tests/Serialization.Tests.csproj rename to OmniSerializer.Dynamic.Tests/OmniSerializer.Dynamic.Tests.csproj index 4d5f7da..e93ca70 100644 --- a/Serializer.Tests/Serialization.Tests.csproj +++ b/OmniSerializer.Dynamic.Tests/OmniSerializer.Dynamic.Tests.csproj @@ -3,11 +3,11 @@ Debug AnyCPU - {306FE793-C33A-443B-94D2-A5EBD79C8D4A} + {5BC3CA1A-E46A-4EC2-A107-4244D61B7C56} Library Properties - Serialization.Tests - Serialization.Tests + OmniSerializer.Dynamic.Tests + OmniSerializer.Dynamic.Tests v4.5 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -26,6 +26,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -34,10 +35,10 @@ TRACE prompt 4 + true - @@ -52,26 +53,15 @@ + - - - - - - - - - - - - + {85a11d07-8d18-42d5-accf-ef9744efe825} - Serialization + OmniSerializer - diff --git a/Serialization/Properties/AssemblyInfo.cs b/OmniSerializer.Dynamic.Tests/Properties/AssemblyInfo.cs similarity index 77% rename from Serialization/Properties/AssemblyInfo.cs rename to OmniSerializer.Dynamic.Tests/Properties/AssemblyInfo.cs index 4b97297..2a978f4 100644 --- a/Serialization/Properties/AssemblyInfo.cs +++ b/OmniSerializer.Dynamic.Tests/Properties/AssemblyInfo.cs @@ -5,10 +5,14 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Orckestra.Serializer")] +[assembly: AssemblyTitle("OmniSerializer.Dynamic.Tests")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("Serializer")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OmniSerializer.Dynamic.Tests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from @@ -16,7 +20,7 @@ [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("46f876f8-4fa5-4d3b-9619-f47b03fa6c95")] +[assembly: Guid("5bc3ca1a-e46a-4ec2-a107-4244d61b7c56")] // Version information for an assembly consists of the following four values: // diff --git a/OmniSerializer.Tests/OmniSerializer.Tests.csproj b/OmniSerializer.Tests/OmniSerializer.Tests.csproj new file mode 100644 index 0000000..1744534 --- /dev/null +++ b/OmniSerializer.Tests/OmniSerializer.Tests.csproj @@ -0,0 +1,119 @@ + + + + Debug + AnyCPU + {306FE793-C33A-443B-94D2-A5EBD79C8D4A} + Library + Properties + OmniSerializer.Tests + OmniSerializer.Tests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + true + + + SerializerKey.snk + + + + + + + + + + + + + + + + False + + + + + + + + + + + + + + + + + + + + + + {85a11d07-8d18-42d5-accf-ef9744efe825} + OmniSerializer + + + + + Properties\SerializerKey.snk + + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/Serializer.Tests/Properties/AssemblyInfo.cs b/OmniSerializer.Tests/Properties/AssemblyInfo.cs similarity index 100% rename from Serializer.Tests/Properties/AssemblyInfo.cs rename to OmniSerializer.Tests/Properties/AssemblyInfo.cs diff --git a/Serialization/SerializerKey.snk b/OmniSerializer.Tests/SerializerKey.snk similarity index 100% rename from Serialization/SerializerKey.snk rename to OmniSerializer.Tests/SerializerKey.snk diff --git a/Serializer.Tests/SerializerTests.cs b/OmniSerializer.Tests/SerializerTests.cs similarity index 51% rename from Serializer.Tests/SerializerTests.cs rename to OmniSerializer.Tests/SerializerTests.cs index f308dcf..aed2638 100644 --- a/Serializer.Tests/SerializerTests.cs +++ b/OmniSerializer.Tests/SerializerTests.cs @@ -1,15 +1,17 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Dynamic; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Numerics; +using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Orckestra.Serialization; -using Serialization.Tests.TestData; +using OmniSerializer.Exceptions; +using OmniSerializer.Tests.TestObjects; -namespace Serialization.Tests +namespace OmniSerializer.Tests { [TestClass] public class SerializerTests @@ -33,6 +35,11 @@ public void SerializePrimitives() { Value = 38 }); + SerializePrimitive(new StructForTestingWithString + { + StringValue = "abc", + IntWrapper = new IntGenericBaseClass(123) + }); SerializePrimitive(EnumForTesting.Two); SerializePrimitive("Test text"); SerializePrimitive('c'); @@ -79,9 +86,9 @@ private static void SerializePrimitiveValue(T value) using (var memoryStream = new MemoryStream()) { Type targetType = typeof(T); - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, value); memoryStream.Position = 0; @@ -101,9 +108,9 @@ private static void SerializePrimitiveArray(T value) var array = new T[1]; array[0] = value; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, array); memoryStream.Position = 0; @@ -123,9 +130,9 @@ private static void SerializePrimitiveList(T value) value }; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, list); memoryStream.Position = 0; @@ -151,13 +158,13 @@ public void SerializeTestClass() using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, classInstance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (ClassWithDifferentAccessModifiers)deserialiser.Deserialize(memoryStream); Assert.AreEqual(classInstance.PublicFieldValue, @@ -176,7 +183,7 @@ public void SerializeTestClass() } [TestMethod] - [ExpectedException(typeof(CircularReferenceException))] + [ExpectedException(typeof(ObjectExistsInCurrentSerializationGraphException))] public void SerializeCircularReference() { var instance1 = new CircularReference(); @@ -186,16 +193,16 @@ public void SerializeCircularReference() using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance1); memoryStream.Position = 0; var deserializedValue = (CircularReference) serialiser.Deserialize(memoryStream); - Assert.IsTrue(Object.ReferenceEquals(deserializedValue, - deserializedValue.Child)); + Assert.IsTrue(ReferenceEquals(deserializedValue, + deserializedValue.Child)); } } @@ -204,7 +211,7 @@ public void SerializeTrackMultipleReference() { using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); var instance = new IntGenericBaseClass(123); var list = new List { @@ -212,7 +219,7 @@ public void SerializeTrackMultipleReference() instance }; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, list); memoryStream.Position = 0; @@ -231,7 +238,7 @@ public void SerializeTrackSamePrimitiveMultipleTimes() // this case exists to make sure that the ReferenceWatcher only tracks classes using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); var instance = 3; var list = new List { @@ -239,7 +246,7 @@ public void SerializeTrackSamePrimitiveMultipleTimes() instance }; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, list); memoryStream.Position = 0; @@ -261,9 +268,9 @@ public void SerializeClassWithoutSerializableAttribute() { PublicPropertyValue = 4 }; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; @@ -283,9 +290,9 @@ public void SerializeClassWithGenericBase() { Value = 4 }; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; @@ -305,9 +312,9 @@ public void SerializeGenericClass() { Value = 4 }; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; @@ -329,9 +336,9 @@ public void SerializeDictionaryStringObject() {"Key2", "abc"}, {"Key3", new IntGenericBaseClass(3) }, }; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; @@ -360,9 +367,9 @@ public void SerializeCustomDictionary() {"Key2", "abc"}, {"Key3", new IntGenericBaseClass(3) }, }; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; @@ -391,9 +398,9 @@ public void SerializeCustomDictionaryWithComparer() {"Key2", "abc"}, {"Key3", new IntGenericBaseClass(3) }, }; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; @@ -425,13 +432,13 @@ public void SerializeCustomDictionaryWithoutPublicParameterlessConstructor() {"Key2", "abc"}, {"Key3", new IntGenericBaseClass(3) }, }; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserializedValue = (CustomDictionaryWithoutPublicParameterlessConstructor)serialiser.Deserialize(memoryStream); + serialiser.Deserialize(memoryStream); } } @@ -447,9 +454,9 @@ public void SerializeCustomDictionaryWithAdditionalProperties() {"Key3", new IntGenericBaseClass(3) }, }; instance.SomeProperty = 849; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; @@ -469,6 +476,53 @@ public void SerializeCustomDictionaryWithAdditionalProperties() } } + [TestMethod] + public void SerializeCustomDictionaryWithDictionaryProperty() + { + using (var memoryStream = new MemoryStream()) + { + var instance = new CustomDictionaryWithDictionaryProperty + { + {"Key1", 123}, + {"Key2", "abc"}, + {"Key3", new IntGenericBaseClass(3) }, + }; + instance.SwitchedDictionary = new CustomDictionaryWithAdditionalPropertiesAndGenerics + { + { 123, "Key1"}, + { "abc", "Key2"}, + { new IntGenericBaseClass(3), "Key3" }, + }; + var serialiser = new Serializer(); + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserializedValue = (CustomDictionaryWithDictionaryProperty)serialiser.Deserialize(memoryStream); + + CompareDictionaries(instance, + deserializedValue); + CompareDictionaries(instance.SwitchedDictionary, + deserializedValue.SwitchedDictionary); + } + } + + private static void CompareDictionaries(Dictionary instance, Dictionary deserializedValue) + { + Assert.AreEqual(instance.Count, + deserializedValue.Count); + Assert.AreEqual(instance.Comparer.GetType(), + deserializedValue.Comparer.GetType()); + CollectionAssert.AreEquivalent(instance.Keys, + deserializedValue.Keys); + foreach (var key in instance.Keys) + { + Assert.AreEqual(instance[key], + deserializedValue[key]); + } + } + [TestMethod] public void SerializeCustomDictionaryWithAdditionalPropertiesAndGenerics() { @@ -481,9 +535,9 @@ public void SerializeCustomDictionaryWithAdditionalPropertiesAndGenerics() {"Key3", 789}, }; instance.SomeProperty = 849; - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; @@ -517,18 +571,18 @@ public void SerializeTestClassWithAdditionalTypeRoots() using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(new[] + var serialiser = new Serializer(new[] { typeof(ClassWithClassProperty), typeof(StructForTesting), typeof(IntGenericBaseClass), }); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, classInstance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(new [] + var deserialiser = new Serializer(new [] { typeof(IntGenericBaseClass), typeof(StructForTesting), @@ -552,21 +606,21 @@ public void SerializeListWithMultipleTypes() using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, list); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (List)deserialiser.Deserialize(memoryStream); Assert.AreEqual(list.Count, deserializedValue.Count); - Assert.AreEqual(list.OfType().FirstOrDefault().Value, - deserializedValue.OfType().FirstOrDefault().Value); - Assert.AreEqual(list.OfType().FirstOrDefault().Value, - deserializedValue.OfType().FirstOrDefault().Value); + Assert.AreEqual(list.OfType().First().Value, + deserializedValue.OfType().First().Value); + Assert.AreEqual(list.OfType().First().Value, + deserializedValue.OfType().First().Value); } } @@ -575,20 +629,14 @@ public void SerializeObjectWithListAsIEnumerable() { using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); - var instance = new ClassWithIEnumerable(); - instance.Items = new List - { - 1, - 2, - 3 - }; + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 } }; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); Assert.AreEqual(instance.Items.Count(), @@ -603,20 +651,17 @@ public void SerializeObjectWithHashsetAsIEnumerable() { using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); - var instance = new ClassWithIEnumerable(); - instance.Items = new HashSet - { - 1, - 2, - 3 - }; + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable + { + Items = new HashSet { 1, 2, 3 } + }; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); Assert.AreEqual(instance.Items.Count(), @@ -631,15 +676,14 @@ public void SerializeObjectWithEnumProperty() { using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); - var instance = new GenericBaseClass(); - instance.Value = EnumForTesting.Two; + var serialiser = new Serializer(); + var instance = new GenericBaseClass { Value = EnumForTesting.Two }; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (GenericBaseClass)deserialiser.Deserialize(memoryStream); Assert.AreEqual(instance.Value, @@ -652,18 +696,18 @@ public void SerializeHashtable() { using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); var instance = new Hashtable { {1, 2}, {"a", "b"}, }; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (Hashtable)deserialiser.Deserialize(memoryStream); Assert.AreEqual(instance.Count, @@ -678,20 +722,14 @@ public void SerializeObjectWithArrayAsIEnumerable() { using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); - var instance = new ClassWithIEnumerable(); - instance.Items = new int[] - { - 1, - 2, - 3 - }; + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new [] { 1, 2, 3 } }; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); Assert.AreEqual(instance.Items.Count(), @@ -706,21 +744,234 @@ public void SerializeObjectWithDistinctIEnumerable() { using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); - var instance = new ClassWithIEnumerable(); - instance.Items = new List - { - 1, - 1, - 2, - 3 - }.Distinct(); + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 1, 2, 3 }.Distinct() }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithWhereIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.Where(x => x > 1) }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithOrderByIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.OrderBy(x => x) }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithDefaultIfEmptyIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.DefaultIfEmpty(123) }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithExceptIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.Except(new[] { 2 }) }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithUnionIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.Union(new[] { 4 }) }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithIntersectIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.Intersect(new[] { 2 }) }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithOfTypeIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.OfType() }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithSkipByIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.Skip(1) }; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithTakeByIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.Take(1) }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + } + + [TestMethod] + public void SerializeObjectWithSelectByIEnumerable() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 2, 3 }.Select(x => x * 2) }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); Assert.AreEqual(instance.Items.Count(), @@ -738,14 +989,14 @@ public void SerializeSimpleFunc() using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); System.Func instance = x => x > 3; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (System.Func)deserialiser.Deserialize(memoryStream); Assert.IsNotNull(deserializedValue); @@ -763,14 +1014,14 @@ public void SerializeSimpleExpression() using (var memoryStream = new MemoryStream()) { - var serialiser = new Orckestra.Serialization.Serializer(); + var serialiser = new Serializer(); Expression> instance = x => x > 3; - serialiser.Serialize(memoryStream, + serialiser.SerializeObject(memoryStream, instance); memoryStream.Position = 0; - var deserialiser = new Orckestra.Serialization.Serializer(); + var deserialiser = new Serializer(); var deserializedValue = (Expression>)deserialiser.Deserialize(memoryStream); Assert.IsNotNull(deserializedValue); @@ -780,35 +1031,198 @@ public void SerializeSimpleExpression() } } - // + [TestMethod] + [ExpectedException(typeof(AnonymousTypesCannotBeSerializedException))] + public void SerializeAnonymousObject() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new { Property1 = "hello", Property2 = 123 }; + serialiser.SerializeObject(memoryStream, + instance); + Assert.Fail(); + } + } + [TestMethod] + public void SerializeClassWithDynamicObject() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + ClassWithDynamicProperty instance = new ClassWithDynamicProperty { Value = 123 }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithDynamicProperty)deserialiser.Deserialize(memoryStream); + + Assert.IsNotNull(deserializedValue); + + Assert.AreEqual(instance.Value, + deserializedValue.Value); + } + } + + [TestMethod] + [ExpectedException(typeof(TypeNotFoundException))] + public void DeserializeUnknownType() + { + byte[] bytes; + var instance = new IntGenericBaseClass(123); + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + + serialiser.SerializeObject(memoryStream, + instance); + bytes = memoryStream.ToArray(); + } + + bytes[10] = (byte)'z'; // change the type to something invalid + + using (var memoryStream = new MemoryStream(bytes)) + { + var deserialiser = new Serializer(); + var deserializedValue = (IntGenericBaseClass)deserialiser.Deserialize(memoryStream); + + Assert.IsNull(deserializedValue); + Assert.Fail(); + } + } + + [TestMethod] + [ExpectedException(typeof(TypeWasModifiedSinceItWasSerializedException))] + public void DeserializeTypeThatWasModified() + { + byte[] bytes; + var instance = new IntGenericBaseClass(123); + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + + serialiser.SerializeObject(memoryStream, + instance); + bytes = memoryStream.ToArray(); + } + + var needle = typeof(IntGenericBaseClass).AssemblyQualifiedName; + var index = System.Text.Encoding.ASCII.GetString(bytes).IndexOf(needle); + + // this is a hackish way to change the hashcode of a serialized object + // if the way/order (currently TypeName + Hash) that an object is serialized changes the line below will need to be modified to target a byte of the hashcode + bytes[index + needle.Length + 1] = (bytes[index + needle.Length + 1] == 255) ? (byte)0 : (byte)(bytes[index + needle.Length] + 1); // change the hashcode to something invalid + + using (var memoryStream = new MemoryStream(bytes)) + { + var deserialiser = new Serializer(); + var deserializedValue = (IntGenericBaseClass)deserialiser.Deserialize(memoryStream); + Assert.IsNull(deserializedValue); + Assert.Fail(); + } + } + [TestMethod] + public void SerializeExpandoObject() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + dynamic instance = new ExpandoObject(); + instance.Property1 = 123; + instance.Property2 = "abc"; + instance.Property3 = new IntGenericBaseClass(349); + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + var deserialiser = new Serializer(); + var deserializedValue = (dynamic)deserialiser.Deserialize(memoryStream); + Assert.IsNotNull(deserializedValue); + Assert.AreEqual(instance.Property1, + deserializedValue.Property1); + Assert.AreEqual(instance.Property2, + deserializedValue.Property2); + Assert.AreEqual(instance.Property3, + deserializedValue.Property3); + } + } - //[TestMethod] - //public void zzSerializeClassWithoutRootType() - //{ - // using (var memoryStream = new MemoryStream()) - // { - // var instance = new GenericBaseClass - // { - // Value = 4 - // }; - // var serialiser = new Serializer(); + [TestMethod] + public void SerializeInParallel() + { + SerializerTestHelper.ClearTypeDataMap(); // empty the serialization Type to TypeData dictionary to start from a fresh state. - // serialiser.Serialize(memoryStream, - // instance); - // memoryStream.Position = 0; + for (int i = 0; i < 100; i++) + { + Parallel.For(0, + 1000, + k => + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new ClassWithIEnumerable { Items = new List { 1, 1, 2, 3 }.Distinct() }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; + + var deserialiser = new Serializer(); + var deserializedValue = (ClassWithIEnumerable)deserialiser.Deserialize(memoryStream); + + Assert.AreEqual(instance.Items.Count(), + deserializedValue.Items.Count()); + CollectionAssert.AreEquivalent(instance.Items.ToList(), + deserializedValue.Items.ToList()); + } + }); + } + } - // var deserializedValue = (GenericBaseClass)serialiser.Deserialize(memoryStream); + [TestMethod] + public void SerializeEnumEqualityComparer() + { + using (var memoryStream = new MemoryStream()) + { + var serialiser = new Serializer(); + var instance = new Dictionary { { EnumForTesting.One, 1 }, { EnumForTesting.Two, 2 } }; + + serialiser.SerializeObject(memoryStream, + instance); + memoryStream.Position = 0; - // Assert.AreEqual(instance.Value, - // deserializedValue.Value); - // } - //} + var deserialiser = new Serializer(); + var deserializedValue = (Dictionary)deserialiser.Deserialize(memoryStream); + + Assert.IsNotNull(deserializedValue); + + CompareDictionaries(instance, + deserializedValue); + } + } + + [TestMethod] + [ExpectedException(typeof(ObjectExistsInCurrentSerializationGraphException))] + public void TrackObjectsWithSameHashcode() + { + var object1 = new IntGenericBaseClass(123); + var object2 = new IntGenericBaseClass(123); + Assert.AreEqual(object1, object2); + + using (var tracker = new ReferenceTracker()) + { + tracker.TrackObject(object1); + tracker.TrackObject(object2); + } + } } } \ No newline at end of file diff --git a/Serializer.Tests/TestData/CircularReference.cs b/OmniSerializer.Tests/TestObjects/CircularReference.cs similarity index 81% rename from Serializer.Tests/TestData/CircularReference.cs rename to OmniSerializer.Tests/TestObjects/CircularReference.cs index d390a4c..0356289 100644 --- a/Serializer.Tests/TestData/CircularReference.cs +++ b/OmniSerializer.Tests/TestObjects/CircularReference.cs @@ -1,6 +1,6 @@ using System; -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { [Serializable] public class CircularReference diff --git a/Serializer.Tests/TestData/ClassWithClassProperty.cs b/OmniSerializer.Tests/TestObjects/ClassWithClassProperty.cs similarity index 77% rename from Serializer.Tests/TestData/ClassWithClassProperty.cs rename to OmniSerializer.Tests/TestObjects/ClassWithClassProperty.cs index a4eacfc..7d044d3 100644 --- a/Serializer.Tests/TestData/ClassWithClassProperty.cs +++ b/OmniSerializer.Tests/TestObjects/ClassWithClassProperty.cs @@ -1,6 +1,6 @@ using System; -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { [Serializable] public class ClassWithClassProperty diff --git a/Serializer.Tests/TestData/ClassWithDifferentAccessModifiers.cs b/OmniSerializer.Tests/TestObjects/ClassWithDifferentAccessModifiers.cs similarity index 95% rename from Serializer.Tests/TestData/ClassWithDifferentAccessModifiers.cs rename to OmniSerializer.Tests/TestObjects/ClassWithDifferentAccessModifiers.cs index 5c8246e..af73563 100644 --- a/Serializer.Tests/TestData/ClassWithDifferentAccessModifiers.cs +++ b/OmniSerializer.Tests/TestObjects/ClassWithDifferentAccessModifiers.cs @@ -1,6 +1,6 @@ using System; -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { [Serializable] public class ClassWithDifferentAccessModifiers diff --git a/OmniSerializer.Tests/TestObjects/ClassWithDynamicProperty.cs b/OmniSerializer.Tests/TestObjects/ClassWithDynamicProperty.cs new file mode 100644 index 0000000..e146d3e --- /dev/null +++ b/OmniSerializer.Tests/TestObjects/ClassWithDynamicProperty.cs @@ -0,0 +1,9 @@ +using System; + +namespace OmniSerializer.Tests.TestObjects +{ + public class ClassWithDynamicProperty + { + public dynamic Value { get; set; } + } +} \ No newline at end of file diff --git a/Serializer.Tests/TestData/ClassWithIEnumerable.cs b/OmniSerializer.Tests/TestObjects/ClassWithIEnumerable.cs similarity index 80% rename from Serializer.Tests/TestData/ClassWithIEnumerable.cs rename to OmniSerializer.Tests/TestObjects/ClassWithIEnumerable.cs index 90c25db..0c61ec7 100644 --- a/Serializer.Tests/TestData/ClassWithIEnumerable.cs +++ b/OmniSerializer.Tests/TestObjects/ClassWithIEnumerable.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { [Serializable] public class ClassWithIEnumerable diff --git a/Serializer.Tests/TestData/ClassWithoutSerializableAttribute.cs b/OmniSerializer.Tests/TestObjects/ClassWithoutSerializableAttribute.cs similarity index 72% rename from Serializer.Tests/TestData/ClassWithoutSerializableAttribute.cs rename to OmniSerializer.Tests/TestObjects/ClassWithoutSerializableAttribute.cs index 44d6e43..ac6b999 100644 --- a/Serializer.Tests/TestData/ClassWithoutSerializableAttribute.cs +++ b/OmniSerializer.Tests/TestObjects/ClassWithoutSerializableAttribute.cs @@ -1,4 +1,4 @@ -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { public class ClassWithoutSerializableAttribute { diff --git a/Serializer.Tests/TestData/CustomDictionaries.cs b/OmniSerializer.Tests/TestObjects/CustomDictionaries.cs similarity index 80% rename from Serializer.Tests/TestData/CustomDictionaries.cs rename to OmniSerializer.Tests/TestObjects/CustomDictionaries.cs index ce918cd..0011b75 100644 --- a/Serializer.Tests/TestData/CustomDictionaries.cs +++ b/OmniSerializer.Tests/TestObjects/CustomDictionaries.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { [Serializable] public class CustomDictionary : Dictionary @@ -38,4 +38,10 @@ public CustomDictionaryWithoutPublicParameterlessConstructor(int capacity) } } + + [Serializable] + public class CustomDictionaryWithDictionaryProperty : Dictionary + { + public Dictionary SwitchedDictionary { get; set; } + } } \ No newline at end of file diff --git a/Serializer.Tests/TestData/EnumForTesting.cs b/OmniSerializer.Tests/TestObjects/EnumForTesting.cs similarity index 75% rename from Serializer.Tests/TestData/EnumForTesting.cs rename to OmniSerializer.Tests/TestObjects/EnumForTesting.cs index 953977a..f9693e4 100644 --- a/Serializer.Tests/TestData/EnumForTesting.cs +++ b/OmniSerializer.Tests/TestObjects/EnumForTesting.cs @@ -1,6 +1,6 @@ using System; -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { [Serializable] public enum EnumForTesting diff --git a/Serializer.Tests/TestData/GenericBaseClass.cs b/OmniSerializer.Tests/TestObjects/GenericBaseClass.cs similarity index 94% rename from Serializer.Tests/TestData/GenericBaseClass.cs rename to OmniSerializer.Tests/TestObjects/GenericBaseClass.cs index f345a98..fe487fc 100644 --- a/Serializer.Tests/TestData/GenericBaseClass.cs +++ b/OmniSerializer.Tests/TestObjects/GenericBaseClass.cs @@ -1,6 +1,6 @@ using System; -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { [Serializable] public class GenericBaseClass diff --git a/Serializer.Tests/TestData/HierarchyClasses.cs b/OmniSerializer.Tests/TestObjects/HierarchyClasses.cs similarity index 92% rename from Serializer.Tests/TestData/HierarchyClasses.cs rename to OmniSerializer.Tests/TestObjects/HierarchyClasses.cs index 9a2d5fb..4f93e70 100644 --- a/Serializer.Tests/TestData/HierarchyClasses.cs +++ b/OmniSerializer.Tests/TestObjects/HierarchyClasses.cs @@ -1,6 +1,6 @@ using System; -namespace Serialization.Tests.TestData +namespace OmniSerializer.Tests.TestObjects { public interface IHierarchy { diff --git a/OmniSerializer.Tests/TestObjects/TestStruct.cs b/OmniSerializer.Tests/TestObjects/TestStruct.cs new file mode 100644 index 0000000..637fe97 --- /dev/null +++ b/OmniSerializer.Tests/TestObjects/TestStruct.cs @@ -0,0 +1,17 @@ +using System; + +namespace OmniSerializer.Tests.TestObjects +{ + [Serializable] + public struct StructForTesting + { + public int Value; + } + + [Serializable] + public struct StructForTestingWithString + { + public string StringValue; + public IntGenericBaseClass IntWrapper; + } +} diff --git a/Serializer.sln b/OmniSerializer.sln similarity index 71% rename from Serializer.sln rename to OmniSerializer.sln index 81fa025..a92f09f 100644 --- a/Serializer.sln +++ b/OmniSerializer.sln @@ -3,13 +3,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serialization", "Serialization\Serialization.csproj", "{85A11D07-8D18-42D5-ACCF-EF9744EFE825}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniSerializer", "OmniSerializer\OmniSerializer.csproj", "{85A11D07-8D18-42D5-ACCF-EF9744EFE825}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{DD1A3698-ED72-49C8-874F-F721EA8012B6}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrimitiveTest", "PrimitiveTest\PrimitiveTest.csproj", "{CBA6D818-4B6A-4A80-95D5-7F5EC3FBB3C4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serialization.Tests", "Serializer.Tests\Serialization.Tests.csproj", "{306FE793-C33A-443B-94D2-A5EBD79C8D4A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniSerializer.Tests", "OmniSerializer.Tests\OmniSerializer.Tests.csproj", "{306FE793-C33A-443B-94D2-A5EBD79C8D4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OmniSerializer.Dynamic.Tests", "OmniSerializer.Dynamic.Tests\OmniSerializer.Dynamic.Tests.csproj", "{5BC3CA1A-E46A-4EC2-A107-4244D61B7C56}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,6 +35,10 @@ Global {306FE793-C33A-443B-94D2-A5EBD79C8D4A}.Debug|Any CPU.Build.0 = Debug|Any CPU {306FE793-C33A-443B-94D2-A5EBD79C8D4A}.Release|Any CPU.ActiveCfg = Release|Any CPU {306FE793-C33A-443B-94D2-A5EBD79C8D4A}.Release|Any CPU.Build.0 = Release|Any CPU + {5BC3CA1A-E46A-4EC2-A107-4244D61B7C56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BC3CA1A-E46A-4EC2-A107-4244D61B7C56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BC3CA1A-E46A-4EC2-A107-4244D61B7C56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BC3CA1A-E46A-4EC2-A107-4244D61B7C56}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OmniSerializer/Exceptions/AnonymousTypesCannotBeSerializedException.cs b/OmniSerializer/Exceptions/AnonymousTypesCannotBeSerializedException.cs new file mode 100644 index 0000000..503e259 --- /dev/null +++ b/OmniSerializer/Exceptions/AnonymousTypesCannotBeSerializedException.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.Serialization; + +namespace OmniSerializer.Exceptions +{ + /// + /// Exception raised when trying to serialize an anonymous type. + /// + [Serializable] + public class AnonymousTypesCannotBeSerializedException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The anonymous type that cannot be serialized. + public AnonymousTypesCannotBeSerializedException(Type type) + : base(string.Format("Anonymous type {0} cannot be serialized.", + type)) { } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected AnonymousTypesCannotBeSerializedException(SerializationInfo info, StreamingContext context) + : base(info, + context) { } + } +} \ No newline at end of file diff --git a/OmniSerializer/Exceptions/ObjectExistsInCurrentSerializationGraphException.cs b/OmniSerializer/Exceptions/ObjectExistsInCurrentSerializationGraphException.cs new file mode 100644 index 0000000..58bfe14 --- /dev/null +++ b/OmniSerializer/Exceptions/ObjectExistsInCurrentSerializationGraphException.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.Serialization; + +namespace OmniSerializer +{ + /// + /// Exception raised when an object has already been serialized in the current object graph. + /// This is usally caused by a circular reference. + /// + [Serializable] + public class ObjectExistsInCurrentSerializationGraphException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ObjectExistsInCurrentSerializationGraphException() + : base() {} + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + public ObjectExistsInCurrentSerializationGraphException(string message) + : base(message) {} + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected ObjectExistsInCurrentSerializationGraphException(SerializationInfo info, StreamingContext context) + : base(info, + context) {} + } +} \ No newline at end of file diff --git a/OmniSerializer/Exceptions/RequiredPublicParameterlessConstructorException.cs b/OmniSerializer/Exceptions/RequiredPublicParameterlessConstructorException.cs new file mode 100644 index 0000000..cf3eb7a --- /dev/null +++ b/OmniSerializer/Exceptions/RequiredPublicParameterlessConstructorException.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.Serialization; + +namespace OmniSerializer +{ + /// + /// Exception raised when an object requires a parameterless public constructor to be serialized. + /// + [Serializable] + public class RequiredPublicParameterlessConstructorException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The type that requires a parameterless public constructor. + public RequiredPublicParameterlessConstructorException(Type type) + : base(String.Format("Type {0} requires a parameterless public contructor.", + type)) {} + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected RequiredPublicParameterlessConstructorException(SerializationInfo info, StreamingContext context) + : base(info, + context) {} + } +} \ No newline at end of file diff --git a/OmniSerializer/Exceptions/TypeNotFoundException.cs b/OmniSerializer/Exceptions/TypeNotFoundException.cs new file mode 100644 index 0000000..a4175ed --- /dev/null +++ b/OmniSerializer/Exceptions/TypeNotFoundException.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.Serialization; + +namespace OmniSerializer +{ + /// + /// Exception raised when a type cannot be found. + /// + [Serializable] + public class TypeNotFoundException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public TypeNotFoundException() + : base() {} + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + public TypeNotFoundException(string message) + : base(message) {} + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected TypeNotFoundException(SerializationInfo info, StreamingContext context) + : base(info, + context) {} + } +} \ No newline at end of file diff --git a/OmniSerializer/Exceptions/TypeWasModifiedSinceItWasSerializedException.cs b/OmniSerializer/Exceptions/TypeWasModifiedSinceItWasSerializedException.cs new file mode 100644 index 0000000..8b16779 --- /dev/null +++ b/OmniSerializer/Exceptions/TypeWasModifiedSinceItWasSerializedException.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace OmniSerializer.Exceptions +{ + /// + /// Exception raised when the definition of an object was modified since its serialization. + /// + [Serializable] + public class TypeWasModifiedSinceItWasSerializedException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The type that was modified since it was serialized. + public TypeWasModifiedSinceItWasSerializedException(Type type) + : base(string.Format("Type {0} was modified since it was serialized.", + type)) { } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected TypeWasModifiedSinceItWasSerializedException(SerializationInfo info, StreamingContext context) + : base(info, + context) { } + } +} \ No newline at end of file diff --git a/Serialization/Helpers.cs b/OmniSerializer/Helpers.cs similarity index 86% rename from Serialization/Helpers.cs rename to OmniSerializer/Helpers.cs index 3c9c8f2..b6e5bf3 100644 --- a/Serialization/Helpers.cs +++ b/OmniSerializer/Helpers.cs @@ -13,8 +13,9 @@ using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; -namespace Orckestra.Serialization +namespace OmniSerializer { static class Helpers { @@ -33,6 +34,10 @@ public static void ValidateIfSerializable(this Type type) && (type.Namespace.StartsWith("System") || type.Namespace.StartsWith("Microsoft")) && !type.IsDictionary()) { + // We do not serialize System.* or Microsoft.* types that are not serializable. + // Microsoft had a reason to not mark them as serializable so we do not even try to serialize them. + // You can create a custom serializer that handles the object creation/serialization/deserialization if you have a use case where you need to serialize a built-in type. + if (!type.IsSerializable) { throw new NotSupportedException(String.Format(".Net type {0} is not marked as Serializable", @@ -47,6 +52,20 @@ public static void ValidateIfSerializable(this Type type) } } + + public static bool IsAnonymousType(this Type type) + { + return Attribute.IsDefined(type, + typeof(CompilerGeneratedAttribute), + false) + && (type.IsGenericType && type.Name.Contains("AnonymousType") + || type.IsGenericType && type.Name.Contains("AnonType")) + && (type.Name.StartsWith("<>", StringComparison.OrdinalIgnoreCase) + || type.Name.StartsWith("VB$", StringComparison.OrdinalIgnoreCase)) + && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic + && type.IsSealed; + } + public static bool IsDictionary(this Type type) { if (!type.IsGenericType) diff --git a/Serialization/ITypeSerializer.cs b/OmniSerializer/ITypeSerializer.cs similarity index 97% rename from Serialization/ITypeSerializer.cs rename to OmniSerializer/ITypeSerializer.cs index da7c5e1..e57f12a 100644 --- a/Serialization/ITypeSerializer.cs +++ b/OmniSerializer/ITypeSerializer.cs @@ -11,7 +11,7 @@ using System.Reflection; using System.Reflection.Emit; -namespace Orckestra.Serialization +namespace OmniSerializer { public interface ITypeSerializer { diff --git a/Serialization/NetSerializer.nuspec b/OmniSerializer/NetSerializer.nuspec similarity index 100% rename from Serialization/NetSerializer.nuspec rename to OmniSerializer/NetSerializer.nuspec diff --git a/Serialization/Serialization.csproj b/OmniSerializer/OmniSerializer.csproj similarity index 76% rename from Serialization/Serialization.csproj rename to OmniSerializer/OmniSerializer.csproj index b118235..461c625 100644 --- a/Serialization/Serialization.csproj +++ b/OmniSerializer/OmniSerializer.csproj @@ -8,8 +8,8 @@ {85A11D07-8D18-42D5-ACCF-EF9744EFE825} Library Properties - Orckestra.Serialization - Orckestra.Serialization + OmniSerializer + OmniSerializer v4.5 512 @@ -24,6 +24,7 @@ 4 true false + true pdbonly @@ -34,24 +35,31 @@ 4 true false + true true - SerializerKey.snk + Properties\SerializerKey.snk - + + - - + + + + + + + @@ -59,7 +67,6 @@ - @@ -68,9 +75,10 @@ + - +