650 lines
24 KiB
C#
650 lines
24 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Runtime.CompilerServices;
|
||
using AyCode.Core.Helpers;
|
||
using AyCode.Core.Serializers;
|
||
using static AyCode.Core.Helpers.JsonUtilities;
|
||
|
||
namespace AyCode.Core.Serializers.Binaries;
|
||
|
||
public static partial class AcBinaryDeserializer
|
||
{
|
||
#region Helper Methods
|
||
|
||
// ReadAndRegisterHashcodeIfNeeded removed - new format uses position-based footer for all reference tracking.
|
||
// No hashcode prefix in wire format anymore.
|
||
|
||
#endregion
|
||
|
||
#region Populate Object Methods
|
||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
private static BinaryDeserializeTypeMetadata GetTypeMetadata(Type type)
|
||
=> null;//MetadataCache.GetOrAdd(type, static t => new BinaryDeserializeTypeMetadata(t));
|
||
|
||
/// <summary>
|
||
/// Populate object with automatic mode detection from context.
|
||
/// Uses IsMergeMode to determine merge behavior for IId collections.
|
||
/// Wire format:
|
||
/// - IId types: [Object][props 0-t<>l...] - no refId prefix, Id is in props
|
||
/// - Non-IId + All: [Object][hashcode][props 0-t<>l...] - hashcode prefix
|
||
/// - Ref=Off: [Object][props 0-t<>l...] - no prefix
|
||
/// </summary>
|
||
private static void PopulateObject(BinaryDeserializationContext context, object target, Type targetType, int depth)
|
||
{
|
||
var wrapper = context.GetWrapper(targetType);
|
||
|
||
// UseMetadata: CacheMap is built in ReadObjectWithMetadata or ReadInlineMetadataForPopulate
|
||
PopulateObjectCore(context, target, wrapper, depth, skipDefaultWrite: false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Core populate logic shared by all populate paths.
|
||
/// Wire format: All properties are written WITH type markers (including Id for IId types).
|
||
/// No hashcode prefix - position-based footer handles reference tracking.
|
||
/// </summary>
|
||
private static void PopulateObjectCore(
|
||
BinaryDeserializationContext context,
|
||
object target,
|
||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||
int depth,
|
||
bool skipDefaultWrite)
|
||
{
|
||
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unified property populate for both UseMetadata and non-UseMetadata modes.
|
||
/// UseMetadata=true: cacheMap[i] gives the setter (null → skip).
|
||
/// UseMetadata=false: properties[i] gives the setter directly.
|
||
/// </summary>
|
||
private static void PopulateObjectPropertiesIndexed(
|
||
BinaryDeserializationContext context,
|
||
object target,
|
||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||
int depth,
|
||
bool skipDefaultWrite)
|
||
{
|
||
var metadata = wrapper.Metadata;
|
||
var properties = metadata.PropertiesArray;
|
||
var cacheMap = wrapper.CacheMap;
|
||
var nextDepth = depth + 1;
|
||
var isMergeMode = context.IsMergeMode;
|
||
|
||
// UseMetadata: cacheMap.Length a source property-k száma
|
||
// Non-UseMetadata: properties.Length a target property-k száma (source == target)
|
||
var propCount = cacheMap?.Length ?? properties.Length;
|
||
|
||
if (!context.HasMetadata)
|
||
{
|
||
// Markerless loop: properties with ExpectedTypeCode read raw values directly.
|
||
// Properties without ExpectedTypeCode (bool, enum, string, object) use standard marker path.
|
||
for (int i = 0; i < propCount; i++)
|
||
{
|
||
var propInfo = properties[i];
|
||
|
||
if (propInfo.ExpectedTypeCode.HasValue)
|
||
{
|
||
ReadAndSetMarkerlessValue(context, target, propInfo);
|
||
continue;
|
||
}
|
||
|
||
// Non-markerless properties: standard marker-based read
|
||
PopulatePropertyWithMarker(context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// UseMetadata=true loop — UNCHANGED, zero extra overhead
|
||
for (int i = 0; i < propCount; i++)
|
||
{
|
||
var propInfo = cacheMap != null ? cacheMap[i] : properties[i];
|
||
|
||
PopulatePropertyWithMarker(context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Standard marker-based property read. Extracted to avoid duplicating logic in both loops.
|
||
/// </summary>
|
||
private static void PopulatePropertyWithMarker(
|
||
BinaryDeserializationContext context,
|
||
object target,
|
||
BinaryPropertySetterBase? propInfo,
|
||
BinaryDeserializeTypeMetadata metadata,
|
||
int nextDepth,
|
||
bool isMergeMode,
|
||
bool skipDefaultWrite,
|
||
int propertyIndex,
|
||
int depth)
|
||
{
|
||
var peekCode = context.PeekByte();
|
||
|
||
// Nincs megfelelő target property → skip
|
||
if (propInfo == null)
|
||
{
|
||
SkipValue(context, metadata);
|
||
return;
|
||
}
|
||
|
||
// Skip marker - property has default/null value
|
||
if (peekCode == BinaryTypeCode.PropertySkip)
|
||
{
|
||
context.ReadByte(); // consume Skip marker
|
||
|
||
// Populate mode: overwrite with default (existing object may have non-default values)
|
||
// Deserialize mode: skip write (new object already has defaults from CreateInstance)
|
||
if (!skipDefaultWrite)
|
||
{
|
||
SetPropertyToDefault(target, propInfo);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Null values - always set
|
||
if (peekCode == BinaryTypeCode.Null)
|
||
{
|
||
context.ReadByte(); // consume Null marker
|
||
propInfo.SetValue(target, null);
|
||
return;
|
||
}
|
||
|
||
// Handle collections
|
||
if (peekCode == BinaryTypeCode.Array && propInfo.IsCollection)
|
||
{
|
||
var existingCollection = propInfo.GetValue(target);
|
||
if (existingCollection is IList existingList)
|
||
{
|
||
context.ReadByte(); // consume Array marker
|
||
|
||
// Merge mode with IId collection: use merge logic
|
||
if (isMergeMode && propInfo.IsIIdCollection)
|
||
{
|
||
MergeIIdCollection(context, existingList, propInfo, nextDepth);
|
||
}
|
||
else
|
||
{
|
||
// Normal populate: replace collection contents
|
||
PopulateListOptimized(context, existingList, propInfo, nextDepth);
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Handle nested complex objects - reuse existing if available
|
||
if ((peekCode == BinaryTypeCode.Object || peekCode == BinaryTypeCode.ObjectWithMetadata) && propInfo.IsComplexType)
|
||
{
|
||
var existingObj = propInfo.GetValue(target);
|
||
if (existingObj != null)
|
||
{
|
||
// ReadValue kezeli mindkét markert
|
||
var nestedValue = ReadValue(context, propInfo.PropertyType, nextDepth);
|
||
if (nestedValue != null)
|
||
{
|
||
var nestedMeta = context.GetWrapper(propInfo.PropertyType).Metadata;
|
||
CopyProperties(nestedValue, existingObj, nestedMeta);
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Default: read value and set (for primitives, strings, new objects)
|
||
var positionBeforeRead = context.Position;
|
||
try
|
||
{
|
||
// Use typed setters for primitives and strings to avoid ReadValue dispatch
|
||
if (propInfo.AccessorType != PropertyAccessorType.Object &&
|
||
TryReadAndSetTypedValue(context, target, propInfo, peekCode))
|
||
return;
|
||
|
||
var value = ReadValue(context, propInfo.PropertyType, nextDepth);
|
||
propInfo.SetValue(target, value);
|
||
}
|
||
catch (InvalidCastException ex)
|
||
{
|
||
var targetType = target.GetType();
|
||
throw new AcBinaryDeserializationException(
|
||
$"Type mismatch for property '{propInfo.Name}' (index {propertyIndex}) on '{targetType.Name}'. " +
|
||
$"Expected type: '{propInfo.PropertyType.FullName}'. " +
|
||
$"PeekCode before read: {peekCode} (0x{peekCode:X2}). " +
|
||
$"Position before read: {positionBeforeRead}, current: {context.Position}. " +
|
||
$"Depth: {depth}. " +
|
||
$"Target type: {targetType.FullName}, Assembly: {targetType.Assembly.GetName().Name} v{targetType.Assembly.GetName().Version}. " +
|
||
$"All target properties: [{string.Join(", ", metadata.PropertiesArray.Select(p => $"{p.Name}:{p.PropertyType.Name}"))}]. " +
|
||
$"Error: {ex.Message}",
|
||
positionBeforeRead,
|
||
propInfo.PropertyType,
|
||
ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads a raw value without type marker from stream (markerless mode, UseMetadata=false).
|
||
/// The property's type is known from metadata — no type code in the stream.
|
||
/// Only called for non-nullable value types with ExpectedTypeCode set.
|
||
/// </summary>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
private static void ReadAndSetMarkerlessValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo)
|
||
{
|
||
switch (propInfo.AccessorType)
|
||
{
|
||
case PropertyAccessorType.Int32:
|
||
propInfo.SetInt32(target, context.ReadVarInt());
|
||
return;
|
||
case PropertyAccessorType.Int64:
|
||
propInfo.SetInt64(target, context.ReadVarLong());
|
||
return;
|
||
case PropertyAccessorType.Double:
|
||
propInfo.SetDouble(target, context.ReadDoubleUnsafe());
|
||
return;
|
||
case PropertyAccessorType.Single:
|
||
propInfo.SetSingle(target, context.ReadSingleUnsafe());
|
||
return;
|
||
case PropertyAccessorType.Decimal:
|
||
propInfo.SetDecimal(target, context.ReadDecimalUnsafe());
|
||
return;
|
||
case PropertyAccessorType.DateTime:
|
||
propInfo.SetDateTime(target, context.ReadDateTimeUnsafe());
|
||
return;
|
||
case PropertyAccessorType.Guid:
|
||
propInfo.SetGuid(target, context.ReadGuidUnsafe());
|
||
return;
|
||
case PropertyAccessorType.Byte:
|
||
propInfo.SetByte(target, context.ReadByte());
|
||
return;
|
||
case PropertyAccessorType.Int16:
|
||
propInfo.SetInt16(target, context.ReadInt16Unsafe());
|
||
return;
|
||
case PropertyAccessorType.UInt16:
|
||
propInfo.SetUInt16(target, context.ReadUInt16Unsafe());
|
||
return;
|
||
case PropertyAccessorType.UInt32:
|
||
propInfo.SetUInt32(target, context.ReadVarUInt());
|
||
return;
|
||
case PropertyAccessorType.UInt64:
|
||
propInfo.SetUInt64(target, context.ReadVarULong());
|
||
return;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
|
||
/// </summary>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
private static void PopulateObject(BinaryDeserializationContext context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
|
||
{
|
||
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Populate List Methods
|
||
|
||
private static void PopulateList(BinaryDeserializationContext context, IList targetList, Type listType, int depth)
|
||
{
|
||
var elementType = GetCollectionElementType(listType) ?? typeof(object);
|
||
|
||
var acObservable = targetList as IAcObservableCollection;
|
||
acObservable?.BeginUpdate();
|
||
|
||
try
|
||
{
|
||
targetList.Clear();
|
||
|
||
var count = (int)context.ReadVarUInt();
|
||
var nextDepth = depth + 1;
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
// ReadValue handles ChainMode internally (ReadObject returns cached instance)
|
||
var value = ReadValue(context, elementType, nextDepth);
|
||
targetList.Add(value);
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
acObservable?.EndUpdate();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Optimized list populate that reuses existing items when possible.
|
||
/// </summary>
|
||
private static void PopulateListOptimized(BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
|
||
{
|
||
var elementType = propInfo.ElementType ?? typeof(object);
|
||
var count = (int)context.ReadVarUInt();
|
||
var nextDepth = depth + 1;
|
||
|
||
var acObservable = existingList as IAcObservableCollection;
|
||
acObservable?.BeginUpdate();
|
||
|
||
try
|
||
{
|
||
var existingCount = existingList.Count;
|
||
|
||
// Early exit if empty source - just clear destination
|
||
if (count == 0)
|
||
{
|
||
existingList.Clear();
|
||
return;
|
||
}
|
||
|
||
var wrapper = context.GetWrapper(elementType);
|
||
var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null;
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
var peekCode = context.PeekByte();
|
||
|
||
// If we have an existing item at this index and the incoming is an object, reuse it
|
||
if (i < existingCount && peekCode == BinaryTypeCode.Object && elementMetadata != null)
|
||
{
|
||
var existingItem = existingList[i];
|
||
if (existingItem != null)
|
||
{
|
||
context.ReadByte(); // consume Object marker
|
||
PopulateObjectCore(context, existingItem, wrapper, nextDepth, skipDefaultWrite: false);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// Read new value
|
||
var value = ReadValue(context, elementType, nextDepth);
|
||
|
||
if (i < existingCount)
|
||
{
|
||
existingList[i] = value;
|
||
}
|
||
else
|
||
{
|
||
existingList.Add(value);
|
||
}
|
||
}
|
||
|
||
// Remove extra items if new list is shorter
|
||
while (existingList.Count > count)
|
||
{
|
||
existingList.RemoveAt(existingList.Count - 1);
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
acObservable?.EndUpdate();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Merge Methods
|
||
|
||
/// <summary>
|
||
/// IId collection merge using cached property info.
|
||
/// Matches items by Id, updates existing, adds new, optionally removes orphans.
|
||
/// </summary>
|
||
private static void MergeIIdCollection(BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
|
||
{
|
||
var elementType = propInfo.ElementType!;
|
||
var idGetter = propInfo.ElementIdGetter!;
|
||
var idType = propInfo.ElementIdType!;
|
||
|
||
var count = existingList.Count;
|
||
var acObservable = existingList as IAcObservableCollection;
|
||
acObservable?.BeginUpdate();
|
||
|
||
try
|
||
{
|
||
// Build lookup dictionary with capacity hint
|
||
Dictionary<object, object>? existingById = null;
|
||
if (count > 0)
|
||
{
|
||
existingById = new Dictionary<object, object>(count);
|
||
for (var idx = 0; idx < count; idx++)
|
||
{
|
||
var item = existingList[idx];
|
||
if (item != null)
|
||
{
|
||
var id = idGetter(item);
|
||
if (id != null && !IsDefaultValue(id, idType))
|
||
existingById[id] = item;
|
||
}
|
||
}
|
||
}
|
||
|
||
var arrayCount = (int)context.ReadVarUInt();
|
||
var nextDepth = depth + 1;
|
||
var wrapper = context.GetWrapper(elementType);
|
||
var elementMetadata = wrapper.Metadata;
|
||
|
||
// Track which IDs we see in source (for orphan removal)
|
||
HashSet<object>? sourceIds = context.RemoveOrphanedItems && existingById != null
|
||
? new HashSet<object>(arrayCount)
|
||
: null;
|
||
|
||
for (int i = 0; i < arrayCount; i++)
|
||
{
|
||
var itemCode = context.PeekByte();
|
||
if (itemCode != BinaryTypeCode.Object)
|
||
{
|
||
var value = ReadValue(context, elementType, nextDepth);
|
||
if (value != null)
|
||
existingList.Add(value);
|
||
continue;
|
||
}
|
||
|
||
context.ReadByte(); // consume Object marker
|
||
var newItem = CreateInstance(elementType, elementMetadata);
|
||
if (newItem == null) continue;
|
||
|
||
// PopulateObjectCore handles hashcode reading for Non-IId types
|
||
PopulateObjectCore(context, newItem, wrapper, nextDepth, skipDefaultWrite: true);
|
||
|
||
var itemId = idGetter(newItem);
|
||
if (itemId != null && !IsDefaultValue(itemId, idType))
|
||
{
|
||
// Track this ID as seen in source
|
||
sourceIds?.Add(itemId);
|
||
|
||
if (existingById != null && existingById.TryGetValue(itemId, out var existingItem))
|
||
{
|
||
// Copy properties to existing item (preserves reference)
|
||
CopyProperties(newItem, existingItem, elementMetadata);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
existingList.Add(newItem);
|
||
}
|
||
|
||
// Remove orphaned items (items in destination but not in source)
|
||
if (context.RemoveOrphanedItems && existingById != null && sourceIds != null)
|
||
{
|
||
var itemsToRemove = new List<object>();
|
||
foreach (var kvp in existingById)
|
||
{
|
||
if (!sourceIds.Contains(kvp.Key))
|
||
{
|
||
itemsToRemove.Add(kvp.Value);
|
||
}
|
||
}
|
||
|
||
foreach (var item in itemsToRemove)
|
||
{
|
||
existingList.Remove(item);
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
acObservable?.EndUpdate();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// IId collection merge using type metadata (for top-level list merge).
|
||
/// </summary>
|
||
private static void MergeIIdCollectionWithMetadata(
|
||
BinaryDeserializationContext context,
|
||
IList existingList,
|
||
Type elementType,
|
||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||
int depth)
|
||
{
|
||
var elementMetadata = wrapper.Metadata;
|
||
var idGetter = elementMetadata.IdGetter!;
|
||
var idType = elementMetadata.IdType!;
|
||
|
||
var count = existingList.Count;
|
||
var acObservable = existingList as IAcObservableCollection;
|
||
acObservable?.BeginUpdate();
|
||
|
||
try
|
||
{
|
||
// Build lookup dictionary with capacity hint
|
||
Dictionary<object, object>? existingById = null;
|
||
if (count > 0)
|
||
{
|
||
existingById = new Dictionary<object, object>(count);
|
||
for (var idx = 0; idx < count; idx++)
|
||
{
|
||
var item = existingList[idx];
|
||
if (item != null)
|
||
{
|
||
var id = idGetter(item);
|
||
if (id != null && !IsDefaultValue(id, idType))
|
||
existingById[id] = item;
|
||
}
|
||
}
|
||
}
|
||
|
||
var arrayCount = (int)context.ReadVarUInt();
|
||
var nextDepth = depth + 1;
|
||
|
||
// Track which IDs we see in source (for orphan removal)
|
||
HashSet<object>? sourceIds = context.RemoveOrphanedItems && existingById != null
|
||
? new HashSet<object>(arrayCount)
|
||
: null;
|
||
|
||
for (int i = 0; i < arrayCount; i++)
|
||
{
|
||
var itemCode = context.PeekByte();
|
||
if (itemCode != BinaryTypeCode.Object)
|
||
{
|
||
var value = ReadValue(context, elementType, nextDepth);
|
||
if (value != null)
|
||
existingList.Add(value);
|
||
continue;
|
||
}
|
||
|
||
context.ReadByte(); // consume Object marker
|
||
var newItem = CreateInstance(elementType, elementMetadata);
|
||
if (newItem == null) continue;
|
||
|
||
// PopulateObjectCore handles hashcode reading for Non-IId types
|
||
PopulateObjectCore(context, newItem, wrapper, nextDepth, skipDefaultWrite: true);
|
||
|
||
var itemId = idGetter(newItem);
|
||
if (itemId != null && !IsDefaultValue(itemId, idType))
|
||
{
|
||
// Track this ID as seen in source
|
||
sourceIds?.Add(itemId);
|
||
|
||
if (existingById != null && existingById.TryGetValue(itemId, out var existingItem))
|
||
{
|
||
// Copy properties to existing item (preserves reference)
|
||
CopyProperties(newItem, existingItem, elementMetadata);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
existingList.Add(newItem);
|
||
}
|
||
|
||
// Remove orphaned items (items in destination but not in source)
|
||
if (context.RemoveOrphanedItems && existingById != null && sourceIds != null)
|
||
{
|
||
var itemsToRemove = new List<object>();
|
||
foreach (var kvp in existingById)
|
||
{
|
||
if (!sourceIds.Contains(kvp.Key))
|
||
{
|
||
itemsToRemove.Add(kvp.Value);
|
||
}
|
||
}
|
||
|
||
foreach (var item in itemsToRemove)
|
||
{
|
||
existingList.Remove(item);
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
acObservable?.EndUpdate();
|
||
}
|
||
}
|
||
|
||
private static void CopyProperties(object source, object target, BinaryDeserializeTypeMetadata metadata)
|
||
{
|
||
var props = metadata.PropertiesArray;
|
||
for (var i = 0; i < props.Length; i++)
|
||
{
|
||
var prop = props[i];
|
||
var value = prop.GetValue(source);
|
||
if (value != null)
|
||
prop.SetValue(target, value);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Reads Id value without type marker. The serializer didn't write a marker for IId types.
|
||
/// </summary>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
private static void ReadIdValueWithoutMarker(BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, IdAccessorType idType)
|
||
{
|
||
switch (idType)
|
||
{
|
||
case IdAccessorType.Int32:
|
||
propInfo.SetInt32(target, context.ReadVarInt());
|
||
break;
|
||
case IdAccessorType.Int64:
|
||
propInfo.SetInt64(target, context.ReadVarLong());
|
||
break;
|
||
case IdAccessorType.Guid:
|
||
propInfo.SetGuid(target, context.ReadGuidUnsafe());
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets a property to its default value using typed setters to avoid boxing.
|
||
/// </summary>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
private static void SetPropertyToDefault(object target, BinaryPropertySetterBase propInfo)
|
||
=> propInfo.SetToDefault(target);
|
||
|
||
/// <summary>
|
||
/// Determines if a type is a complex type (not primitive, string, or simple value type).
|
||
/// </summary>
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
private static bool IsComplexType5(Type type)
|
||
{
|
||
if (type.IsPrimitive) return false;
|
||
if (ReferenceEquals(type, StringType)) return false;
|
||
if (type.IsEnum) return false;
|
||
if (ReferenceEquals(type, GuidType)) return false;
|
||
if (ReferenceEquals(type, DateTimeType)) return false;
|
||
if (ReferenceEquals(type, DecimalType)) return false;
|
||
if (ReferenceEquals(type, TimeSpanType)) return false;
|
||
if (ReferenceEquals(type, DateTimeOffsetType)) return false;
|
||
if (Nullable.GetUnderlyingType(type) != null) return false;
|
||
return true;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|