You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1148 lines
39 KiB
1148 lines
39 KiB
// 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<object> |
|
{ |
|
bool IEqualityComparer<object>.Equals(object a, object b) |
|
{ |
|
return a == b; |
|
} |
|
|
|
int IEqualityComparer<object>.GetHashCode(object obj) |
|
{ |
|
return RuntimeHelpers.GetHashCode(obj); |
|
} |
|
} |
|
|
|
sealed class SerializationContext |
|
{ |
|
readonly Dictionary<object, int> objectToID = new Dictionary<object, int>(new ReferenceComparer()); |
|
readonly List<object> instances = new List<object>(); // index: object ID |
|
readonly List<int> typeIDs = new List<int>(); // index: object ID |
|
int stringTypeID = -1; |
|
int typeCountForObjects = 0; |
|
|
|
readonly Dictionary<Type, int> typeToID = new Dictionary<Type, int>(); |
|
readonly List<Type> types = new List<Type>(); // index: type ID |
|
readonly List<ObjectWriter> writers = new List<ObjectWriter>(); // 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 |
|
/// <summary> |
|
/// Marks an instance for future scanning. |
|
/// </summary> |
|
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<ObjectScanner> objectScanners = new List<ObjectScanner>(); // 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<Type, ObjectScanner> scanners = new Dictionary<Type, ObjectScanner>(); |
|
|
|
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<FieldInfo> 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)); |
|
} |
|
|
|
/// <summary> |
|
/// Emit 'scan instance.Field'. |
|
/// Stack transition: ... => ... |
|
/// </summary> |
|
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); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Stack transition: ..., value => ... |
|
/// </summary> |
|
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<FieldInfo> GetSerializableFields(Type type) |
|
{ |
|
List<FieldInfo> fields = new List<FieldInfo>(); |
|
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<Type, ObjectWriter> writers = new Dictionary<Type, ObjectWriter>(); |
|
|
|
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<FieldInfo> 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)); |
|
} |
|
|
|
/// <summary> |
|
/// Emit 'write instance.Field'. |
|
/// Stack transition: ... => ... |
|
/// </summary> |
|
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); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Writes a primitive value of the specified type. |
|
/// Stack transition: ..., writer, value => ... |
|
/// </summary> |
|
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); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Stack transition: ..., value => ... |
|
/// </summary> |
|
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 BinaryWriterWith7BitEncodedInts(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 BinaryReaderWith7BitEncodedInts(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<CustomDeserialization> customDeserializatons = new List<CustomDeserialization>(); |
|
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<Type, ObjectReader> readers = new Dictionary<Type, ObjectReader>(); |
|
|
|
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(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Reads a primitive value of the specified type. |
|
/// Stack transition: ... => ..., value |
|
/// </summary> |
|
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); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Stack transition: ..., field-ref => ... |
|
/// </summary> |
|
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<Type, CustomDeserializationAction> customDeserializationActions = new Dictionary<Type, CustomDeserializationAction>(); |
|
|
|
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); |
|
} |
|
} |
|
}
|
|
|