diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs index e6b67ae927..96a2899ca0 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/TypeSystemConvertVisitorTests.cs @@ -20,6 +20,7 @@ using System; using System.IO; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; +using ICSharpCode.NRefactory.Utils; using NUnit.Framework; namespace ICSharpCode.NRefactory.CSharp.Parser @@ -27,10 +28,13 @@ namespace ICSharpCode.NRefactory.CSharp.Parser [TestFixture] public class TypeSystemConvertVisitorTests : TypeSystemTests { - ITypeResolveContext ctx = CecilLoaderTests.Mscorlib; - [TestFixtureSetUp] public void FixtureSetUp() + { + testCasePC = ParseTestCase(); + } + + internal static IProjectContent ParseTestCase() { const string fileName = "TypeSystemTests.TestCase.cs"; @@ -40,10 +44,11 @@ namespace ICSharpCode.NRefactory.CSharp.Parser cu = parser.Parse(s); } - testCasePC = new SimpleProjectContent(); + var testCasePC = new SimpleProjectContent(); ParsedFile parsedFile = new TypeSystemConvertVisitor(testCasePC, fileName).Convert(cu); parsedFile.Freeze(); testCasePC.UpdateProjectContent(null, parsedFile); + return testCasePC; } } } diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 5b247cae60..614f24eb7e 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -148,6 +148,7 @@ + diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs index c9dc2859c3..0a3746f585 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/CecilLoaderTests.cs @@ -78,7 +78,7 @@ namespace ICSharpCode.NRefactory.TypeSystem IProperty def = c.Properties.Single(p => p.Name == "Default"); ParameterizedType pt = (ParameterizedType)def.ReturnType.Resolve(ctx); Assert.AreEqual("System.Collections.Generic.Comparer", pt.FullName); - Assert.AreSame(c.TypeParameters[0], pt.TypeArguments[0]); + Assert.AreEqual(c.TypeParameters[0], pt.TypeArguments[0]); } [Test] diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/SerializedCecilLoaderTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/SerializedCecilLoaderTests.cs new file mode 100644 index 0000000000..9bbafb8516 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/SerializedCecilLoaderTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using ICSharpCode.NRefactory.Utils; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.TypeSystem +{ + [TestFixture] + public class SerializedCecilLoaderTests : TypeSystemTests + { + [TestFixtureSetUp] + public void FixtureSetUp() + { + CecilLoader loader = new CecilLoader() { IncludeInternalMembers = true }; + IProjectContent pc = loader.LoadAssemblyFile(typeof(TestCase.SimplePublicClass).Assembly.Location); + FastSerializer serializer = new FastSerializer(); + using (MemoryStream ms = new MemoryStream()) { + serializer.Serialize(ms, pc); + ms.Position = 0; + testCasePC = (IProjectContent)serializer.Deserialize(ms); + } + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs index b65aa16a9c..8146213d65 100644 --- a/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs +++ b/ICSharpCode.NRefactory.Tests/TypeSystem/TypeSystemTests.cs @@ -84,7 +84,7 @@ namespace ICSharpCode.NRefactory.TypeSystem public void DynamicType() { ITypeDefinition testClass = testCasePC.GetTypeDefinition(typeof(DynamicTest)); - Assert.AreSame(SharedTypes.Dynamic, testClass.Properties.Single().ReturnType.Resolve(ctx)); + Assert.AreEqual(SharedTypes.Dynamic, testClass.Properties.Single().ReturnType.Resolve(ctx)); Assert.AreEqual(0, testClass.Properties.Single().Attributes.Count); } diff --git a/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs b/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs index bef0dffd81..ef5fc7cac3 100644 --- a/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs +++ b/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs @@ -33,6 +33,7 @@ namespace ICSharpCode.NRefactory.Documentation /// This class first creates an in-memory index of the .xml file, and then uses that to read only the requested members. /// This way, we avoid keeping all the documentation in memory. /// + [Serializable] public class XmlDocumentationProvider : IDocumentationProvider { #region Cache @@ -66,6 +67,7 @@ namespace ICSharpCode.NRefactory.Documentation } #endregion + [Serializable] struct IndexEntry : IComparable { /// @@ -90,7 +92,9 @@ namespace ICSharpCode.NRefactory.Documentation } } + [NonSerialized] readonly XmlDocumentationCache cache = new XmlDocumentationCache(); + readonly string fileName; DateTime lastWriteDate; IndexEntry[] index; // SORTED array of index entries @@ -265,6 +269,8 @@ namespace ICSharpCode.NRefactory.Documentation #endregion #region Save index / Restore from index + // TODO: consider removing this code, we're just using serialization instead + // FILE FORMAT FOR BINARY DOCUMENTATION // long magic = 0x4244636f446c6d58 (identifies file type = 'XmlDocDB') const long magic = 0x4244636f446c6d58; diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index a50b047eb4..0f57ac2975 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -336,6 +336,7 @@ + @@ -343,6 +344,7 @@ + diff --git a/ICSharpCode.NRefactory/TypeSystem/ArrayType.cs b/ICSharpCode.NRefactory/TypeSystem/ArrayType.cs index 86dc6884ad..c5a8fb5a8a 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ArrayType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ArrayType.cs @@ -25,6 +25,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// /// Represents an array type. /// + [Serializable] public sealed class ArrayType : TypeWithElementType { readonly int dimensions; @@ -135,6 +136,7 @@ namespace ICSharpCode.NRefactory.TypeSystem } } + [Serializable] public sealed class ArrayTypeReference : ITypeReference, ISupportsInterning { ITypeReference elementType; diff --git a/ICSharpCode.NRefactory/TypeSystem/ByReferenceType.cs b/ICSharpCode.NRefactory/TypeSystem/ByReferenceType.cs index 8ae58c6e7c..553341f2f3 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ByReferenceType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ByReferenceType.cs @@ -21,6 +21,7 @@ using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.TypeSystem { + [Serializable] public sealed class ByReferenceType : TypeWithElementType { public ByReferenceType(IType elementType) : base(elementType) @@ -68,6 +69,7 @@ namespace ICSharpCode.NRefactory.TypeSystem } } + [Serializable] public class ByReferenceTypeReference : ITypeReference { readonly ITypeReference elementType; diff --git a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index 02fbb9fa67..0bdb727d61 100644 --- a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -84,6 +84,9 @@ namespace ICSharpCode.NRefactory.TypeSystem { if (createCecilReferences) typeSystemTranslationTable = new Dictionary (); + + // Enable interning by default. + this.InterningProvider = new SimpleInterningProvider(); } #region Load From AssemblyDefinition @@ -164,6 +167,7 @@ namespace ICSharpCode.NRefactory.TypeSystem #endregion #region IProjectContent implementation + [Serializable] sealed class CecilProjectContent : ProxyTypeResolveContext, IProjectContent, ISynchronizedTypeResolveContext, IDocumentationProvider { readonly string assemblyName; @@ -840,8 +844,10 @@ namespace ICSharpCode.NRefactory.TypeSystem #endregion #region Read Type Definition + [Serializable] sealed class CecilTypeDefinition : DefaultTypeDefinition { + [NonSerialized] internal TypeDefinition typeDefinition; public CecilTypeDefinition(IProjectContent pc, TypeDefinition typeDefinition) diff --git a/ICSharpCode.NRefactory/TypeSystem/IAnnotatable.cs b/ICSharpCode.NRefactory/TypeSystem/IAnnotatable.cs index 2021496969..ff30f87443 100644 --- a/ICSharpCode.NRefactory/TypeSystem/IAnnotatable.cs +++ b/ICSharpCode.NRefactory/TypeSystem/IAnnotatable.cs @@ -78,6 +78,7 @@ namespace ICSharpCode.NRefactory void RemoveAnnotations(Type type); } + [Serializable] public abstract class AbstractAnnotatable : IAnnotatable { // Annotations: points either null (no annotations), to the single annotation, diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractFreezable.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractFreezable.cs index b3f5996ee2..7cc8cf63a7 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractFreezable.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractFreezable.cs @@ -27,6 +27,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// Base class for immutable objects. Provides implementation for IFreezable that reports the /// object as always-frozen. /// + [Serializable] public abstract class Immutable : IFreezable { bool IFreezable.IsFrozen { @@ -38,6 +39,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } + [Serializable] public abstract class AbstractFreezable : IFreezable { bool isFrozen; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractMember.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractMember.cs index 31521b5ce7..e581c1ec2b 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractMember.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractMember.cs @@ -25,6 +25,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Base class for implementations. /// + [Serializable] public abstract class AbstractMember : AbstractFreezable, IMember { // possible optimizations to reduce the memory usage of AbstractMember: diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs index bc2a300b2b..d070a81471 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/AbstractType.cs @@ -26,6 +26,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation for IType interface. /// + [Serializable] public abstract class AbstractType : IType { public virtual string FullName { diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAccessor.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAccessor.cs index 98df4ba2f8..3be4ba66d5 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAccessor.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAccessor.cs @@ -25,6 +25,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation of . /// + [Serializable] public sealed class DefaultAccessor : AbstractFreezable, IAccessor, ISupportsInterning { static readonly DefaultAccessor[] defaultAccessors = CreateDefaultAccessors(); diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAttribute.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAttribute.cs index f14bc057a6..152146d52e 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAttribute.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultAttribute.cs @@ -27,6 +27,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation of . /// + [Serializable] public sealed class DefaultAttribute : AbstractFreezable, IAttribute, ISupportsInterning { ITypeReference attributeType; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultEvent.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultEvent.cs index 6f065d0030..bea77a960f 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultEvent.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultEvent.cs @@ -23,6 +23,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation of . /// + [Serializable] public class DefaultEvent : AbstractMember, IEvent { IAccessor addAccessor, removeAccessor, invokeAccessor; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultField.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultField.cs index 2293e7f6a7..22c33d980d 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultField.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultField.cs @@ -23,6 +23,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation of . /// + [Serializable] public class DefaultField : AbstractMember, IField { IConstantValue constantValue; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultMethod.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultMethod.cs index daa3047ff6..203cd31ff2 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultMethod.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultMethod.cs @@ -25,6 +25,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation of interface. /// + [Serializable] public class DefaultMethod : AbstractMember, IMethod { IList returnTypeAttributes; @@ -123,7 +124,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation var p = this.Parameters; for (int i = 0; i < p.Count; i++) { if (i > 0) b.Append(", "); - b.Append(p[i].ToString()); + if (p[i] == null) + b.Append("null"); + else + b.Append(p[i].ToString()); } b.Append("):"); b.Append(ReturnType.ToString()); diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultParameter.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultParameter.cs index a9823bc5e6..fc035938fe 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultParameter.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultParameter.cs @@ -26,6 +26,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation for IParameter. /// + [Serializable] public sealed class DefaultParameter : AbstractFreezable, IParameter, ISupportsInterning { string name = string.Empty; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultProperty.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultProperty.cs index 6a2ddf4949..8e3ec666fb 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultProperty.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultProperty.cs @@ -24,6 +24,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation of . /// + [Serializable] public class DefaultProperty : AbstractMember, IProperty { IAccessor getter, setter; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs index f9c3eaa086..34d4b3d79a 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs @@ -25,6 +25,7 @@ using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.TypeSystem.Implementation { + [Serializable] public class DefaultTypeDefinition : AbstractFreezable, ITypeDefinition { readonly IProjectContent projectContent; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs index ca234e5bd1..656e1348ce 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeParameter.cs @@ -27,6 +27,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Default implementation of . /// + [Serializable] public sealed class DefaultTypeParameter : AbstractFreezable, ITypeParameter, ISupportsInterning { string name; @@ -348,8 +349,13 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation void ISupportsInterning.PrepareForInterning(IInterningProvider provider) { - constraints = provider.InternList(constraints); - attributes = provider.InternList(attributes); + // protect against cyclic constraints + using (var busyLock = BusyManager.Enter(this)) { + if (busyLock.Success) { + constraints = provider.InternList(constraints); + attributes = provider.InternList(attributes); + } + } } int ISupportsInterning.GetHashCodeForInterning() diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/GetClassTypeReference.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/GetClassTypeReference.cs index 83a5cb45d0..c0df2456bd 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/GetClassTypeReference.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/GetClassTypeReference.cs @@ -25,11 +25,12 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Type Reference used when the fully qualified type name is known. /// + [Serializable] public sealed class GetClassTypeReference : ITypeReference, ISupportsInterning { string nameSpace, name; int typeParameterCount; - //volatile CachedResult v_cachedResult; + // [NonSerialized] volatile CachedResult v_cachedResult; public GetClassTypeReference(string nameSpace, string name, int typeParameterCount) { diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/NestedTypeReference.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/NestedTypeReference.cs index 11f8749454..04e9385a21 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/NestedTypeReference.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/NestedTypeReference.cs @@ -23,6 +23,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Type reference used to reference nested types. /// + [Serializable] public sealed class NestedTypeReference : ITypeReference, ISupportsInterning { ITypeReference declaringTypeRef; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/ProxyTypeResolveContext.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/ProxyTypeResolveContext.cs index 43f1f9fe56..898c98077b 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/ProxyTypeResolveContext.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/ProxyTypeResolveContext.cs @@ -25,6 +25,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// Proxy that forwards calls to another TypeResolveContext. /// Useful as base class for decorators. /// + [Serializable] public class ProxyTypeResolveContext : AbstractAnnotatable, ITypeResolveContext { protected readonly ITypeResolveContext target; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleConstantValue.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleConstantValue.cs index 9cba2a5ecc..b378430374 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleConstantValue.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleConstantValue.cs @@ -23,6 +23,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// A simple constant value that is independent of the resolve context. /// + [Serializable] public sealed class SimpleConstantValue : Immutable, IConstantValue, ISupportsInterning { ITypeReference type; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleInterningProvider.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleInterningProvider.cs index ca7ef2f7dc..58009843df 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleInterningProvider.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/SimpleInterningProvider.cs @@ -104,6 +104,8 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation Dictionary supportsInternDict = new Dictionary(new InterningComparer()); Dictionary listDict = new Dictionary(new ListComparer()); + int stackDepth = 0; + public T Intern(T obj) where T : class { if (obj == null) @@ -127,6 +129,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation else byValueDict.Add(obj, obj); } + stackDepth--; return obj; } @@ -138,8 +141,11 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation T oldItem = list[i]; T newItem = Intern(oldItem); if (oldItem != newItem) { - if (list.IsReadOnly) - list = new T[list.Count]; + if (list.IsReadOnly) { + T[] array = new T[list.Count]; + list.CopyTo(array, 0); + list = array; + } list[i] = newItem; } } diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeStorage.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeStorage.cs index 407390cf55..53eb718cef 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeStorage.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeStorage.cs @@ -19,8 +19,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using System.Threading; - using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.TypeSystem.Implementation @@ -31,7 +31,8 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Concurrent read accesses are thread-safe, but a write access concurrent to any other access is not safe. /// - public sealed class TypeStorage : ITypeResolveContext + [Serializable] + public sealed class TypeStorage : ITypeResolveContext, ISerializable, IDeserializationCallback { #region FullNameAndTypeParameterCount struct FullNameAndTypeParameterCount @@ -362,5 +363,35 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } } #endregion + + #region Serialization + /// + /// Creates a new TypeStorage instance. + /// + public TypeStorage() + { + } + + SerializationInfo serializationInfo; + + protected TypeStorage(SerializationInfo info, StreamingContext context) + { + this.serializationInfo = info; + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("Types", this.GetTypes().ToArray()); + } + + void IDeserializationCallback.OnDeserialization(object sender) + { + if (serializationInfo == null) + return; + foreach (var typeDef in (ITypeDefinition[])serializationInfo.GetValue("Types", typeof(ITypeDefinition[]))) { + UpdateType(typeDef); + } + } + #endregion } } diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs index 1f86861879..482fcc8acd 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/TypeWithElementType.cs @@ -20,6 +20,7 @@ using System; namespace ICSharpCode.NRefactory.TypeSystem.Implementation { + [Serializable] public abstract class TypeWithElementType : AbstractType { protected readonly IType elementType; diff --git a/ICSharpCode.NRefactory/TypeSystem/Implementation/VoidTypeDefinition.cs b/ICSharpCode.NRefactory/TypeSystem/Implementation/VoidTypeDefinition.cs index e8ec0a12c3..c8b2d8832f 100644 --- a/ICSharpCode.NRefactory/TypeSystem/Implementation/VoidTypeDefinition.cs +++ b/ICSharpCode.NRefactory/TypeSystem/Implementation/VoidTypeDefinition.cs @@ -24,6 +24,7 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation /// /// Special type definition for 'void'. /// + [Serializable] public class VoidTypeDefinition : DefaultTypeDefinition { public VoidTypeDefinition(IProjectContent projectContent) diff --git a/ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs b/ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs index 3e0251845a..2e5bf8849a 100644 --- a/ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/IntersectionType.cs @@ -30,6 +30,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// /// Represents the intersection of several types. /// + [Serializable] public class IntersectionType : AbstractType { readonly ReadOnlyCollection types; diff --git a/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs b/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs index 9cd4f872e8..862abbc3fb 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ParameterizedType.cs @@ -36,6 +36,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// type parameters in the signatures of the members are replaced with /// the type arguments. /// + [Serializable] public sealed class ParameterizedType : Immutable, IType { readonly ITypeDefinition genericType; @@ -327,6 +328,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// ParameterizedTypeReference is a reference to generic class that specifies the type parameters. /// Example: List<string> /// + [Serializable] public sealed class ParameterizedTypeReference : ITypeReference, ISupportsInterning { public static ITypeReference Create(ITypeReference genericType, IEnumerable typeArguments) diff --git a/ICSharpCode.NRefactory/TypeSystem/PointerType.cs b/ICSharpCode.NRefactory/TypeSystem/PointerType.cs index 2b2a54d6d7..07dc29c1a2 100644 --- a/ICSharpCode.NRefactory/TypeSystem/PointerType.cs +++ b/ICSharpCode.NRefactory/TypeSystem/PointerType.cs @@ -22,6 +22,7 @@ using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.TypeSystem { + [Serializable] public sealed class PointerType : TypeWithElementType { public PointerType(IType elementType) : base(elementType) @@ -69,6 +70,7 @@ namespace ICSharpCode.NRefactory.TypeSystem } } + [Serializable] public class PointerTypeReference : ITypeReference { readonly ITypeReference elementType; diff --git a/ICSharpCode.NRefactory/TypeSystem/SharedTypes.cs b/ICSharpCode.NRefactory/TypeSystem/SharedTypes.cs index 62bb562adf..6fc19ae88f 100644 --- a/ICSharpCode.NRefactory/TypeSystem/SharedTypes.cs +++ b/ICSharpCode.NRefactory/TypeSystem/SharedTypes.cs @@ -70,6 +70,7 @@ namespace ICSharpCode.NRefactory.TypeSystem * would have to return true even though these are two distinct definitions. */ + [Serializable] sealed class SharedTypeImpl : AbstractType { readonly TypeKind kind; diff --git a/ICSharpCode.NRefactory/Utils/7BitEncodedInts.cs b/ICSharpCode.NRefactory/Utils/7BitEncodedInts.cs new file mode 100644 index 0000000000..b16047c95c --- /dev/null +++ b/ICSharpCode.NRefactory/Utils/7BitEncodedInts.cs @@ -0,0 +1,117 @@ +// Copyright (c) 2011 Daniel Grunwald +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; + +namespace ICSharpCode.NRefactory.Utils +{ + /// + /// A binary reader that can read the output of BinaryWriterWith7BitEncodedInts. + /// + public sealed class BinaryReaderWith7BitEncodedInts : BinaryReader + { + public BinaryReaderWith7BitEncodedInts(Stream stream) : base(stream) + { + } + + public override short ReadInt16() + { + return unchecked((short)(ushort)base.Read7BitEncodedInt()); + } + + public override ushort ReadUInt16() + { + return unchecked((ushort)base.Read7BitEncodedInt()); + } + + public override int ReadInt32() + { + return base.Read7BitEncodedInt(); + } + + public override uint ReadUInt32() + { + return unchecked((uint)base.Read7BitEncodedInt()); + } + + public override long ReadInt64() + { + return unchecked((long)this.ReadUInt64()); + } + + public override ulong ReadUInt64() + { + ulong num = 0; + int shift = 0; + while (shift < 64) { + byte b = this.ReadByte(); + num |= (ulong)(b & 127) << shift; + shift += 7; + if ((b & 128) == 0) { + return num; + } + } + throw new FormatException("Invalid 7-bit int64"); + } + } + + /// + /// A binary writer that encodes all integers as 7-bit-encoded-ints. + /// + public sealed class BinaryWriterWith7BitEncodedInts : BinaryWriter + { + public BinaryWriterWith7BitEncodedInts(Stream stream) : base(stream) + { + } + + public override void Write(short value) + { + base.Write7BitEncodedInt(unchecked((ushort)value)); + } + + public override void Write(ushort value) + { + base.Write7BitEncodedInt(value); + } + + public override void Write(int value) + { + base.Write7BitEncodedInt(value); + } + + public override void Write(uint value) + { + base.Write7BitEncodedInt(unchecked((int)value)); + } + + public override void Write(long value) + { + this.Write(unchecked((ulong)value)); + } + + public override void Write(ulong value) + { + while (value >= 128) { + this.Write(unchecked((byte)(value | 128u))); + value >>= 7; + } + this.Write(unchecked((byte)value)); + } + } +} diff --git a/ICSharpCode.NRefactory/Utils/BitVector16.cs b/ICSharpCode.NRefactory/Utils/BitVector16.cs index b5ab44bee8..a8b5122159 100644 --- a/ICSharpCode.NRefactory/Utils/BitVector16.cs +++ b/ICSharpCode.NRefactory/Utils/BitVector16.cs @@ -23,6 +23,7 @@ namespace ICSharpCode.NRefactory.Utils /// /// Holds 16 boolean values. /// + [Serializable] public struct BitVector16 : IEquatable { ushort data; diff --git a/ICSharpCode.NRefactory/Utils/EmptyList.cs b/ICSharpCode.NRefactory/Utils/EmptyList.cs index 4022223a83..f39e6f2916 100644 --- a/ICSharpCode.NRefactory/Utils/EmptyList.cs +++ b/ICSharpCode.NRefactory/Utils/EmptyList.cs @@ -23,6 +23,7 @@ using System.Collections.ObjectModel; namespace ICSharpCode.NRefactory { + [Serializable] sealed class EmptyList : IList, IEnumerator { public static readonly IList Instance = new EmptyList(); diff --git a/ICSharpCode.NRefactory/Utils/FastSerializer.cs b/ICSharpCode.NRefactory/Utils/FastSerializer.cs new file mode 100644 index 0000000000..a823c2db1a --- /dev/null +++ b/ICSharpCode.NRefactory/Utils/FastSerializer.cs @@ -0,0 +1,1148 @@ +// Copyright (c) 2011 Daniel Grunwald +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; + +namespace ICSharpCode.NRefactory.Utils +{ + public class FastSerializer + { + #region Serialization + sealed class ReferenceComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(object a, object b) + { + return a == b; + } + + int IEqualityComparer.GetHashCode(object obj) + { + return RuntimeHelpers.GetHashCode(obj); + } + } + + sealed class SerializationContext + { + readonly Dictionary objectToID = new Dictionary(new ReferenceComparer()); + readonly List instances = new List(); // index: object ID + readonly List typeIDs = new List(); // index: object ID + int stringTypeID = -1; + int typeCountForObjects = 0; + + readonly Dictionary typeToID = new Dictionary(); + readonly List types = new List(); // index: type ID + readonly List writers = new List(); // index: type ID + + readonly FastSerializer fastSerializer; + public readonly BinaryWriter writer; + + internal SerializationContext(FastSerializer fastSerializer, BinaryWriter writer) + { + this.fastSerializer = fastSerializer; + this.writer = writer; + instances.Add(null); // use object ID 0 for null + typeIDs.Add(-1); + } + + #region Scanning + /// + /// Marks an instance for future scanning. + /// + public void Mark(object instance) + { + if (instance == null || objectToID.ContainsKey(instance)) + return; + Log(" Mark {0}", instance.GetType().Name); + + objectToID.Add(instance, instances.Count); + instances.Add(instance); + } + + internal void Scan() + { + Log("Scanning..."); + List objectScanners = new List(); // index: type ID + // starting from 1, because index 0 is null + for (int i = 1; i < instances.Count; i++) { + object instance = instances[i]; + ISerializable serializable = instance as ISerializable; + Type type = instance.GetType(); + Log("Scan #{0}: {1}", i, type.Name); + int typeID; + if (!typeToID.TryGetValue(type, out typeID)) { + typeID = types.Count; + typeToID.Add(type, typeID); + types.Add(type); + Log("Registered type %{0}: {1}", typeID, type); + if (type == typeof(string)) { + stringTypeID = typeID; + } + objectScanners.Add(serializable != null ? null : fastSerializer.GetScanner(type)); + writers.Add(serializable != null ? serializationInfoWriter : fastSerializer.GetWriter(type)); + } + typeIDs.Add(typeID); + if (serializable != null) { + SerializationInfo info = new SerializationInfo(type, fastSerializer.formatterConverter); + serializable.GetObjectData(info, fastSerializer.streamingContext); + instances[i] = info; + foreach (SerializationEntry entry in info) { + Mark(entry.Value); + } + } else { + objectScanners[typeID](this, instance); + } + } + } + #endregion + + #region Scan Types + internal void ScanTypes() + { + typeCountForObjects = types.Count; + for (int i = 0; i < types.Count; i++) { + foreach (FieldInfo field in GetSerializableFields(types[i])) { + if (!typeToID.ContainsKey(field.FieldType)) { + typeToID.Add(field.FieldType, types.Count); + types.Add(field.FieldType); + } + } + } + } + #endregion + + #region Writing + public void WriteObjectID(object instance) + { + int id = (instance == null) ? 0 : objectToID[instance]; + if (instances.Count <= ushort.MaxValue) + writer.Write((ushort)id); + else + writer.Write(id); + } + + internal void Write() + { + Log("Writing..."); + // Write out type information + writer.Write(types.Count); + writer.Write(instances.Count); + writer.Write(typeCountForObjects); + writer.Write(stringTypeID); + foreach (Type type in types) { + writer.Write(type.AssemblyQualifiedName); + } + foreach (Type type in types) { + if (type.IsArray || type.IsPrimitive || typeof(ISerializable).IsAssignableFrom(type)) { + writer.Write(byte.MaxValue); + } else { + var fields = GetSerializableFields(type); + if (fields.Count >= byte.MaxValue) + throw new SerializationException("Too many fields."); + writer.Write((byte)fields.Count); + foreach (var field in fields) { + int typeID = typeToID[field.FieldType]; + if (types.Count <= ushort.MaxValue) + writer.Write((ushort)typeID); + else + writer.Write(typeID); + writer.Write(field.Name); + } + } + } + + // Write out information necessary to create the instances + // starting from 1, because index 0 is null + for (int i = 1; i < instances.Count; i++) { + int typeID = typeIDs[i]; + if (types.Count <= ushort.MaxValue) + writer.Write((ushort)typeID); + else + writer.Write(typeID); + if (typeID == stringTypeID) { + // Strings are written to the output immediately + // - we can't create an empty string and fill it later + writer.Write((string)instances[i]); + } else if (types[typeID].IsArray) { + // For arrays, write down the length, because we need that to create the array instance + writer.Write(((Array)instances[i]).Length); + } + } + // Write out information necessary to fill data into the instances + for (int i = 1; i < instances.Count; i++) { + Log("0x{2:x6}, Write #{0}: {1}", i, types[typeIDs[i]].Name, writer.BaseStream.Position); + writers[typeIDs[i]](this, instances[i]); + } + Log("Serialization done."); + } + #endregion + } + + #region Object Scanners + delegate void ObjectScanner(SerializationContext context, object instance); + + static readonly MethodInfo mark = typeof(SerializationContext).GetMethod("Mark", new[] { typeof(object) }); + static readonly FieldInfo writerField = typeof(SerializationContext).GetField("writer"); + + Dictionary scanners = new Dictionary(); + + ObjectScanner GetScanner(Type type) + { + ObjectScanner scanner; + if (!scanners.TryGetValue(type, out scanner)) { + scanner = CreateScanner(type); + scanners.Add(type, scanner); + } + return scanner; + } + + ObjectScanner CreateScanner(Type type) + { + bool isArray = type.IsArray; + if (isArray) { + if (type.GetArrayRank() != 1) + throw new NotImplementedException(); + type = type.GetElementType(); + if (!type.IsValueType) { + return delegate (SerializationContext context, object array) { + foreach (object val in (object[])array) { + context.Mark(val); + } + }; + } + } + for (Type baseType = type; baseType != null; baseType = baseType.BaseType) { + if (!baseType.IsSerializable) + throw new SerializationException("Type " + baseType + " is not [Serializable]."); + } + List fields = GetSerializableFields(type); + fields.RemoveAll(f => !IsReferenceOrContainsReferences(f.FieldType)); + if (fields.Count == 0) { + // The scanner has nothing to do for this object. + return delegate { }; + } + + DynamicMethod dynamicMethod = new DynamicMethod( + (isArray ? "ScanArray_" : "Scan_") + type.Name, + typeof(void), new [] { typeof(SerializationContext), typeof(object) }, + true); + ILGenerator il = dynamicMethod.GetILGenerator(); + + + if (isArray) { + var instance = il.DeclareLocal(type.MakeArrayType()); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Castclass, type.MakeArrayType()); + il.Emit(OpCodes.Stloc, instance); // instance = (type[])arg_1; + + // for (int i = 0; i < instance.Length; i++) scan instance[i]; + var loopStart = il.DefineLabel(); + var loopHead = il.DefineLabel(); + var loopVariable = il.DeclareLocal(typeof(int)); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc, loopVariable); // loopVariable = 0 + il.Emit(OpCodes.Br, loopHead); // goto loopHead; + + il.MarkLabel(loopStart); + + il.Emit(OpCodes.Ldloc, instance); // instance + il.Emit(OpCodes.Ldloc, loopVariable); // instance, loopVariable + il.Emit(OpCodes.Ldelem, type); // &instance[loopVariable] + EmitScanValueType(il, type); + + + il.Emit(OpCodes.Ldloc, loopVariable); // loopVariable + il.Emit(OpCodes.Ldc_I4_1); // loopVariable, 1 + il.Emit(OpCodes.Add); // loopVariable+1 + il.Emit(OpCodes.Stloc, loopVariable); // loopVariable++; + + il.MarkLabel(loopHead); + il.Emit(OpCodes.Ldloc, loopVariable); // loopVariable + il.Emit(OpCodes.Ldloc, instance); // loopVariable, instance + il.Emit(OpCodes.Ldlen); // loopVariable, instance.Length + il.Emit(OpCodes.Conv_I4); + il.Emit(OpCodes.Blt, loopStart); // if (loopVariable < instance.Length) goto loopStart; + } else if (type.IsValueType) { + // boxed value type + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Unbox_Any, type); + EmitScanValueType(il, type); + } else { + // reference type + var instance = il.DeclareLocal(type); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Castclass, type); + il.Emit(OpCodes.Stloc, instance); // instance = (type)arg_1; + + foreach (FieldInfo field in fields) { + EmitScanField(il, instance, field); // scan instance.Field + } + } + il.Emit(OpCodes.Ret); + return (ObjectScanner)dynamicMethod.CreateDelegate(typeof(ObjectScanner)); + } + + /// + /// Emit 'scan instance.Field'. + /// Stack transition: ... => ... + /// + void EmitScanField(ILGenerator il, LocalBuilder instance, FieldInfo field) + { + if (field.FieldType.IsValueType) { + il.Emit(OpCodes.Ldloc, instance); // instance + il.Emit(OpCodes.Ldfld, field); // instance.field + EmitScanValueType(il, field.FieldType); + } else { + il.Emit(OpCodes.Ldarg_0); // context + il.Emit(OpCodes.Ldloc, instance); // context, instance + il.Emit(OpCodes.Ldfld, field); // context, instance.field + il.Emit(OpCodes.Call, mark); // context.Mark(instance.field); + } + } + + /// + /// Stack transition: ..., value => ... + /// + void EmitScanValueType(ILGenerator il, Type valType) + { + var fieldRef = il.DeclareLocal(valType); + il.Emit(OpCodes.Stloc, fieldRef); + + foreach (FieldInfo field in GetSerializableFields(valType)) { + if (IsReferenceOrContainsReferences(field.FieldType)) { + EmitScanField(il, fieldRef, field); + } + } + } + + static List GetSerializableFields(Type type) + { + List fields = new List(); + for (Type baseType = type; baseType != null; baseType = baseType.BaseType) { + FieldInfo[] declFields = baseType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly); + Array.Sort(declFields, (a,b) => a.Name.CompareTo(b.Name)); + fields.AddRange(declFields); + } + fields.RemoveAll(f => f.IsNotSerialized); + return fields; + } + + static bool IsReferenceOrContainsReferences(Type type) + { + if (!type.IsValueType) + return true; + if (type.IsPrimitive) + return false; + foreach (FieldInfo field in GetSerializableFields(type)) { + if (IsReferenceOrContainsReferences(field.FieldType)) + return true; + } + return false; + } + #endregion + + #region Object Writers + delegate void ObjectWriter(SerializationContext context, object instance); + + static readonly MethodInfo writeObjectID = typeof(SerializationContext).GetMethod("WriteObjectID", new[] { typeof(object) }); + + static readonly MethodInfo writeByte = typeof(BinaryWriter).GetMethod("Write", new[] { typeof(byte) }); + static readonly MethodInfo writeShort = typeof(BinaryWriter).GetMethod("Write", new[] { typeof(short) }); + static readonly MethodInfo writeInt = typeof(BinaryWriter).GetMethod("Write", new[] { typeof(int) }); + static readonly MethodInfo writeLong = typeof(BinaryWriter).GetMethod("Write", new[] { typeof(long) }); + static readonly MethodInfo writeFloat = typeof(BinaryWriter).GetMethod("Write", new[] { typeof(float) }); + static readonly MethodInfo writeDouble = typeof(BinaryWriter).GetMethod("Write", new[] { typeof(double) }); + OpCode callVirt = OpCodes.Callvirt; + + static readonly ObjectWriter serializationInfoWriter = delegate(SerializationContext context, object instance) { + BinaryWriter writer = context.writer; + SerializationInfo info = (SerializationInfo)instance; + writer.Write(info.MemberCount); + foreach (SerializationEntry entry in info) { + writer.Write(entry.Name); + context.WriteObjectID(entry.Value); + } + }; + + Dictionary writers = new Dictionary(); + + ObjectWriter GetWriter(Type type) + { + ObjectWriter writer; + if (!writers.TryGetValue(type, out writer)) { + writer = CreateWriter(type); + writers.Add(type, writer); + } + return writer; + } + + ObjectWriter CreateWriter(Type type) + { + if (type == typeof(string)) { + // String contents are written in the object creation section, + // not into the field value section. + return delegate {}; + } + bool isArray = type.IsArray; + if (isArray) { + if (type.GetArrayRank() != 1) + throw new NotImplementedException(); + type = type.GetElementType(); + if (!type.IsValueType) { + return delegate (SerializationContext context, object array) { + foreach (object val in (object[])array) { + context.WriteObjectID(val); + } + }; + } else if (type == typeof(byte[])) { + return delegate (SerializationContext context, object array) { + context.writer.Write((byte[])array); + }; + } + } + List fields = GetSerializableFields(type); + if (fields.Count == 0) { + // The writer has nothing to do for this object. + return delegate { }; + } + + + DynamicMethod dynamicMethod = new DynamicMethod( + (isArray ? "WriteArray_" : "Write_") + type.Name, + typeof(void), new [] { typeof(SerializationContext), typeof(object) }, + true); + ILGenerator il = dynamicMethod.GetILGenerator(); + + var writer = il.DeclareLocal(typeof(BinaryWriter)); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, writerField); + il.Emit(OpCodes.Stloc, writer); // writer = context.writer; + + if (isArray) { + var instance = il.DeclareLocal(type.MakeArrayType()); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Castclass, type.MakeArrayType()); + il.Emit(OpCodes.Stloc, instance); // instance = (type[])arg_1; + + // for (int i = 0; i < instance.Length; i++) write instance[i]; + + var loopStart = il.DefineLabel(); + var loopHead = il.DefineLabel(); + var loopVariable = il.DeclareLocal(typeof(int)); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc, loopVariable); // loopVariable = 0 + il.Emit(OpCodes.Br, loopHead); // goto loopHead; + + il.MarkLabel(loopStart); + + if (type.IsEnum || type.IsPrimitive) { + if (type.IsEnum) { + type = type.GetEnumUnderlyingType(); + } + Debug.Assert(type.IsPrimitive); + il.Emit(OpCodes.Ldloc, writer); // writer + il.Emit(OpCodes.Ldloc, instance); // writer, instance + il.Emit(OpCodes.Ldloc, loopVariable); // writer, instance, loopVariable + switch (Type.GetTypeCode(type)) { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + il.Emit(OpCodes.Ldelem_I1); // writer, instance[loopVariable] + il.Emit(callVirt, writeByte); // writer.Write(instance[loopVariable]); + break; + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + il.Emit(OpCodes.Ldelem_I2); // writer, instance[loopVariable] + il.Emit(callVirt, writeShort); // writer.Write(instance[loopVariable]); + break; + case TypeCode.Int32: + case TypeCode.UInt32: + il.Emit(OpCodes.Ldelem_I4); // writer, instance[loopVariable] + il.Emit(callVirt, writeInt); // writer.Write(instance[loopVariable]); + break; + case TypeCode.Int64: + case TypeCode.UInt64: + il.Emit(OpCodes.Ldelem_I8); // writer, instance[loopVariable] + il.Emit(callVirt, writeLong); // writer.Write(instance[loopVariable]); + break; + case TypeCode.Single: + il.Emit(OpCodes.Ldelem_R4); // writer, instance[loopVariable] + il.Emit(callVirt, writeFloat); // writer.Write(instance[loopVariable]); + break; + case TypeCode.Double: + il.Emit(OpCodes.Ldelem_R8); // writer, instance[loopVariable] + il.Emit(callVirt, writeDouble); // writer.Write(instance[loopVariable]); + break; + default: + throw new NotSupportedException("Unknown primitive type " + type); + } + } else { + il.Emit(OpCodes.Ldloc, instance); // instance + il.Emit(OpCodes.Ldloc, loopVariable); // instance, loopVariable + il.Emit(OpCodes.Ldelem, type); // instance[loopVariable] + EmitWriteValueType(il, writer, type); + } + + il.Emit(OpCodes.Ldloc, loopVariable); // loopVariable + il.Emit(OpCodes.Ldc_I4_1); // loopVariable, 1 + il.Emit(OpCodes.Add); // loopVariable+1 + il.Emit(OpCodes.Stloc, loopVariable); // loopVariable++; + + il.MarkLabel(loopHead); + il.Emit(OpCodes.Ldloc, loopVariable); // loopVariable + il.Emit(OpCodes.Ldloc, instance); // loopVariable, instance + il.Emit(OpCodes.Ldlen); // loopVariable, instance.Length + il.Emit(OpCodes.Conv_I4); + il.Emit(OpCodes.Blt, loopStart); // if (loopVariable < instance.Length) goto loopStart; + } else if (type.IsValueType) { + // boxed value type + if (type.IsEnum || type.IsPrimitive) { + il.Emit(OpCodes.Ldloc, writer); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Unbox_Any, type); + WritePrimitiveValue(il, type); + } else { + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Unbox_Any, type); + EmitWriteValueType(il, writer, type); + } + } else { + // reference type + var instance = il.DeclareLocal(type); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Castclass, type); + il.Emit(OpCodes.Stloc, instance); // instance = (type)arg_1; + + foreach (FieldInfo field in fields) { + EmitWriteField(il, writer, instance, field); // write instance.Field + } + } + il.Emit(OpCodes.Ret); + return (ObjectWriter)dynamicMethod.CreateDelegate(typeof(ObjectWriter)); + } + + /// + /// Emit 'write instance.Field'. + /// Stack transition: ... => ... + /// + void EmitWriteField(ILGenerator il, LocalBuilder writer, LocalBuilder instance, FieldInfo field) + { + Type fieldType = field.FieldType; + if (fieldType.IsValueType) { + if (fieldType.IsPrimitive || fieldType.IsEnum) { + il.Emit(OpCodes.Ldloc, writer); // writer + il.Emit(OpCodes.Ldloc, instance); // writer, instance + il.Emit(OpCodes.Ldfld, field); // writer, instance.field + WritePrimitiveValue(il, fieldType); + } else { + il.Emit(OpCodes.Ldloc, instance); // instance + il.Emit(OpCodes.Ldfld, field); // instance.field + EmitWriteValueType(il, writer, fieldType); + } + } else { + il.Emit(OpCodes.Ldarg_0); // context + il.Emit(OpCodes.Ldloc, instance); // context, instance + il.Emit(OpCodes.Ldfld, field); // context, instance.field + il.Emit(OpCodes.Call, writeObjectID); // context.WriteObjectID(instance.field); + } + } + + /// + /// Writes a primitive value of the specified type. + /// Stack transition: ..., writer, value => ... + /// + void WritePrimitiveValue(ILGenerator il, Type fieldType) + { + if (fieldType.IsEnum) { + fieldType = fieldType.GetEnumUnderlyingType(); + Debug.Assert(fieldType.IsPrimitive); + } + switch (Type.GetTypeCode(fieldType)) { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + il.Emit(callVirt, writeByte); // writer.Write(value); + break; + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + il.Emit(callVirt, writeShort); // writer.Write(value); + break; + case TypeCode.Int32: + case TypeCode.UInt32: + il.Emit(callVirt, writeInt); // writer.Write(value); + break; + case TypeCode.Int64: + case TypeCode.UInt64: + il.Emit(callVirt, writeLong); // writer.Write(value); + break; + case TypeCode.Single: + il.Emit(callVirt, writeFloat); // writer.Write(value); + break; + case TypeCode.Double: + il.Emit(callVirt, writeDouble); // writer.Write(value); + break; + default: + throw new NotSupportedException("Unknown primitive type " + fieldType); + } + } + + /// + /// Stack transition: ..., value => ... + /// + void EmitWriteValueType(ILGenerator il, LocalBuilder writer, Type valType) + { + Debug.Assert(valType.IsValueType); + Debug.Assert(!(valType.IsEnum || valType.IsPrimitive)); + + var fieldVal = il.DeclareLocal(valType); + il.Emit(OpCodes.Stloc, fieldVal); + + foreach (FieldInfo field in GetSerializableFields(valType)) { + EmitWriteField(il, writer, fieldVal, field); + } + } + #endregion + + StreamingContext streamingContext = new StreamingContext(StreamingContextStates.All); + FormatterConverter formatterConverter = new FormatterConverter(); + + public void Serialize(Stream stream, object instance) + { + Serialize(new BinaryWriter(stream), instance); + } + + public void Serialize(BinaryWriter writer, object instance) + { + SerializationContext context = new SerializationContext(this, writer); + context.Mark(instance); + context.Scan(); + context.ScanTypes(); + context.Write(); + } + + delegate void TypeSerializer(object instance, SerializationContext context); + #endregion + + #region Deserialization + sealed class DeserializationContext + { + public Type[] Types; // index: type ID + public ObjectReader[] ObjectReaders; // index: type ID + + public object[] Objects; // index: object ID + + public BinaryReader Reader; + + public object ReadObject() + { + if (this.Objects.Length <= ushort.MaxValue) + return this.Objects[Reader.ReadUInt16()]; + else + return this.Objects[Reader.ReadInt32()]; + } + + #region DeserializeTypeDescriptions + internal int ReadFieldTypeID() + { + if (this.Types.Length <= ushort.MaxValue) + return Reader.ReadUInt16(); + else + return Reader.ReadInt32(); + } + + internal void DeserializeTypeDescriptions(FastSerializer fastSerializer) + { + for (int i = 0; i < this.Types.Length; i++) { + Type type = this.Types[i]; + bool isCustomSerialization = typeof(ISerializable).IsAssignableFrom(type); + bool typeIsSpecial = type.IsArray || type.IsPrimitive || isCustomSerialization; + + byte serializedFieldCount = Reader.ReadByte(); + if (serializedFieldCount == byte.MaxValue) { + // special type + if (!typeIsSpecial) + throw new SerializationException("Type " + type + " was serialized as special type, but isn't special now."); + } else { + if (typeIsSpecial) + throw new SerializationException("Type " + type.FullName + " wasn't serialized as special type, but is special now."); + + var availableFields = GetSerializableFields(this.Types[i]); + if (availableFields.Count != serializedFieldCount) + throw new SerializationException("Number of fields on " + type.FullName + " has changed."); + for (int j = 0; j < serializedFieldCount; j++) { + int fieldTypeID = ReadFieldTypeID(); + + string fieldName = Reader.ReadString(); + FieldInfo fieldInfo = availableFields[j]; + if (fieldInfo.Name != fieldName) + throw new SerializationException("Field mismatch on type " + type.FullName); + if (fieldInfo.FieldType != this.Types[fieldTypeID]) + throw new SerializationException(type.FullName + "." + fieldName + " was serialized as " + this.Types[fieldTypeID] + ", but now is " + fieldInfo.FieldType); + } + } + + if (i < this.ObjectReaders.Length && !isCustomSerialization) + this.ObjectReaders[i] = fastSerializer.GetReader(type); + } + } + #endregion + } + + delegate void ObjectReader(DeserializationContext context, object instance); + + public object Deserialize(Stream stream) + { + return Deserialize(new BinaryReader(stream)); + } + + public object Deserialize(BinaryReader reader) + { + DeserializationContext context = new DeserializationContext(); + context.Reader = reader; + context.Types = new Type[reader.ReadInt32()]; + context.Objects = new object[reader.ReadInt32()]; + context.ObjectReaders = new ObjectReader[reader.ReadInt32()]; + int stringTypeID = reader.ReadInt32(); + for (int i = 0; i < context.Types.Length; i++) { + string typeName = reader.ReadString(); + Type type = Type.GetType(typeName); + if (type == null) + throw new SerializationException("Could not find " + typeName); + context.Types[i] = type; + } + context.DeserializeTypeDescriptions(this); + int[] typeIDByObjectID = new int[context.Objects.Length]; + for (int i = 1; i < context.Objects.Length; i++) { + int typeID = context.ReadFieldTypeID(); + + object instance; + if (typeID == stringTypeID) { + instance = reader.ReadString(); + } else { + Type type = context.Types[typeID]; + if (type.IsArray) { + int length = reader.ReadInt32(); + instance = Array.CreateInstance(type.GetElementType(), length); + } else { + instance = FormatterServices.GetUninitializedObject(type); + } + } + context.Objects[i] = instance; + typeIDByObjectID[i] = typeID; + } + List customDeserializatons = new List(); + for (int i = 1; i < context.Objects.Length; i++) { + object instance = context.Objects[i]; + int typeID = typeIDByObjectID[i]; + Log("0x{2:x6} Read #{0}: {1}", i, context.Types[typeID].Name, reader.BaseStream.Position); + ISerializable serializable = instance as ISerializable; + if (serializable != null) { + Type type = context.Types[typeID]; + SerializationInfo info = new SerializationInfo(type, formatterConverter); + int count = reader.ReadInt32(); + for (int j = 0; j < count; j++) { + string name = reader.ReadString(); + object val = context.ReadObject(); + info.AddValue(name, val); + } + CustomDeserializationAction action = GetCustomDeserializationAction(type); + customDeserializatons.Add(new CustomDeserialization(instance, info, action)); + } else { + context.ObjectReaders[typeID](context, instance); + } + } + Log("File was read successfully, now running {0} custom deserializations...", customDeserializatons.Count); + foreach (CustomDeserialization customDeserializaton in customDeserializatons) { + customDeserializaton.Run(streamingContext); + } + for (int i = 1; i < context.Objects.Length; i++) { + IDeserializationCallback dc = context.Objects[i] as IDeserializationCallback; + if (dc != null) + dc.OnDeserialization(null); + } + + if (context.Objects.Length <= 1) + return null; + else + return context.Objects[1]; + } + + #region Object Reader + static readonly FieldInfo readerField = typeof(DeserializationContext).GetField("Reader"); + static readonly MethodInfo readObject = typeof(DeserializationContext).GetMethod("ReadObject"); + + static readonly MethodInfo readByte = typeof(BinaryReader).GetMethod("ReadByte"); + static readonly MethodInfo readShort = typeof(BinaryReader).GetMethod("ReadInt16"); + static readonly MethodInfo readInt = typeof(BinaryReader).GetMethod("ReadInt32"); + static readonly MethodInfo readLong = typeof(BinaryReader).GetMethod("ReadInt64"); + static readonly MethodInfo readFloat = typeof(BinaryReader).GetMethod("ReadSingle"); + static readonly MethodInfo readDouble = typeof(BinaryReader).GetMethod("ReadDouble"); + + Dictionary readers = new Dictionary(); + + ObjectReader GetReader(Type type) + { + ObjectReader reader; + if (!readers.TryGetValue(type, out reader)) { + reader = CreateReader(type); + readers.Add(type, reader); + } + return reader; + } + + ObjectReader CreateReader(Type type) + { + if (type == typeof(string)) { + // String contents are written in the object creation section, + // not into the field value section; so there's nothing to read here. + return delegate {}; + } + bool isArray = type.IsArray; + if (isArray) { + if (type.GetArrayRank() != 1) + throw new NotImplementedException(); + type = type.GetElementType(); + if (!type.IsValueType) { + return delegate (DeserializationContext context, object arrayInstance) { + object[] array = (object[])arrayInstance; + for (int i = 0; i < array.Length; i++) { + array[i] = context.ReadObject(); + } + }; + } else if (type == typeof(byte[])) { + return delegate (DeserializationContext context, object arrayInstance) { + byte[] array = (byte[])arrayInstance; + BinaryReader binaryReader = context.Reader; + int pos = 0; + int bytesRead; + do { + bytesRead = binaryReader.Read(array, pos, array.Length - pos); + pos += bytesRead; + } while (bytesRead > 0); + if (pos != array.Length) + throw new EndOfStreamException(); + }; + } + } + var fields = GetSerializableFields(type); + if (fields.Count == 0) { + // The reader has nothing to do for this object. + return delegate { }; + } + + DynamicMethod dynamicMethod = new DynamicMethod( + (isArray ? "ReadArray_" : "Read_") + type.Name, + MethodAttributes.Public | MethodAttributes.Static, + CallingConventions.Standard, + typeof(void), new [] { typeof(DeserializationContext), typeof(object) }, + type, + true); + ILGenerator il = dynamicMethod.GetILGenerator(); + + var reader = il.DeclareLocal(typeof(BinaryReader)); + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, readerField); + il.Emit(OpCodes.Stloc, reader); // reader = context.reader; + + if (isArray) { + var instance = il.DeclareLocal(type.MakeArrayType()); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Castclass, type.MakeArrayType()); + il.Emit(OpCodes.Stloc, instance); // instance = (type[])arg_1; + + // for (int i = 0; i < instance.Length; i++) read &instance[i]; + + var loopStart = il.DefineLabel(); + var loopHead = il.DefineLabel(); + var loopVariable = il.DeclareLocal(typeof(int)); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc, loopVariable); // loopVariable = 0 + il.Emit(OpCodes.Br, loopHead); // goto loopHead; + + il.MarkLabel(loopStart); + + if (type.IsEnum || type.IsPrimitive) { + if (type.IsEnum) { + type = type.GetEnumUnderlyingType(); + } + Debug.Assert(type.IsPrimitive); + il.Emit(OpCodes.Ldloc, instance); // instance + il.Emit(OpCodes.Ldloc, loopVariable); // instance, loopVariable + EmitReadValueType(il, reader, type); // instance, loopVariable, value + switch (Type.GetTypeCode(type)) { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + il.Emit(OpCodes.Stelem_I1); // instance[loopVariable] = value; + break; + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + il.Emit(OpCodes.Stelem_I2); // instance[loopVariable] = value; + break; + case TypeCode.Int32: + case TypeCode.UInt32: + il.Emit(OpCodes.Stelem_I4); // instance[loopVariable] = value; + break; + case TypeCode.Int64: + case TypeCode.UInt64: + il.Emit(OpCodes.Stelem_I8); // instance[loopVariable] = value; + break; + case TypeCode.Single: + il.Emit(OpCodes.Stelem_R4); // instance[loopVariable] = value; + break; + case TypeCode.Double: + il.Emit(OpCodes.Stelem_R8); // instance[loopVariable] = value; + break; + default: + throw new NotSupportedException("Unknown primitive type " + type); + } + } else { + il.Emit(OpCodes.Ldloc, instance); // instance + il.Emit(OpCodes.Ldloc, loopVariable); // instance, loopVariable + il.Emit(OpCodes.Ldelema, type); // instance[loopVariable] + EmitReadValueType(il, reader, type); + } + + il.Emit(OpCodes.Ldloc, loopVariable); // loopVariable + il.Emit(OpCodes.Ldc_I4_1); // loopVariable, 1 + il.Emit(OpCodes.Add); // loopVariable+1 + il.Emit(OpCodes.Stloc, loopVariable); // loopVariable++; + + il.MarkLabel(loopHead); + il.Emit(OpCodes.Ldloc, loopVariable); // loopVariable + il.Emit(OpCodes.Ldloc, instance); // loopVariable, instance + il.Emit(OpCodes.Ldlen); // loopVariable, instance.Length + il.Emit(OpCodes.Conv_I4); + il.Emit(OpCodes.Blt, loopStart); // if (loopVariable < instance.Length) goto loopStart; + } else if (type.IsValueType) { + // boxed value type + il.Emit(OpCodes.Ldarg_1); // instance + il.Emit(OpCodes.Unbox, type); // &(Type)instance + if (type.IsEnum || type.IsPrimitive) { + if (type.IsEnum) { + type = type.GetEnumUnderlyingType(); + } + Debug.Assert(type.IsPrimitive); + ReadPrimitiveValue(il, reader, type); // &(Type)instance, value + switch (Type.GetTypeCode(type)) { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + il.Emit(OpCodes.Stind_I1); + break; + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + il.Emit(OpCodes.Stind_I2); + break; + case TypeCode.Int32: + case TypeCode.UInt32: + il.Emit(OpCodes.Stind_I4); + break; + case TypeCode.Int64: + case TypeCode.UInt64: + il.Emit(OpCodes.Stind_I8); + break; + case TypeCode.Single: + il.Emit(OpCodes.Stind_R4); + break; + case TypeCode.Double: + il.Emit(OpCodes.Stind_R8); + break; + default: + throw new NotSupportedException("Unknown primitive type " + type); + } + } else { + EmitReadValueType(il, reader, type); + } + } else { + // reference type + var instance = il.DeclareLocal(type); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Castclass, type); + il.Emit(OpCodes.Stloc, instance); // instance = (type)arg_1; + + foreach (FieldInfo field in fields) { + EmitReadField(il, reader, instance, field); // read instance.Field + } + } + il.Emit(OpCodes.Ret); + return (ObjectReader)dynamicMethod.CreateDelegate(typeof(ObjectReader)); + } + + void EmitReadField(ILGenerator il, LocalBuilder reader, LocalBuilder instance, FieldInfo field) + { + Type fieldType = field.FieldType; + if (fieldType.IsValueType) { + if (fieldType.IsPrimitive || fieldType.IsEnum) { + il.Emit(OpCodes.Ldloc, instance); // instance + ReadPrimitiveValue(il, reader, fieldType); // instance, value + il.Emit(OpCodes.Stfld, field); // instance.field = value; + } else { + il.Emit(OpCodes.Ldloc, instance); // instance + il.Emit(OpCodes.Ldflda, field); // &instance.field + EmitReadValueType(il, reader, fieldType); + } + } else { + il.Emit(OpCodes.Ldloc, instance); // instance + il.Emit(OpCodes.Ldarg_0); // instance, context + il.Emit(OpCodes.Call, readObject); // instance, context.ReadObject() + il.Emit(OpCodes.Stfld, field); // instance.field = context.ReadObject(); + } + } + + /// + /// Reads a primitive value of the specified type. + /// Stack transition: ... => ..., value + /// + void ReadPrimitiveValue(ILGenerator il, LocalBuilder reader, Type fieldType) + { + if (fieldType.IsEnum) { + fieldType = fieldType.GetEnumUnderlyingType(); + Debug.Assert(fieldType.IsPrimitive); + } + il.Emit(OpCodes.Ldloc, reader); + switch (Type.GetTypeCode(fieldType)) { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Byte: + il.Emit(callVirt, readByte); + break; + case TypeCode.Char: + case TypeCode.Int16: + case TypeCode.UInt16: + il.Emit(callVirt, readShort); + break; + case TypeCode.Int32: + case TypeCode.UInt32: + il.Emit(callVirt, readInt); + break; + case TypeCode.Int64: + case TypeCode.UInt64: + il.Emit(callVirt, readLong); + break; + case TypeCode.Single: + il.Emit(callVirt, readFloat); + break; + case TypeCode.Double: + il.Emit(callVirt, readDouble); + break; + default: + throw new NotSupportedException("Unknown primitive type " + fieldType); + } + } + + /// + /// Stack transition: ..., field-ref => ... + /// + void EmitReadValueType(ILGenerator il, LocalBuilder reader, Type valType) + { + Debug.Assert(valType.IsValueType); + Debug.Assert(!(valType.IsEnum || valType.IsPrimitive)); + + var fieldRef = il.DeclareLocal(valType.MakeByRefType()); + il.Emit(OpCodes.Stloc, fieldRef); + + foreach (FieldInfo field in GetSerializableFields(valType)) { + EmitReadField(il, reader, fieldRef, field); + } + } + #endregion + + #region Custom Deserialization + struct CustomDeserialization + { + readonly object instance; + readonly SerializationInfo serializationInfo; + readonly CustomDeserializationAction action; + + public CustomDeserialization(object instance, SerializationInfo serializationInfo, CustomDeserializationAction action) + { + this.instance = instance; + this.serializationInfo = serializationInfo; + this.action = action; + } + + public void Run(StreamingContext context) + { + action(instance, serializationInfo, context); + } + } + + delegate void CustomDeserializationAction(object instance, SerializationInfo info, StreamingContext context); + + Dictionary customDeserializationActions = new Dictionary(); + + CustomDeserializationAction GetCustomDeserializationAction(Type type) + { + CustomDeserializationAction action; + if (!customDeserializationActions.TryGetValue(type, out action)) { + action = CreateCustomDeserializationAction(type); + customDeserializationActions.Add(type, action); + } + return action; + } + + CustomDeserializationAction CreateCustomDeserializationAction(Type type) + { + ConstructorInfo ctor = type.GetConstructor( + BindingFlags.DeclaredOnly | BindingFlags.ExactBinding | BindingFlags.Instance + | BindingFlags.NonPublic | BindingFlags.Public, + null, + new Type [] { typeof(SerializationInfo), typeof(StreamingContext) }, + null); + if (ctor == null) + throw new SerializationException("Could not find deserialization constructor for " + type.FullName); + + DynamicMethod dynamicMethod = new DynamicMethod( + "CallCtor_" + type.Name, + MethodAttributes.Public | MethodAttributes.Static, + CallingConventions.Standard, + typeof(void), new [] { typeof(object), typeof(SerializationInfo), typeof(StreamingContext) }, + type, + true); + ILGenerator il = dynamicMethod.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Call, ctor); + il.Emit(OpCodes.Ret); + return (CustomDeserializationAction)dynamicMethod.CreateDelegate(typeof(CustomDeserializationAction)); + } + #endregion + #endregion + + [Conditional("DEBUG_SERIALIZER")] + static void Log(string format, params object[] args) + { + Debug.WriteLine(format, args); + } + } +}