AyCode.Core/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Popula...

775 lines
31 KiB
C#
Raw Blame History

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
/// <summary>
/// Loop-invariant state for PopulatePropertyWithMarker. Built once per object, passed by ref.
/// Reduces call-site overhead from 9 parameters to 3 (ref state + propInfo + propertyIndex).
/// </summary>
private ref struct PopulateState<TInput> where TInput : struct, IBinaryInputBase
{
public BinaryDeserializationContext<TInput> Context;
public object Target;
public TypeMetadataWrapper<BinaryDeserializeTypeMetadata> ParentWrapper;
public BinaryDeserializeTypeMetadata Metadata;
public int NextDepth;
public int Depth;
public bool IsMergeMode;
public bool SkipDefaultWrite;
}
#region Populate Object Methods
/// <summary>
/// Resolves a property type wrapper using PropertyTypeWrappers cache.
/// Falls back to GetWrapper on cache miss and populates the cache.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TypeMetadataWrapper<BinaryDeserializeTypeMetadata> ResolvePropertyWrapper<TInput>(
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> parentWrapper,
int complexPropertyIndex,
Type propertyType,
BinaryDeserializationContext<TInput> context)
where TInput : struct, IBinaryInputBase
{
var cached = parentWrapper.GetPropertyTypeWrapper(complexPropertyIndex, propertyType);
if (cached != null)
return cached;
var resolved = context.GetWrapper(propertyType);
parentWrapper.SetPropertyTypeWrapper(complexPropertyIndex, resolved);
return resolved;
}
[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<TInput>(BinaryDeserializationContext<TInput> context, object target, Type targetType, int depth)
where TInput : struct, IBinaryInputBase
{
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>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PopulateObjectCore<TInput>(
BinaryDeserializationContext<TInput> context,
object target,
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
int depth,
bool skipDefaultWrite)
where TInput : struct, IBinaryInputBase
{
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<TInput>(BinaryDeserializationContext<TInput> context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
where TInput : struct, IBinaryInputBase
{
// No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter
// already prevents pathological depth in well-formed payloads. Malicious wire is out of scope.
var metadata = wrapper.Metadata;
var properties = metadata.PropertiesArray;
var cacheMap = wrapper.CacheMap;
// 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;
var state = new PopulateState<TInput>
{
Context = context,
Target = target,
ParentWrapper = wrapper,
Metadata = metadata,
NextDepth = depth + 1,
Depth = depth,
IsMergeMode = context.IsMergeMode,
SkipDefaultWrite = skipDefaultWrite
};
if (!context.HasMetadata || !metadata.EnableMetadataFeature)
{
// Markerless loop: properties with ExpectedTypeCode read raw values directly.
// Properties without ExpectedTypeCode (bool, enum, string, object) use standard marker path.
// Also used when EnableMetadataFeature=false on the type (per-type metadata opt-out).
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(ref state, propInfo, i);
}
}
else
{
// UseMetadata=true loop — UNCHANGED, zero extra overhead
for (int i = 0; i < propCount; i++)
{
var propInfo = cacheMap != null ? cacheMap[i] : properties[i];
PopulatePropertyWithMarker(ref state, propInfo, i);
}
}
}
/// <summary>
/// Standard marker-based property read. Extracted to avoid duplicating logic in both loops.
/// Loop-invariant state is passed via ref PopulateState to reduce call-site overhead.
/// </summary>
private static void PopulatePropertyWithMarker<TInput>(
ref PopulateState<TInput> state,
BinaryPropertySetterBase? propInfo,
int propertyIndex)
where TInput : struct, IBinaryInputBase
{
var context = state.Context;
var target = state.Target;
// Nincs megfelelő target property → skip (SkipValue reads its own marker byte)
if (propInfo == null)
{
SkipValue(context, state.Metadata);
return;
}
// FastWire markerless string-property fast-path — int32 sentinel header (-1 null / 0 empty / N>0
// content). Wire-symmetric with `WriteStringGenerated` / `WritePropertyOrSkip` String case via
// `WriteStringUtf16Markerless`. Skips the typeCode-read entirely; reader-writer pair eliminates
// 1 byte per content string in FastWire mode. Condition order: bool field-load (`FastWire`)
// first → cheap short-circuit in Compact mode (most-common case in many deployments) and
// branch-predictor-stable in FastWire mode (constant for the entire Deserialize). The
// `AccessorType == String` enum-compare (2 instructions: load + cmp) only runs when needed.
if (context.FastWire && propInfo.AccessorType == PropertyAccessorType.String)
{
propInfo.SetValue(target, context.ReadStringUtf16Markerless());
return;
}
// Read marker once — eliminates redundant PeekByte + ReadByte boundary checks.
// All branches below receive the already-consumed typeCode.
var typeCode = context.ReadByte();
// Skip marker - property has default/null value
if (typeCode == BinaryTypeCode.PropertySkip)
{
// Populate mode: overwrite with default (existing object may have non-default values)
// Deserialize mode: skip write (new object already has defaults from CreateInstance)
if (!state.SkipDefaultWrite)
{
SetPropertyToDefault(target, propInfo);
}
return;
}
// Null values - always set
if (typeCode == BinaryTypeCode.Null)
{
propInfo.SetValue(target, null);
return;
}
var nextDepth = state.NextDepth;
// Handle collections
if (typeCode == BinaryTypeCode.Array && propInfo.IsCollection)
{
var existingCollection = propInfo.GetValue(target);
if (existingCollection is IList existingList)
{
// Merge mode with IId collection: use merge logic
if (state.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 ((typeCode == BinaryTypeCode.Object || typeCode == BinaryTypeCode.ObjectWithMetadata
|| typeCode == BinaryTypeCode.ObjectRefFirst || typeCode == BinaryTypeCode.ObjectWithMetadataRefFirst) && propInfo.IsComplexType)
{
var existingObj = propInfo.GetValue(target);
if (existingObj != null)
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
var nestedValue = ReadValue(context, propInfo.PropertyType);
if (nestedValue != null)
{
var complexIdx = propInfo.ComplexPropertyIndex;
var parentWrapper = state.ParentWrapper;
var nestedWrapper = complexIdx >= 0
? ResolvePropertyWrapper(parentWrapper, complexIdx, propInfo.PropertyType, context)
: context.GetWrapper(propInfo.PropertyType);
CopyProperties(nestedValue, existingObj, nestedWrapper.Metadata);
}
return;
}
}
// Default: read value and set (for primitives, strings, new objects)
var positionBeforeRead = context.Position - 1; // marker already consumed
try
{
// Use typed setters for primitives and strings to avoid ReadValue dispatch.
// typeCode is already consumed — TryReadAndSetTypedValue skips its internal ReadByte.
if (propInfo.AccessorType != PropertyAccessorType.Object &&
TryReadAndSetTypedValue(context, target, propInfo, typeCode))
return;
// Complex property with Object marker: use pre-cached wrapper to skip GetWrapper lookup
var complexIdx = propInfo.ComplexPropertyIndex;
if (complexIdx >= 0 && typeCode == BinaryTypeCode.Object)
{
// Marker already consumed — go straight to ReadObjectCoreWithWrapper
var propWrapper = ResolvePropertyWrapper(state.ParentWrapper, complexIdx, propInfo.PropertyType, context);
var value = ReadObjectCoreWithWrapper(context, propWrapper, cacheIndex: -1);
propInfo.SetValue(target, value);
}
else
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
var value = ReadValue(context, propInfo.PropertyType);
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}'. " +
$"TypeCode read: {typeCode} (0x{typeCode:X2}). " +
$"Position before read: {positionBeforeRead}, current: {context.Position}. " +
$"Depth: {state.Depth}. " +
$"Target type: {targetType.FullName}, Assembly: {targetType.Assembly.GetName().Name} v{targetType.Assembly.GetName().Version}. " +
$"All target properties: [{string.Join(", ", state.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<TInput>(BinaryDeserializationContext<TInput> context, object target, BinaryPropertySetterBase propInfo)
where TInput : struct, IBinaryInputBase
{
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;
case PropertyAccessorType.Boolean:
propInfo.SetBoolean(target, context.ReadByte() != 0);
return;
case PropertyAccessorType.Enum:
propInfo.SetEnumAsInt32(target, context.ReadVarInt());
return;
}
}
/// <summary>
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PopulateObject<TInput>(BinaryDeserializationContext<TInput> context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, bool skipDefaultWrite)
where TInput : struct, IBinaryInputBase
{
// Bridge: PopulateObjectPropertiesIndexed still has int depth in signature (other callers use it).
// Pass 0 as placeholder; depth-cleanup of PopulateObjectPropertiesIndexed + its other callers is pending.
PopulateObjectPropertiesIndexed(context, target, wrapper, 0, skipDefaultWrite);
}
#endregion
#region Populate List Methods
private static void PopulateList<TInput>(BinaryDeserializationContext<TInput> context, IList targetList, Type listType, int depth)
where TInput : struct, IBinaryInputBase
{
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);
targetList.Add(value);
}
}
finally
{
acObservable?.EndUpdate();
}
}
/// <summary>
/// Optimized list populate that reuses existing items when possible.
/// </summary>
private static void PopulateListOptimized<TInput>(BinaryDeserializationContext<TInput> context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
where TInput : struct, IBinaryInputBase
{
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++)
{
// Read marker once — eliminates redundant PeekByte + ReadByte boundary checks
var typeCode = context.ReadByte();
// ObjectRefFirst: read cacheIndex before processing the object body
var cacheIndex = -1;
if (typeCode == BinaryTypeCode.ObjectRefFirst && elementMetadata != null)
{
cacheIndex = (int)context.ReadVarUInt();
// Treat as Object from here on — same wire body, just with cacheIndex prefix
typeCode = BinaryTypeCode.Object;
}
// If we have an existing item at this index and the incoming is an object, reuse it
if (i < existingCount && typeCode == BinaryTypeCode.Object && elementMetadata != null)
{
var existingItem = existingList[i];
if (existingItem != null)
{
if (cacheIndex >= 0)
context.RegisterInternedValueAt(cacheIndex, existingItem);
PopulateObjectPropertiesIndexed(context, existingItem, wrapper, nextDepth, skipDefaultWrite: false);
continue;
}
}
// Read new value — use pre-resolved wrapper for Object elements to skip GetWrapper dictionary lookup
object? value;
if (typeCode == BinaryTypeCode.Object && elementMetadata != null)
{
value = ReadObjectCoreWithWrapper(context, wrapper, cacheIndex: cacheIndex);
}
else
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
value = ReadValue(context, elementType);
}
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<TInput>(BinaryDeserializationContext<TInput> context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
where TInput : struct, IBinaryInputBase
{
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++)
{
// Read marker once — eliminates redundant PeekByte + ReadByte boundary checks
var itemCode = context.ReadByte();
// Read or create the new item
object? newItem;
if (itemCode == BinaryTypeCode.Object)
{
// Fast path: use pre-resolved wrapper, skip GetWrapper dictionary lookup
newItem = CreateInstance(elementType, elementMetadata);
if (newItem == null) continue;
PopulateObjectPropertiesIndexed(context, newItem, wrapper, nextDepth, skipDefaultWrite: true);
}
else
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
newItem = ReadValue(context, elementType);
if (newItem == null) continue;
}
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<TInput>(
BinaryDeserializationContext<TInput> context,
IList existingList,
Type elementType,
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
int depth)
where TInput : struct, IBinaryInputBase
{
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++)
{
// Read marker once — eliminates redundant PeekByte + ReadByte boundary checks
var itemCode = context.ReadByte();
// Read or create the new item
object? newItem;
if (itemCode == BinaryTypeCode.Object)
{
// Fast path: use pre-resolved wrapper, skip GetWrapper dictionary lookup
newItem = CreateInstance(elementType, elementMetadata);
if (newItem == null) continue;
PopulateObjectPropertiesIndexed(context, newItem, wrapper, nextDepth, skipDefaultWrite: true);
}
else
{
// Marker already consumed → rewind so ReadValue can read it
context._position--;
newItem = ReadValue(context, elementType);
if (newItem == null) continue;
}
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<TInput>(BinaryDeserializationContext<TInput> context, object target, BinaryPropertySetterInfo propInfo, IdAccessorType idType)
where TInput : struct, IBinaryInputBase
{
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
}