1052 lines
32 KiB
C#
1052 lines
32 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace XCEngine
|
|
{
|
|
internal static class ScriptableObjectSerializedGraph
|
|
{
|
|
private const string FormatTag = "XCEngine.ScriptableObjectGraph";
|
|
private const int FormatVersion = 1;
|
|
private static readonly object MissingValue = new object();
|
|
|
|
private enum ValueKind : byte
|
|
{
|
|
Null = 0,
|
|
Boolean = 1,
|
|
Byte = 2,
|
|
SByte = 3,
|
|
Int16 = 4,
|
|
UInt16 = 5,
|
|
Int32 = 6,
|
|
UInt32 = 7,
|
|
Int64 = 8,
|
|
UInt64 = 9,
|
|
Single = 10,
|
|
Double = 11,
|
|
String = 12,
|
|
Enum = 13,
|
|
ObjectReference = 14,
|
|
Array = 15,
|
|
Struct = 16
|
|
}
|
|
|
|
public static string Serialize(
|
|
ScriptableObject root)
|
|
{
|
|
if (root == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
SerializationContext context =
|
|
new SerializationContext(root);
|
|
using (MemoryStream stream = new MemoryStream())
|
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
|
{
|
|
writer.Write(FormatTag);
|
|
writer.Write(FormatVersion);
|
|
writer.Write(context.rootId);
|
|
writer.Write(context.nodes.Count);
|
|
|
|
for (int i = 0; i < context.nodes.Count; ++i)
|
|
{
|
|
context.WriteNode(writer, context.nodes[i]);
|
|
}
|
|
|
|
writer.Flush();
|
|
return Convert.ToBase64String(stream.ToArray());
|
|
}
|
|
}
|
|
|
|
public static ScriptableObject Deserialize(
|
|
string serializedGraph)
|
|
{
|
|
if (string.IsNullOrEmpty(serializedGraph))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
byte[] bytes = Convert.FromBase64String(serializedGraph);
|
|
using (MemoryStream stream = new MemoryStream(bytes))
|
|
using (BinaryReader reader = new BinaryReader(stream))
|
|
{
|
|
string tag = reader.ReadString();
|
|
int version = reader.ReadInt32();
|
|
if (tag != FormatTag ||
|
|
version != FormatVersion)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int rootId = reader.ReadInt32();
|
|
int nodeCount = reader.ReadInt32();
|
|
if (rootId < 0 ||
|
|
rootId >= nodeCount ||
|
|
nodeCount < 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
List<NodeRecord> nodes =
|
|
new List<NodeRecord>(nodeCount);
|
|
for (int i = 0; i < nodeCount; ++i)
|
|
{
|
|
nodes.Add(ReadNode(reader));
|
|
}
|
|
|
|
for (int i = 0; i < nodes.Count; ++i)
|
|
{
|
|
Type type = ResolveType(
|
|
nodes[i].assemblyName,
|
|
nodes[i].typeName);
|
|
nodes[i].instance =
|
|
CreateObjectInstance(type);
|
|
}
|
|
|
|
if (nodes[rootId].instance == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (int i = 0; i < nodes.Count; ++i)
|
|
{
|
|
ApplyFields(
|
|
nodes[i].instance,
|
|
nodes[i].fields,
|
|
nodes);
|
|
}
|
|
|
|
return nodes[rootId].instance as ScriptableObject;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private sealed class SerializationContext
|
|
{
|
|
public readonly List<object> nodes = new List<object>();
|
|
public readonly int rootId;
|
|
private readonly Dictionary<object, int> m_nodeIds =
|
|
new Dictionary<object, int>(
|
|
ReferenceEqualityComparer.Instance);
|
|
|
|
public SerializationContext(
|
|
object root)
|
|
{
|
|
rootId = CollectObject(root);
|
|
}
|
|
|
|
public void WriteNode(
|
|
BinaryWriter writer,
|
|
object node)
|
|
{
|
|
WriteType(writer, node.GetType());
|
|
WriteFieldSet(writer, node, node.GetType());
|
|
}
|
|
|
|
private int CollectObject(
|
|
object value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int existingId;
|
|
if (m_nodeIds.TryGetValue(value, out existingId))
|
|
{
|
|
return existingId;
|
|
}
|
|
|
|
int id = nodes.Count;
|
|
m_nodeIds[value] = id;
|
|
nodes.Add(value);
|
|
|
|
Type type = value.GetType();
|
|
FieldInfo[] fields = GetSerializableFields(type);
|
|
for (int i = 0; i < fields.Length; ++i)
|
|
{
|
|
FieldInfo field = fields[i];
|
|
object fieldValue = field.GetValue(value);
|
|
EnsureSerializableFieldValue(
|
|
field,
|
|
fieldValue);
|
|
CollectValue(fieldValue);
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
private void CollectValue(
|
|
object value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Type type = value.GetType();
|
|
if (IsScalarType(type) ||
|
|
type.IsEnum)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (type.IsArray)
|
|
{
|
|
Array array = value as Array;
|
|
if (array == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < array.Length; ++i)
|
|
{
|
|
object elementValue = array.GetValue(i);
|
|
EnsureSerializableArrayElementValue(
|
|
type.GetElementType(),
|
|
elementValue);
|
|
CollectValue(elementValue);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (type.IsValueType)
|
|
{
|
|
FieldInfo[] fields = GetSerializableFields(type);
|
|
for (int i = 0; i < fields.Length; ++i)
|
|
{
|
|
FieldInfo field = fields[i];
|
|
object fieldValue = field.GetValue(value);
|
|
EnsureSerializableFieldValue(
|
|
field,
|
|
fieldValue);
|
|
CollectValue(fieldValue);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (CanSerializeReferenceType(type))
|
|
{
|
|
CollectObject(value);
|
|
}
|
|
}
|
|
|
|
private void WriteFieldSet(
|
|
BinaryWriter writer,
|
|
object instance,
|
|
Type type)
|
|
{
|
|
FieldInfo[] fields = GetSerializableFields(type);
|
|
writer.Write(fields.Length);
|
|
for (int i = 0; i < fields.Length; ++i)
|
|
{
|
|
FieldInfo field = fields[i];
|
|
object fieldValue = field.GetValue(instance);
|
|
EnsureSerializableFieldValue(
|
|
field,
|
|
fieldValue);
|
|
WriteType(writer, field.DeclaringType);
|
|
writer.Write(field.Name);
|
|
WriteValue(
|
|
writer,
|
|
fieldValue,
|
|
field.FieldType);
|
|
}
|
|
}
|
|
|
|
private void WriteValue(
|
|
BinaryWriter writer,
|
|
object value,
|
|
Type declaredType)
|
|
{
|
|
if (value == null)
|
|
{
|
|
writer.Write((byte)ValueKind.Null);
|
|
return;
|
|
}
|
|
|
|
Type type = value.GetType();
|
|
if (type == typeof(bool))
|
|
{
|
|
writer.Write((byte)ValueKind.Boolean);
|
|
writer.Write((bool)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(byte))
|
|
{
|
|
writer.Write((byte)ValueKind.Byte);
|
|
writer.Write((byte)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(sbyte))
|
|
{
|
|
writer.Write((byte)ValueKind.SByte);
|
|
writer.Write((sbyte)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(short))
|
|
{
|
|
writer.Write((byte)ValueKind.Int16);
|
|
writer.Write((short)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(ushort))
|
|
{
|
|
writer.Write((byte)ValueKind.UInt16);
|
|
writer.Write((ushort)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(int))
|
|
{
|
|
writer.Write((byte)ValueKind.Int32);
|
|
writer.Write((int)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(uint))
|
|
{
|
|
writer.Write((byte)ValueKind.UInt32);
|
|
writer.Write((uint)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(long))
|
|
{
|
|
writer.Write((byte)ValueKind.Int64);
|
|
writer.Write((long)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(ulong))
|
|
{
|
|
writer.Write((byte)ValueKind.UInt64);
|
|
writer.Write((ulong)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(float))
|
|
{
|
|
writer.Write((byte)ValueKind.Single);
|
|
writer.Write((float)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(double))
|
|
{
|
|
writer.Write((byte)ValueKind.Double);
|
|
writer.Write((double)value);
|
|
return;
|
|
}
|
|
|
|
if (type == typeof(string))
|
|
{
|
|
writer.Write((byte)ValueKind.String);
|
|
writer.Write((string)value);
|
|
return;
|
|
}
|
|
|
|
if (type.IsEnum)
|
|
{
|
|
writer.Write((byte)ValueKind.Enum);
|
|
WriteType(writer, type);
|
|
writer.Write(Convert.ToInt64(
|
|
value,
|
|
CultureInfo.InvariantCulture));
|
|
return;
|
|
}
|
|
|
|
if (type.IsArray)
|
|
{
|
|
Array array = value as Array;
|
|
writer.Write((byte)ValueKind.Array);
|
|
WriteType(writer, type.GetElementType());
|
|
writer.Write(array != null ? array.Length : 0);
|
|
if (array == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Type elementType = type.GetElementType();
|
|
for (int i = 0; i < array.Length; ++i)
|
|
{
|
|
object elementValue = array.GetValue(i);
|
|
EnsureSerializableArrayElementValue(
|
|
elementType,
|
|
elementValue);
|
|
WriteValue(
|
|
writer,
|
|
elementValue,
|
|
elementType);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (type.IsValueType)
|
|
{
|
|
writer.Write((byte)ValueKind.Struct);
|
|
WriteType(writer, type);
|
|
WriteFieldSet(writer, value, type);
|
|
return;
|
|
}
|
|
|
|
writer.Write((byte)ValueKind.ObjectReference);
|
|
writer.Write(m_nodeIds[value]);
|
|
}
|
|
}
|
|
|
|
private sealed class NodeRecord
|
|
{
|
|
public string assemblyName;
|
|
public string typeName;
|
|
public List<FieldRecord> fields;
|
|
public object instance;
|
|
}
|
|
|
|
private sealed class FieldRecord
|
|
{
|
|
public string declaringAssemblyName;
|
|
public string declaringTypeName;
|
|
public string name;
|
|
public ValueRecord value;
|
|
}
|
|
|
|
private sealed class ValueRecord
|
|
{
|
|
public ValueKind kind;
|
|
public object scalarValue;
|
|
public string assemblyName;
|
|
public string typeName;
|
|
public int objectId;
|
|
public List<ValueRecord> elements;
|
|
public List<FieldRecord> fields;
|
|
}
|
|
|
|
private sealed class ReferenceEqualityComparer
|
|
: IEqualityComparer<object>
|
|
{
|
|
public static readonly ReferenceEqualityComparer Instance =
|
|
new ReferenceEqualityComparer();
|
|
|
|
public new bool Equals(
|
|
object x,
|
|
object y)
|
|
{
|
|
return ReferenceEquals(x, y);
|
|
}
|
|
|
|
public int GetHashCode(
|
|
object obj)
|
|
{
|
|
return RuntimeHelpers.GetHashCode(obj);
|
|
}
|
|
}
|
|
|
|
private static NodeRecord ReadNode(
|
|
BinaryReader reader)
|
|
{
|
|
NodeRecord node = new NodeRecord();
|
|
ReadType(
|
|
reader,
|
|
out node.assemblyName,
|
|
out node.typeName);
|
|
node.fields = ReadFieldSet(reader);
|
|
return node;
|
|
}
|
|
|
|
private static List<FieldRecord> ReadFieldSet(
|
|
BinaryReader reader)
|
|
{
|
|
int fieldCount = reader.ReadInt32();
|
|
List<FieldRecord> fields =
|
|
new List<FieldRecord>(fieldCount);
|
|
for (int i = 0; i < fieldCount; ++i)
|
|
{
|
|
FieldRecord field = new FieldRecord();
|
|
ReadType(
|
|
reader,
|
|
out field.declaringAssemblyName,
|
|
out field.declaringTypeName);
|
|
field.name = reader.ReadString();
|
|
field.value = ReadValue(reader);
|
|
fields.Add(field);
|
|
}
|
|
|
|
return fields;
|
|
}
|
|
|
|
private static ValueRecord ReadValue(
|
|
BinaryReader reader)
|
|
{
|
|
ValueRecord value = new ValueRecord();
|
|
value.kind = (ValueKind)reader.ReadByte();
|
|
switch (value.kind)
|
|
{
|
|
case ValueKind.Null:
|
|
break;
|
|
case ValueKind.Boolean:
|
|
value.scalarValue = reader.ReadBoolean();
|
|
break;
|
|
case ValueKind.Byte:
|
|
value.scalarValue = reader.ReadByte();
|
|
break;
|
|
case ValueKind.SByte:
|
|
value.scalarValue = reader.ReadSByte();
|
|
break;
|
|
case ValueKind.Int16:
|
|
value.scalarValue = reader.ReadInt16();
|
|
break;
|
|
case ValueKind.UInt16:
|
|
value.scalarValue = reader.ReadUInt16();
|
|
break;
|
|
case ValueKind.Int32:
|
|
value.scalarValue = reader.ReadInt32();
|
|
break;
|
|
case ValueKind.UInt32:
|
|
value.scalarValue = reader.ReadUInt32();
|
|
break;
|
|
case ValueKind.Int64:
|
|
value.scalarValue = reader.ReadInt64();
|
|
break;
|
|
case ValueKind.UInt64:
|
|
value.scalarValue = reader.ReadUInt64();
|
|
break;
|
|
case ValueKind.Single:
|
|
value.scalarValue = reader.ReadSingle();
|
|
break;
|
|
case ValueKind.Double:
|
|
value.scalarValue = reader.ReadDouble();
|
|
break;
|
|
case ValueKind.String:
|
|
value.scalarValue = reader.ReadString();
|
|
break;
|
|
case ValueKind.Enum:
|
|
ReadType(
|
|
reader,
|
|
out value.assemblyName,
|
|
out value.typeName);
|
|
value.scalarValue = reader.ReadInt64();
|
|
break;
|
|
case ValueKind.ObjectReference:
|
|
value.objectId = reader.ReadInt32();
|
|
break;
|
|
case ValueKind.Array:
|
|
ReadType(
|
|
reader,
|
|
out value.assemblyName,
|
|
out value.typeName);
|
|
int elementCount = reader.ReadInt32();
|
|
value.elements =
|
|
new List<ValueRecord>(elementCount);
|
|
for (int i = 0; i < elementCount; ++i)
|
|
{
|
|
value.elements.Add(ReadValue(reader));
|
|
}
|
|
break;
|
|
case ValueKind.Struct:
|
|
ReadType(
|
|
reader,
|
|
out value.assemblyName,
|
|
out value.typeName);
|
|
value.fields = ReadFieldSet(reader);
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private static void ApplyFields(
|
|
object target,
|
|
List<FieldRecord> fields,
|
|
List<NodeRecord> nodes)
|
|
{
|
|
if (target == null ||
|
|
fields == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < fields.Count; ++i)
|
|
{
|
|
FieldRecord fieldRecord = fields[i];
|
|
FieldInfo field = ResolveField(fieldRecord);
|
|
if (field == null ||
|
|
!ShouldSerializeField(field))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
object value = ConvertValue(
|
|
fieldRecord.value,
|
|
field.FieldType,
|
|
nodes);
|
|
if (ReferenceEquals(value, MissingValue))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
field.SetValue(target, value);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
private static object ConvertValue(
|
|
ValueRecord value,
|
|
Type targetType,
|
|
List<NodeRecord> nodes)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return MissingValue;
|
|
}
|
|
|
|
switch (value.kind)
|
|
{
|
|
case ValueKind.Null:
|
|
return targetType != null &&
|
|
targetType.IsValueType
|
|
? Activator.CreateInstance(targetType)
|
|
: null;
|
|
case ValueKind.Boolean:
|
|
case ValueKind.Byte:
|
|
case ValueKind.SByte:
|
|
case ValueKind.Int16:
|
|
case ValueKind.UInt16:
|
|
case ValueKind.Int32:
|
|
case ValueKind.UInt32:
|
|
case ValueKind.Int64:
|
|
case ValueKind.UInt64:
|
|
case ValueKind.Single:
|
|
case ValueKind.Double:
|
|
case ValueKind.String:
|
|
return ConvertScalar(
|
|
value.scalarValue,
|
|
targetType);
|
|
case ValueKind.Enum:
|
|
Type enumType =
|
|
ResolveType(
|
|
value.assemblyName,
|
|
value.typeName) ??
|
|
targetType;
|
|
return enumType != null &&
|
|
enumType.IsEnum
|
|
? Enum.ToObject(
|
|
enumType,
|
|
value.scalarValue)
|
|
: MissingValue;
|
|
case ValueKind.ObjectReference:
|
|
return value.objectId >= 0 &&
|
|
value.objectId < nodes.Count
|
|
? nodes[value.objectId].instance
|
|
: MissingValue;
|
|
case ValueKind.Array:
|
|
return ConvertArray(value, targetType, nodes);
|
|
case ValueKind.Struct:
|
|
return ConvertStruct(value, targetType, nodes);
|
|
default:
|
|
return MissingValue;
|
|
}
|
|
}
|
|
|
|
private static object ConvertScalar(
|
|
object value,
|
|
Type targetType)
|
|
{
|
|
if (targetType == null ||
|
|
value == null)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
if (targetType.IsEnum)
|
|
{
|
|
return Enum.ToObject(targetType, value);
|
|
}
|
|
|
|
Type valueType = value.GetType();
|
|
if (targetType.IsAssignableFrom(valueType))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
try
|
|
{
|
|
return Convert.ChangeType(
|
|
value,
|
|
targetType,
|
|
CultureInfo.InvariantCulture);
|
|
}
|
|
catch
|
|
{
|
|
return MissingValue;
|
|
}
|
|
}
|
|
|
|
private static object ConvertArray(
|
|
ValueRecord value,
|
|
Type targetType,
|
|
List<NodeRecord> nodes)
|
|
{
|
|
Type elementType =
|
|
ResolveType(
|
|
value.assemblyName,
|
|
value.typeName);
|
|
if (elementType == null &&
|
|
targetType != null &&
|
|
targetType.IsArray)
|
|
{
|
|
elementType = targetType.GetElementType();
|
|
}
|
|
|
|
if (elementType == null ||
|
|
value.elements == null)
|
|
{
|
|
return MissingValue;
|
|
}
|
|
|
|
Array array = Array.CreateInstance(
|
|
elementType,
|
|
value.elements.Count);
|
|
for (int i = 0; i < value.elements.Count; ++i)
|
|
{
|
|
object elementValue = ConvertValue(
|
|
value.elements[i],
|
|
elementType,
|
|
nodes);
|
|
if (!ReferenceEquals(elementValue, MissingValue))
|
|
{
|
|
array.SetValue(elementValue, i);
|
|
}
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
private static object ConvertStruct(
|
|
ValueRecord value,
|
|
Type targetType,
|
|
List<NodeRecord> nodes)
|
|
{
|
|
Type type =
|
|
ResolveType(
|
|
value.assemblyName,
|
|
value.typeName) ??
|
|
targetType;
|
|
if (type == null ||
|
|
!type.IsValueType)
|
|
{
|
|
return MissingValue;
|
|
}
|
|
|
|
object instance = Activator.CreateInstance(type);
|
|
ApplyFields(instance, value.fields, nodes);
|
|
return instance;
|
|
}
|
|
|
|
private static object CreateObjectInstance(
|
|
Type type)
|
|
{
|
|
if (type == null ||
|
|
type.IsAbstract)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
return Activator.CreateInstance(
|
|
type,
|
|
BindingFlags.Instance |
|
|
BindingFlags.Public |
|
|
BindingFlags.NonPublic,
|
|
binder: null,
|
|
args: new object[0],
|
|
culture: CultureInfo.InvariantCulture);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static FieldInfo ResolveField(
|
|
FieldRecord fieldRecord)
|
|
{
|
|
Type declaringType =
|
|
ResolveType(
|
|
fieldRecord.declaringAssemblyName,
|
|
fieldRecord.declaringTypeName);
|
|
return declaringType != null
|
|
? declaringType.GetField(
|
|
fieldRecord.name,
|
|
BindingFlags.Instance |
|
|
BindingFlags.Public |
|
|
BindingFlags.NonPublic |
|
|
BindingFlags.DeclaredOnly)
|
|
: null;
|
|
}
|
|
|
|
private static void WriteType(
|
|
BinaryWriter writer,
|
|
Type type)
|
|
{
|
|
writer.Write(
|
|
type != null
|
|
? type.Assembly.GetName().Name
|
|
: string.Empty);
|
|
writer.Write(
|
|
type != null
|
|
? type.FullName ?? string.Empty
|
|
: string.Empty);
|
|
}
|
|
|
|
private static void ReadType(
|
|
BinaryReader reader,
|
|
out string assemblyName,
|
|
out string typeName)
|
|
{
|
|
assemblyName = reader.ReadString();
|
|
typeName = reader.ReadString();
|
|
}
|
|
|
|
private static Type ResolveType(
|
|
string assemblyName,
|
|
string typeName)
|
|
{
|
|
if (string.IsNullOrEmpty(typeName))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Type type = null;
|
|
if (!string.IsNullOrEmpty(assemblyName))
|
|
{
|
|
type = Type.GetType(
|
|
typeName + ", " + assemblyName,
|
|
false);
|
|
if (type != null)
|
|
{
|
|
return type;
|
|
}
|
|
}
|
|
|
|
Assembly[] assemblies =
|
|
AppDomain.CurrentDomain.GetAssemblies();
|
|
for (int i = 0; i < assemblies.Length; ++i)
|
|
{
|
|
Assembly assembly = assemblies[i];
|
|
if (!string.IsNullOrEmpty(assemblyName) &&
|
|
assembly.GetName().Name != assemblyName)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
type = assembly.GetType(typeName, false);
|
|
if (type != null)
|
|
{
|
|
return type;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static FieldInfo[] GetSerializableFields(
|
|
Type type)
|
|
{
|
|
List<FieldInfo> fields = new List<FieldInfo>();
|
|
for (Type currentType = type;
|
|
currentType != null &&
|
|
currentType != typeof(object);
|
|
currentType = currentType.BaseType)
|
|
{
|
|
FieldInfo[] declaredFields =
|
|
currentType.GetFields(
|
|
BindingFlags.Instance |
|
|
BindingFlags.Public |
|
|
BindingFlags.NonPublic |
|
|
BindingFlags.DeclaredOnly);
|
|
for (int i = 0; i < declaredFields.Length; ++i)
|
|
{
|
|
FieldInfo field = declaredFields[i];
|
|
if (ShouldSerializeField(field))
|
|
{
|
|
fields.Add(field);
|
|
}
|
|
}
|
|
}
|
|
|
|
fields.Sort(CompareFields);
|
|
return fields.ToArray();
|
|
}
|
|
|
|
private static int CompareFields(
|
|
FieldInfo lhs,
|
|
FieldInfo rhs)
|
|
{
|
|
string lhsType =
|
|
lhs.DeclaringType != null
|
|
? lhs.DeclaringType.FullName
|
|
: string.Empty;
|
|
string rhsType =
|
|
rhs.DeclaringType != null
|
|
? rhs.DeclaringType.FullName
|
|
: string.Empty;
|
|
int result = string.CompareOrdinal(lhsType, rhsType);
|
|
if (result != 0)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
result = lhs.MetadataToken.CompareTo(rhs.MetadataToken);
|
|
return result != 0
|
|
? result
|
|
: string.CompareOrdinal(lhs.Name, rhs.Name);
|
|
}
|
|
|
|
private static bool ShouldSerializeField(
|
|
FieldInfo field)
|
|
{
|
|
if (field == null ||
|
|
field.IsStatic ||
|
|
field.IsLiteral ||
|
|
field.IsInitOnly ||
|
|
field.IsNotSerialized)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return field.IsPublic ||
|
|
Attribute.IsDefined(
|
|
field,
|
|
typeof(SerializeField),
|
|
true);
|
|
}
|
|
|
|
private static bool CanSerializeValue(
|
|
Type declaredType,
|
|
object value)
|
|
{
|
|
Type type = value != null
|
|
? value.GetType()
|
|
: declaredType;
|
|
if (type == null ||
|
|
type.IsPointer ||
|
|
type.IsGenericTypeDefinition)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsScalarType(type) ||
|
|
type.IsEnum)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (type.IsArray)
|
|
{
|
|
Type elementType = type.GetElementType();
|
|
return elementType != null &&
|
|
CanSerializeValue(elementType, null);
|
|
}
|
|
|
|
if (type.IsValueType)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return CanSerializeReferenceType(type);
|
|
}
|
|
|
|
private static void EnsureSerializableFieldValue(
|
|
FieldInfo field,
|
|
object value)
|
|
{
|
|
if (field != null &&
|
|
!CanSerializeValue(
|
|
field.FieldType,
|
|
value))
|
|
{
|
|
string ownerType =
|
|
field.DeclaringType != null
|
|
? field.DeclaringType.FullName
|
|
: string.Empty;
|
|
throw new NotSupportedException(
|
|
"Unsupported serialized field type: " +
|
|
ownerType + "." +
|
|
field.Name + " (" +
|
|
field.FieldType.FullName + ")");
|
|
}
|
|
}
|
|
|
|
private static void EnsureSerializableArrayElementValue(
|
|
Type elementType,
|
|
object value)
|
|
{
|
|
if (!CanSerializeValue(elementType, value))
|
|
{
|
|
throw new NotSupportedException(
|
|
"Unsupported serialized array element type: " +
|
|
(value != null
|
|
? value.GetType().FullName
|
|
: elementType != null
|
|
? elementType.FullName
|
|
: string.Empty));
|
|
}
|
|
}
|
|
|
|
private static bool CanSerializeReferenceType(
|
|
Type type)
|
|
{
|
|
return type != null &&
|
|
type.IsClass &&
|
|
type != typeof(string) &&
|
|
type != typeof(Type) &&
|
|
!typeof(Delegate).IsAssignableFrom(type) &&
|
|
(type.Namespace == null ||
|
|
!type.Namespace.StartsWith(
|
|
"System",
|
|
StringComparison.Ordinal));
|
|
}
|
|
|
|
private static bool IsScalarType(
|
|
Type type)
|
|
{
|
|
return type == typeof(bool) ||
|
|
type == typeof(byte) ||
|
|
type == typeof(sbyte) ||
|
|
type == typeof(short) ||
|
|
type == typeof(ushort) ||
|
|
type == typeof(int) ||
|
|
type == typeof(uint) ||
|
|
type == typeof(long) ||
|
|
type == typeof(ulong) ||
|
|
type == typeof(float) ||
|
|
type == typeof(double) ||
|
|
type == typeof(string);
|
|
}
|
|
}
|
|
}
|