2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2025-12-13 16:30:24 +00:00

Async serialization

This commit is contained in:
2025-11-04 11:47:40 +03:00
parent a764b452e2
commit fc943c8a36
10 changed files with 717 additions and 119 deletions

View File

@@ -22,6 +22,7 @@ SOFTWARE.
*/ */
using Esiur.Data;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -102,6 +103,9 @@ public class AsyncBag<T> : AsyncReply, IAsyncBag
} }
else else
{ {
if (ArrayType != null)
replies[i] = RuntimeCaster.Cast(replies[i], ArrayType);
results.SetValue(replies[i], index); results.SetValue(replies[i], index);
count++; count++;
if (count == replies.Count) if (count == replies.Count)

View File

@@ -45,7 +45,7 @@ public static class DC // Data Converter
{ {
public static object CastConvert(object value, Type destinationType) public static object CastConvertOld(object value, Type destinationType)
{ {
if (value == null) if (value == null)
return null; return null;
@@ -68,7 +68,7 @@ public static class DC // Data Converter
for (var i = 0; i < rt.Length; i++) for (var i = 0; i < rt.Length; i++)
{ {
rt.SetValue(CastConvert(v.GetValue(i), destinationType), i); rt.SetValue(CastConvertOld(v.GetValue(i), destinationType), i);
} }
return rt; return rt;

View File

@@ -376,34 +376,74 @@ public static class DataDeserializer
public static unsafe object RecordParserAsync(ParsedTDU tdu, DistributedConnection connection, uint[] requestSequence) public static unsafe object RecordParserAsync(ParsedTDU tdu, DistributedConnection connection, uint[] requestSequence)
{ {
var reply = new AsyncReply<IRecord>();
var classId = tdu.Metadata.GetUUID(0); var classId = tdu.Metadata.GetUUID(0);
var template = connection.Instance.Warehouse.GetTemplateByClassId(classId,
TemplateType.Record);
var rt = new AsyncReply<IRecord>();
var template = connection.Instance.Warehouse.GetTemplateByClassId(classId, TemplateType.Record);
var list = new AsyncBag<object>();
ParsedTDU current;
ParsedTDU? previous = null;
var offset = tdu.Offset;
var length = tdu.ContentLength;
var ends = offset + (uint)length;
var initRecord = (TypeTemplate template) => var initRecord = (TypeTemplate template) =>
{ {
ListParserAsync(tdu, connection, requestSequence).Then(r => for (var i = 0; i < template.Properties.Length; i++)
{ {
var ar = (object[])r; current = ParsedTDU.Parse(tdu.Data, offset, ends);
if (template == null) if (current.Class == TDUClass.Invalid)
throw new Exception("Unknown type.");
if (current.Identifier == TDUIdentifier.TypeContinuation)
{ {
// @TODO: add parse if no template settings current.Class = previous.Value.Class;
reply.TriggerError(new AsyncException(ErrorType.Management, (ushort)ExceptionCode.TemplateNotFound, current.Identifier = previous.Value.Identifier;
"Template not found for record.")); current.Metadata = previous.Value.Metadata;
} }
else if (template.DefinedType != null) else if (current.Identifier == TDUIdentifier.TypeOfTarget)
{ {
var (idf, mt) = template.Properties[i].ValueType.GetMetadata();
current.Class = TDUClass.Typed;
current.Identifier = idf;
current.Metadata = mt;
current.Index = (int)idf & 0x7;
}
var (cs, reply) = Codec.ParseAsync(current, connection, requestSequence);
list.Add(reply);
if (cs > 0)
{
offset += (uint)current.TotalLength;
length -= (uint)current.TotalLength;
previous = current;
}
else
throw new Exception("Error while parsing structured data");
}
list.Seal();
list.Then(results =>
{
if (template.DefinedType != null)
{
var record = Activator.CreateInstance(template.DefinedType) as IRecord; var record = Activator.CreateInstance(template.DefinedType) as IRecord;
for (var i = 0; i < template.Properties.Length; i++) for (var i = 0; i < template.Properties.Length; i++)
{ {
try try
{ {
//var v = Convert.ChangeType(ar[i], template.Properties[i].PropertyInfo.PropertyType); var v = RuntimeCaster.Cast(results[i], template.Properties[i].PropertyInfo.PropertyType);
var v = DC.CastConvert(ar[i], template.Properties[i].PropertyInfo.PropertyType);
template.Properties[i].PropertyInfo.SetValue(record, v); template.Properties[i].PropertyInfo.SetValue(record, v);
} }
catch (Exception ex) catch (Exception ex)
@@ -412,21 +452,23 @@ public static class DataDeserializer
} }
} }
reply.Trigger(record); rt.Trigger(record);
} }
else else
{ {
var record = new Record(); var record = new Record();
for (var i = 0; i < template.Properties.Length; i++) for (var i = 0; i < template.Properties.Length; i++)
record.Add(template.Properties[i].Name, ar[i]); record.Add(template.Properties[i].Name, results[i]);
reply.Trigger(record); rt.Trigger(record);
} }
}); });
}; };
if (template != null) if (template != null)
{ {
initRecord(template); initRecord(template);
@@ -437,14 +479,83 @@ public static class DataDeserializer
connection.GetTemplate(classId).Then(tmp => connection.GetTemplate(classId).Then(tmp =>
{ {
initRecord(tmp); initRecord(tmp);
}).Error(x => reply.TriggerError(x)); }).Error(x => rt.TriggerError(x));
} }
else else
{ {
initRecord(null); initRecord(null);
} }
return reply; return rt;
//var classId = tdu.Metadata.GetUUID(0);
//var template = connection.Instance.Warehouse.GetTemplateByClassId(classId, TemplateType.Record);
//var initRecord = (TypeTemplate template) =>
//{
// ListParserAsync(tdu, connection, requestSequence).Then(r =>
// {
// var ar = (object[])r;
// if (template == null)
// {
// // @TODO: add parse if no template settings
// reply.TriggerError(new AsyncException(ErrorType.Management, (ushort)ExceptionCode.TemplateNotFound,
// "Template not found for record."));
// }
// else if (template.DefinedType != null)
// {
// var record = Activator.CreateInstance(template.DefinedType) as IRecord;
// for (var i = 0; i < template.Properties.Length; i++)
// {
// try
// {
// //var v = Convert.ChangeType(ar[i], template.Properties[i].PropertyInfo.PropertyType);
// var v = RuntimeCaster.Cast(ar[i], template.Properties[i].PropertyInfo.PropertyType);
// template.Properties[i].PropertyInfo.SetValue(record, v);
// }
// catch (Exception ex)
// {
// Global.Log(ex);
// }
// }
// reply.Trigger(record);
// }
// else
// {
// var record = new Record();
// for (var i = 0; i < template.Properties.Length; i++)
// record.Add(template.Properties[i].Name, ar[i]);
// reply.Trigger(record);
// }
// });
//};
//if (template != null)
//{
// initRecord(template);
//}
//else if (connection != null)
//{
// // try to get the template from the other end
// connection.GetTemplate(classId).Then(tmp =>
// {
// initRecord(tmp);
// }).Error(x => reply.TriggerError(x));
//}
//else
//{
// initRecord(null);
//}
//return reply;
} }
@@ -453,13 +564,6 @@ public static class DataDeserializer
var classId = tdu.Metadata.GetUUID(0); var classId = tdu.Metadata.GetUUID(0);
var template = warehouse.GetTemplateByClassId(classId, TemplateType.Record); var template = warehouse.GetTemplateByClassId(classId, TemplateType.Record);
//var r = ListParser(tdu, warehouse);
//var ar = (object[])r;
if (template == null) if (template == null)
{ {
// @TODO: add parse if no template settings // @TODO: add parse if no template settings
@@ -515,9 +619,6 @@ public static class DataDeserializer
} }
if (template.DefinedType != null) if (template.DefinedType != null)
{ {
@@ -526,8 +627,7 @@ public static class DataDeserializer
{ {
try try
{ {
//var v = Convert.ChangeType(ar[i], template.Properties[i].PropertyInfo.PropertyType); var v = RuntimeCaster.Cast(list[i], template.Properties[i].PropertyInfo.PropertyType);
var v = DC.CastConvert(list[i], template.Properties[i].PropertyInfo.PropertyType);
template.Properties[i].PropertyInfo.SetValue(record, v); template.Properties[i].PropertyInfo.SetValue(record, v);
} }
catch (Exception ex) catch (Exception ex)
@@ -567,7 +667,8 @@ public static class DataDeserializer
var index = tdu.Data[tdu.Offset]; var index = tdu.Data[tdu.Offset];
var template = connection.Instance.Warehouse.GetTemplateByClassId(classId, TemplateType.Enum); var template = connection.Instance.Warehouse.GetTemplateByClassId(classId,
TemplateType.Enum);
if (template != null) if (template != null)
{ {
@@ -839,52 +940,90 @@ public static class DataDeserializer
public static AsyncReply TypedMapParserAsync(ParsedTDU tdu, DistributedConnection connection, uint[] requestSequence) public static AsyncReply TypedMapParserAsync(ParsedTDU tdu, DistributedConnection connection, uint[] requestSequence)
{ {
// get key type
var (keyCs, keyRepType) = TRU.Parse(tdu.Metadata, 0);
var (valueCs, valueRepType) = TRU.Parse(tdu.Metadata, keyCs);
var wh = connection.Instance.Warehouse;
var map = (IMap)Activator.CreateInstance(typeof(Map<,>).MakeGenericType(keyRepType.GetRuntimeType(wh), valueRepType.GetRuntimeType(wh)));
var rt = new AsyncReply(); var rt = new AsyncReply();
var results = new AsyncBag<object>(); // get key type
var offset = tdu.Offset; var (keyCs, keysTru) = TRU.Parse(tdu.Metadata, 0);
var length = tdu.ContentLength; var (valueCs, valuesTru) = TRU.Parse(tdu.Metadata, keyCs);
while (length > 0) var map = (IMap)Activator.CreateInstance(typeof(Map<,>).MakeGenericType(
keysTru.GetRuntimeType(connection.Instance.Warehouse),
valuesTru.GetRuntimeType(connection.Instance.Warehouse)));
var keysTdu = ParsedTDU.Parse(tdu.Data, tdu.Offset,
(uint)(tdu.Offset + tdu.ContentLength));
var valuesTdu = ParsedTDU.Parse(tdu.Data,
(uint)(keysTdu.Offset + keysTdu.ContentLength),
tdu.Ends);
var keysReply = TypedArrayParserAsync(keysTdu, keysTru, connection, requestSequence);
var valuesReply = TypedArrayParserAsync(valuesTdu, valuesTru, connection, requestSequence);
keysReply.Then(keys =>
{ {
var (cs, reply) = Codec.ParseAsync(tdu.Data, offset, connection, requestSequence); valuesReply.Then(values =>
results.Add(reply);
if (cs > 0)
{ {
offset += (uint)cs; for (var i = 0; i < ((Array)keys).Length; i++)
length -= (uint)cs; map.Add(((Array)keys).GetValue(i), ((Array)values).GetValue(i));
} });
else
throw new Exception("Error while parsing structured data");
}
results.Seal();
results.Then(ar =>
{
for (var i = 0; i < ar.Length; i += 2)
map.Add(ar[i], ar[i + 1]);
rt.Trigger(map);
}); });
return rt; return rt;
//// get key type
//var (keyCs, keyRepType) = TRU.Parse(tdu.Metadata, 0);
//var (valueCs, valueRepType) = TRU.Parse(tdu.Metadata, keyCs);
//var wh = connection.Instance.Warehouse;
//var map = (IMap)Activator.CreateInstance(typeof(Map<,>).MakeGenericType(keyRepType.GetRuntimeType(wh), valueRepType.GetRuntimeType(wh)));
//var rt = new AsyncReply();
//var results = new AsyncBag<object>();
//var offset = tdu.Offset;
//var length = tdu.ContentLength;
//while (length > 0)
//{
// var (cs, reply) = Codec.ParseAsync(tdu.Data, offset, connection, requestSequence);
// results.Add(reply);
// if (cs > 0)
// {
// offset += (uint)cs;
// length -= (uint)cs;
// }
// else
// throw new Exception("Error while parsing structured data");
//}
//results.Seal();
//results.Then(ar =>
//{
// for (var i = 0; i < ar.Length; i += 2)
// map.Add(ar[i], ar[i + 1]);
// rt.Trigger(map);
//});
//return rt;
} }
public static Array TypedArrayParser(ParsedTDU tdu, TRU tru, Warehouse warehouse) public static Array TypedArrayParser(ParsedTDU tdu, TRU tru, Warehouse warehouse)
@@ -927,7 +1066,7 @@ public static class DataDeserializer
if (current.Class == TDUClass.Invalid) if (current.Class == TDUClass.Invalid)
throw new Exception("Unknown type."); throw new Exception("Unknown type.");
if (current.Identifier == TDUIdentifier.TypeContinuation) if (current.Identifier == TDUIdentifier.TypeContinuation)
{ {
@@ -979,11 +1118,11 @@ public static class DataDeserializer
var keysTdu = ParsedTDU.Parse(tdu.Data, tdu.Offset, var keysTdu = ParsedTDU.Parse(tdu.Data, tdu.Offset,
(uint)(tdu.Offset + tdu.ContentLength)); (uint)(tdu.Offset + tdu.ContentLength));
var valuesTdu = ParsedTDU.Parse(tdu.Data, var valuesTdu = ParsedTDU.Parse(tdu.Data,
(uint)(keysTdu.Offset+keysTdu.ContentLength), (uint)(keysTdu.Offset + keysTdu.ContentLength),
tdu.Ends); tdu.Ends);
var keys = TypedArrayParser(keysTdu, keysTru, warehouse); var keys = TypedArrayParser(keysTdu, keysTru, warehouse);
@@ -1168,17 +1307,9 @@ public static class DataDeserializer
} }
public static AsyncReply TypedArrayParserAsync(byte[] data, uint offset, TRU tru, DistributedConnection connection, uint[] requestSequence) public static AsyncReply TypedArrayParserAsync(ParsedTDU tdu, TRU tru, DistributedConnection connection, uint[] requestSequence)
{ {
throw new NotImplementedException(); switch (tru.Identifier)
}
public static AsyncReply TypedListParserAsync(ParsedTDU tdu, DistributedConnection connection, uint[] requestSequence)
{
// get the type
var (hdrCs, rep) = TRU.Parse(tdu.Metadata, 0);
switch (rep.Identifier)
{ {
case TRUIdentifier.Int32: case TRUIdentifier.Int32:
return new AsyncReply(GroupInt32Codec.Decode(tdu.Data.AsSpan( return new AsyncReply(GroupInt32Codec.Decode(tdu.Data.AsSpan(
@@ -1200,11 +1331,10 @@ public static class DataDeserializer
(int)tdu.Offset, (int)tdu.ContentLength))); (int)tdu.Offset, (int)tdu.ContentLength)));
default: default:
var rt = new AsyncBag<object>();
var runtimeType = rep.GetRuntimeType(connection.Instance.Warehouse); var list = new AsyncBag<object>();
rt.ArrayType = runtimeType; list.ArrayType = tru.GetRuntimeType(connection.Instance.Warehouse);
ParsedTDU current; ParsedTDU current;
ParsedTDU? previous = null; ParsedTDU? previous = null;
@@ -1215,7 +1345,6 @@ public static class DataDeserializer
while (length > 0) while (length > 0)
{ {
current = ParsedTDU.Parse(tdu.Data, offset, ends); current = ParsedTDU.Parse(tdu.Data, offset, ends);
if (current.Class == TDUClass.Invalid) if (current.Class == TDUClass.Invalid)
@@ -1228,24 +1357,111 @@ public static class DataDeserializer
current.Identifier = previous.Value.Identifier; current.Identifier = previous.Value.Identifier;
current.Metadata = previous.Value.Metadata; current.Metadata = previous.Value.Metadata;
} }
else if (current.Identifier == TDUIdentifier.TypeOfTarget)
{
var (idf, mt) = tru.GetMetadata();
current.Class = TDUClass.Typed;
current.Identifier = idf;
current.Metadata = mt;
current.Index = (int)idf & 0x7;
}
var (cs, reply) = Codec.ParseAsync(tdu.Data, offset, connection, requestSequence); var (cs, reply) = Codec.ParseAsync(current, connection, requestSequence);
rt.Add(reply); list.Add(reply);
if (cs > 0) if (cs > 0)
{ {
offset += (uint)cs; offset += (uint)current.TotalLength;
length -= (uint)cs; length -= (uint)current.TotalLength;
previous = current;
} }
else else
throw new Exception("Error while parsing structured data"); throw new Exception("Error while parsing structured data");
} }
rt.Seal(); list.Seal();
return rt; return list;
} }
}
public static AsyncReply TypedListParserAsync(ParsedTDU tdu, DistributedConnection connection, uint[] requestSequence)
{
// get the type
var (hdrCs, tru) = TRU.Parse(tdu.Metadata, 0);
return TypedArrayParserAsync(tdu, tru, connection, requestSequence);
//switch (rep.Identifier)
//{
// case TRUIdentifier.Int32:
// return new AsyncReply(GroupInt32Codec.Decode(tdu.Data.AsSpan(
// (int)tdu.Offset, (int)tdu.ContentLength)));
// case TRUIdentifier.Int64:
// return new AsyncReply(GroupInt64Codec.Decode(tdu.Data.AsSpan(
// (int)tdu.Offset, (int)tdu.ContentLength)));
// case TRUIdentifier.Int16:
// return new AsyncReply(GroupInt16Codec.Decode(tdu.Data.AsSpan(
// (int)tdu.Offset, (int)tdu.ContentLength)));
// case TRUIdentifier.UInt32:
// return new AsyncReply(GroupUInt32Codec.Decode(tdu.Data.AsSpan(
// (int)tdu.Offset, (int)tdu.ContentLength)));
// case TRUIdentifier.UInt64:
// return new AsyncReply(GroupUInt64Codec.Decode(tdu.Data.AsSpan(
// (int)tdu.Offset, (int)tdu.ContentLength)));
// case TRUIdentifier.UInt16:
// return new AsyncReply(GroupUInt16Codec.Decode(tdu.Data.AsSpan(
// (int)tdu.Offset, (int)tdu.ContentLength)));
// default:
// var rt = new AsyncBag<object>();
// var runtimeType = rep.GetRuntimeType(connection.Instance.Warehouse);
// rt.ArrayType = runtimeType;
// ParsedTDU current;
// ParsedTDU? previous = null;
// var offset = tdu.Offset;
// var length = tdu.ContentLength;
// var ends = offset + (uint)length;
// while (length > 0)
// {
// current = ParsedTDU.Parse(tdu.Data, offset, ends);
// if (current.Class == TDUClass.Invalid)
// throw new Exception("Unknown type.");
// if (current.Identifier == TDUIdentifier.TypeContinuation)
// {
// current.Class = previous.Value.Class;
// current.Identifier = previous.Value.Identifier;
// current.Metadata = previous.Value.Metadata;
// }
// var (cs, reply) = Codec.ParseAsync(tdu.Data, offset, connection, requestSequence);
// rt.Add(reply);
// if (cs > 0)
// {
// offset += (uint)cs;
// length -= (uint)cs;
// }
// else
// throw new Exception("Error while parsing structured data");
// }
// rt.Seal();
// return rt;
//}
} }
public static object TypedListParser(ParsedTDU tdu, Warehouse warehouse) public static object TypedListParser(ParsedTDU tdu, Warehouse warehouse)
@@ -1268,7 +1484,7 @@ public static class DataDeserializer
var pvs = new List<PropertyValue>(); var pvs = new List<PropertyValue>();
for (var i = 0; i < ar.Length; i += 3) for (var i = 0; i < ar.Length; i += 3)
pvs.Add(new PropertyValue(ar[2], (ulong?)ar[0], (DateTime?)ar[1])); pvs.Add(new PropertyValue(ar[2], Convert.ToUInt64(ar[0]), (DateTime?)ar[1]));
rt.Trigger(pvs.ToArray()); rt.Trigger(pvs.ToArray());

View File

@@ -568,7 +568,7 @@ public static class DataSerializer
{ {
var tdu = Codec.ComposeInternal(i, warehouse, connection); var tdu = Codec.ComposeInternal(i, warehouse, connection);
var currentTru = TRU.FromType(i.GetType()); var currentTru = TRU.FromType(i?.GetType());
if (isTyped && tru.Match(currentTru)) if (isTyped && tru.Match(currentTru))
{ {

370
Esiur/Data/RuntimeCaster.cs Normal file
View File

@@ -0,0 +1,370 @@
using System;
using System.Collections.Generic;
using System.Text;
#nullable enable
namespace Esiur.Data;
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.Linq.Expressions;
public enum NaNInfinityPolicy
{
Throw, // Default: throw on NaN/∞ when converting to non-floating types
NullIfNullable, // If target is Nullable<decimal>, return null; otherwise throw
CoerceZero // Replace NaN/∞ with 0
}
public sealed class RuntimeCastOptions
{
public bool CheckedNumeric { get; set; } = true;
// For DateTime/DateTimeOffset parsing
public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture;
public DateTimeStyles DateTimeStyles { get; set; } = DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal;
// For enums
public bool EnumIgnoreCase { get; set; } = true;
public bool EnumMustBeDefined { get; set; } = false;
// float/double → decimal behavior
public NaNInfinityPolicy NaNInfinityPolicy { get; set; } = NaNInfinityPolicy.Throw;
}
public static class RuntimeCaster
{
public static readonly RuntimeCastOptions Default = new RuntimeCastOptions();
// (fromType, toType) -> converter(value, options) (options captured at call time)
private static readonly ConcurrentDictionary<(Type from, Type to), Func<object, RuntimeCastOptions, object>> _cache = new();
// Numeric-only compiled converters (fast path), keyed by checked/unchecked
private static readonly ConcurrentDictionary<(Type from, Type to, bool @checked), Func<object, object>> _numericCache = new();
public static object Cast(object value, Type toType, RuntimeCastOptions? options = null)
{
options ??= Default;
if (toType is null) throw new ArgumentNullException(nameof(toType));
if (value is null)
{
if (IsNonNullableValueType(toType))
throw new InvalidCastException($"Cannot cast null to non-nullable {toType}.");
return null;
}
var fromType = value.GetType();
if (toType.IsAssignableFrom(fromType)) return value; // already compatible
var fn = _cache.GetOrAdd((fromType, toType), k => BuildConverter(k.from, k.to));
return fn(value, options);
}
// ------------------------ Builder ------------------------
private static Func<object, RuntimeCastOptions, object> BuildConverter(Type fromType, Type toType)
{
// Nullable handling is done inside ConvertCore.
return (value, opts) => ConvertCore(value, fromType, toType, opts);
}
// ------------------------ Core Routing ------------------------
private static object ConvertCore(object value, Type fromType, Type toType, RuntimeCastOptions opts)
{
var toUnderlying = Nullable.GetUnderlyingType(toType) ?? toType;
var fromUnderlying = Nullable.GetUnderlyingType(fromType) ?? fromType;
// If converting nullable source and it's null → result is null if target nullable, else throw
if (!fromType.IsValueType && value is null)
{
if (IsNonNullableValueType(toType))
throw new InvalidCastException($"Cannot cast null to non-nullable {toType}.");
return null;
}
// Special cases first
// 1) Enum targets
if (toUnderlying.IsEnum)
return ConvertToEnum(value, fromUnderlying, toType, toUnderlying, opts);
// 2) Guid targets
if (toUnderlying == typeof(Guid))
return ConvertToGuid(value, fromUnderlying, toType);
// 3) DateTime / DateTimeOffset targets
if (toUnderlying == typeof(DateTime))
return ConvertToDateTime(value, fromUnderlying, toType, opts);
if (toUnderlying == typeof(DateTimeOffset))
return ConvertToDateTimeOffset(value, fromUnderlying, toType, opts);
// 4) decimal from float/double with NaN/∞ policy
if (toUnderlying == typeof(decimal) && (fromUnderlying == typeof(float) || fromUnderlying == typeof(double)))
{
var dec = ConvertFloatDoubleToDecimal(value, fromUnderlying, opts, out bool useNull);
if (toType != toUnderlying) // wrap in Nullable<decimal>
return useNull ? null : (decimal?)dec;
if (useNull) throw new OverflowException("NaN/Infinity cannot be converted to decimal.");
return dec;
}
// 5) General numeric conversions via compiled expression
if (IsNumeric(fromUnderlying) && IsNumeric(toUnderlying))
{
var nc = _numericCache.GetOrAdd((fromUnderlying, toUnderlying, opts.CheckedNumeric),
k => BuildNumericConverter(k.from, k.to, k.@checked));
var result = nc(value);
// Wrap into nullable if needed
if (toType != toUnderlying) return BoxNullable(result, toUnderlying);
return result;
}
// 6) String <-> other basics (Use TypeConverter first; if no path, fall through)
if (toUnderlying == typeof(string))
return value?.ToString();
// 7) Last-resort: TypeConverter or ChangeType once, inside this compiled path
// Try TypeConverter(target)
var tc = System.ComponentModel.TypeDescriptor.GetConverter(toUnderlying);
if (tc.CanConvertFrom(fromUnderlying))
{
var r = tc.ConvertFrom(null, opts.Culture, value);
if (toType != toUnderlying) return BoxNullable(r, toUnderlying);
return r!;
}
// Try TypeConverter(source)
var tc2 = System.ComponentModel.TypeDescriptor.GetConverter(fromUnderlying);
if (tc2.CanConvertTo(toUnderlying))
{
var r = tc2.ConvertTo(null, opts.Culture, value, toUnderlying);
if (toType != toUnderlying) return BoxNullable(r, toUnderlying);
return r!;
}
// Try Convert.ChangeType for IConvertible fallbacks
try
{
var r = Convert.ChangeType(value, toUnderlying, opts.Culture);
if (toType != toUnderlying) return BoxNullable(r, toUnderlying);
return r!;
}
catch
{
// Final attempt: assignable cast via reflection (e.g., interfaces)
if (toUnderlying.IsInstanceOfType(value))
{
if (toType != toUnderlying) return BoxNullable(value, toUnderlying);
return value;
}
throw new InvalidCastException($"Cannot cast {fromType} to {toType}.");
}
}
// ------------------------ Helpers: Numeric ------------------------
private static Func<object, object> BuildNumericConverter(Type from, Type to, bool @checked)
{
var p = Expression.Parameter(typeof(object), "v");
Expression val = from.IsValueType
? Expression.Unbox(p, from)
: Expression.Convert(p, from);
Expression body;
try
{
body = @checked ? Expression.ConvertChecked(val, to) : Expression.Convert(val, to);
}
catch (InvalidOperationException)
{
// Non-legal numeric convert — should be rare given IsNumeric check.
throw new InvalidCastException($"Numeric conversion not supported: {from} -> {to}");
}
// Box result to object
Expression boxed = to.IsValueType ? Expression.Convert(body, typeof(object)) : body;
return Expression.Lambda<Func<object, object>>(boxed, p).Compile();
}
private static bool IsNumeric(Type t)
{
t = Nullable.GetUnderlyingType(t) ?? t;
return t == typeof(byte) || t == typeof(sbyte) ||
t == typeof(short) || t == typeof(ushort) ||
t == typeof(int) || t == typeof(uint) ||
t == typeof(long) || t == typeof(ulong) ||
t == typeof(float) || t == typeof(double) ||
t == typeof(decimal);
}
private static bool IsNonNullableValueType(Type t)
=> t.IsValueType && Nullable.GetUnderlyingType(t) is null;
private static object BoxNullable(object value, Type underlying)
{
// Create Nullable<T>(value) then box
var nt = typeof(Nullable<>).MakeGenericType(underlying);
var ctor = nt.GetConstructor(new[] { underlying })!;
return ctor.Invoke(new[] { value });
}
// ------------------------ Helpers: NaN/∞ to decimal ------------------------
private static decimal ConvertFloatDoubleToDecimal(object value, Type fromUnderlying, RuntimeCastOptions opts, out bool useNull)
{
useNull = false;
if (fromUnderlying == typeof(float))
{
var f = (float)value;
if (float.IsNaN(f) || float.IsInfinity(f))
{
switch (opts.NaNInfinityPolicy)
{
case NaNInfinityPolicy.NullIfNullable: useNull = true; return default;
case NaNInfinityPolicy.CoerceZero: return 0m;
default: throw new OverflowException("Cannot convert NaN/Infinity to decimal.");
}
}
return opts.CheckedNumeric ? checked((decimal)f) : (decimal)f;
}
else // double
{
var d = (double)value;
if (double.IsNaN(d) || double.IsInfinity(d))
{
switch (opts.NaNInfinityPolicy)
{
case NaNInfinityPolicy.NullIfNullable: useNull = true; return default;
case NaNInfinityPolicy.CoerceZero: return 0m;
default: throw new OverflowException("Cannot convert NaN/Infinity to decimal.");
}
}
return opts.CheckedNumeric ? checked((decimal)d) : (decimal)d;
}
}
// ------------------------ Helpers: Enum ------------------------
private static object ConvertToEnum(object value, Type fromUnderlying, Type toType, Type enumType, RuntimeCastOptions opts)
{
// Nullable<Enum> wrapping
bool wrapNullable = toType != enumType;
// String → Enum
if (fromUnderlying == typeof(string))
{
var parsed = Enum.Parse(enumType, (string)value, opts.EnumIgnoreCase);
if (opts.EnumMustBeDefined && !Enum.IsDefined(enumType, parsed!))
throw new InvalidCastException($"Value '{value}' is not a defined member of {enumType.Name}.");
return wrapNullable ? BoxNullable(parsed!, enumType) : parsed!;
}
// Numeric → Enum
if (IsNumeric(fromUnderlying))
{
// Convert numeric to enums underlying integral type first (checked/unchecked handled by compiled numeric path)
var et = Enum.GetUnderlyingType(enumType);
var numConv = _numericCache.GetOrAdd((fromUnderlying, et, true), k => BuildNumericConverter(k.from, k.to, k.@checked));
var integral = numConv(value);
var enumObj = Enum.ToObject(enumType, integral);
if (opts.EnumMustBeDefined && !Enum.IsDefined(enumType, enumObj))
throw new InvalidCastException($"Numeric value {integral} is not a defined member of {enumType.Name}.");
return wrapNullable ? BoxNullable(enumObj, enumType) : enumObj;
}
// Fallback: not supported
throw new InvalidCastException($"Cannot cast {fromUnderlying} to enum {enumType.Name}.");
}
// ------------------------ Helpers: Guid ------------------------
private static object ConvertToGuid(object value, Type fromUnderlying, Type toType)
{
bool wrapNullable = toType != typeof(Guid);
if (fromUnderlying == typeof(string))
{
if (!Guid.TryParse((string)value, out var g))
throw new InvalidCastException($"Cannot parse '{value}' to Guid.");
return wrapNullable ? (Guid?)g : g;
}
if (fromUnderlying == typeof(byte[]))
{
var bytes = (byte[])value;
if (bytes.Length != 16)
throw new InvalidCastException("Guid requires a 16-byte array.");
var g = new Guid(bytes);
return wrapNullable ? (Guid?)g : g;
}
throw new InvalidCastException($"Cannot cast {fromUnderlying} to Guid.");
}
// ------------------------ Helpers: DateTime / DateTimeOffset ------------------------
private static object ConvertToDateTime(object value, Type fromUnderlying, Type toType, RuntimeCastOptions opts)
{
bool wrapNullable = toType != typeof(DateTime);
if (fromUnderlying == typeof(string))
{
if (!DateTime.TryParse((string)value, opts.Culture, opts.DateTimeStyles, out var dt))
throw new InvalidCastException($"Cannot parse '{value}' to DateTime.");
return wrapNullable ? (DateTime?)dt : dt;
}
if (fromUnderlying == typeof(long))
{
// Treat as ticks
var dt = new DateTime((long)value, DateTimeKind.Unspecified);
return wrapNullable ? (DateTime?)dt : dt;
}
if (fromUnderlying == typeof(double))
{
// Treat as OADate if finite
var d = (double)value;
if (double.IsNaN(d) || double.IsInfinity(d))
throw new InvalidCastException("Cannot convert NaN/Infinity to DateTime.");
var dt = DateTime.FromOADate(d);
return wrapNullable ? (DateTime?)dt : dt;
}
throw new InvalidCastException($"Cannot cast {fromUnderlying} to DateTime.");
}
private static object ConvertToDateTimeOffset(object value, Type fromUnderlying, Type toType, RuntimeCastOptions opts)
{
bool wrapNullable = toType != typeof(DateTimeOffset);
if (fromUnderlying == typeof(string))
{
if (!DateTimeOffset.TryParse((string)value, opts.Culture, opts.DateTimeStyles, out var dto))
throw new InvalidCastException($"Cannot parse '{value}' to DateTimeOffset.");
return wrapNullable ? (DateTimeOffset?)dto : dto;
}
if (fromUnderlying == typeof(long))
{
// Treat as ticks since 0001-01-01
var dto = new DateTimeOffset(new DateTime((long)value, DateTimeKind.Unspecified));
return wrapNullable ? (DateTimeOffset?)dto : dto;
}
if (fromUnderlying == typeof(double))
{
var d = (double)value;
if (double.IsNaN(d) || double.IsInfinity(d))
throw new InvalidCastException("Cannot convert NaN/Infinity to DateTimeOffset.");
var dt = DateTime.FromOADate(d);
var dto = new DateTimeOffset(dt);
return wrapNullable ? (DateTimeOffset?)dto : dto;
}
throw new InvalidCastException($"Cannot cast {fromUnderlying} to DateTimeOffset.");
}
}

View File

@@ -194,6 +194,9 @@ namespace Esiur.Data
if (Identifier == TRUIdentifier.TypedList && SubTypes[0].Identifier == TRUIdentifier.UInt8) if (Identifier == TRUIdentifier.TypedList && SubTypes[0].Identifier == TRUIdentifier.UInt8)
return false; return false;
if (Identifier == TRUIdentifier.TypedResource)
return false;
return (UUID != null) || (SubTypes != null && SubTypes.Length > 0); return (UUID != null) || (SubTypes != null && SubTypes.Length > 0);
} }
@@ -234,6 +237,7 @@ namespace Esiur.Data
SubTypes[0].Compose().Concat(SubTypes[1].Compose()).ToArray()); SubTypes[0].Compose().Concat(SubTypes[1].Compose()).ToArray());
case TRUIdentifier.Enum: case TRUIdentifier.Enum:
return (TDUIdentifier.TypedEnum, UUID?.Data); return (TDUIdentifier.TypedEnum, UUID?.Data);
default: default:
throw new NotImplementedException(); throw new NotImplementedException();

View File

@@ -113,7 +113,7 @@ public class HTTPServer : NetworkServer<HTTPConnection>, IResource
foreach (var kv in ParameterIndex) foreach (var kv in ParameterIndex)
{ {
var g = match.Groups[kv.Key]; var g = match.Groups[kv.Key];
args[kv.Value.Position] = DC.CastConvert(g.Value, kv.Value.ParameterType); args[kv.Value.Position] = RuntimeCaster.Cast(g.Value, kv.Value.ParameterType);
} }
if (SenderIndex != null) if (SenderIndex != null)

View File

@@ -384,7 +384,7 @@ public partial class DistributedConnection : NetworkConnection, IStore
SendRequest(IIPPacketRequest.KeepAlive, now, interval) SendRequest(IIPPacketRequest.KeepAlive, now, interval)
.Then(x => .Then(x =>
{ {
Jitter = (uint)((object[])x)[1]; Jitter =Convert.ToUInt32(((object[])x)[1]);
keepAliveTimer.Start(); keepAliveTimer.Start();
}).Error(ex => }).Error(ex =>
{ {
@@ -429,7 +429,8 @@ public partial class DistributedConnection : NetworkConnection, IStore
if (packet.DataType == null) if (packet.DataType == null)
return offset; return offset;
//Console.WriteLine("Incoming: " + packet); Console.WriteLine("Incoming: " + packet);
if (packet.Method == IIPPacketMethod.Notification) if (packet.Method == IIPPacketMethod.Notification)
{ {
var dt = packet.DataType.Value; var dt = packet.DataType.Value;
@@ -934,7 +935,7 @@ public partial class DistributedConnection : NetworkConnection, IStore
SendParams() SendParams()
.AddUInt8((byte)IIPAuthPacketAcknowledge.NoAuthCredentials) .AddUInt8((byte)IIPAuthPacketAcknowledge.NoAuthCredentials)
.AddUInt8Array(Codec.Compose(localHeaders, this.Instance.Warehouse, this)) .AddUInt8Array(Codec.Compose(localHeaders, Server.Instance.Warehouse, this))
.Done(); .Done();
} }
else else
@@ -1321,7 +1322,7 @@ public partial class DistributedConnection : NetworkConnection, IStore
SendParams() SendParams()
.AddUInt8((byte)IIPAuthPacketEvent.IAuthHashed) .AddUInt8((byte)IIPAuthPacketEvent.IAuthHashed)
.AddUInt8Array(Codec.Compose(args, this.Instance.Warehouse, this)) .AddUInt8Array(Codec.Compose(args, Server.Instance.Warehouse, this))
.Done(); .Done();
} }

View File

@@ -402,7 +402,7 @@ partial class DistributedConnection
{ {
var (size, rt) = Codec.ParseSync(dataType, Instance.Warehouse); var (size, rt) = Codec.ParseSync(dataType, Instance.Warehouse);
var resourceId = (uint)rt; var resourceId = Convert.ToUInt32(rt);
if (attachedResources.Contains(resourceId)) if (attachedResources.Contains(resourceId))
{ {
@@ -475,7 +475,7 @@ partial class DistributedConnection
DataDeserializer.LimitedCountListParser(data, dataType.Offset, DataDeserializer.LimitedCountListParser(data, dataType.Offset,
dataType.ContentLength, Instance.Warehouse, 2); dataType.ContentLength, Instance.Warehouse, 2);
var resourceId = (uint)args[0]; var resourceId = Convert.ToUInt32(args[0]);
var index = (byte)args[1]; var index = (byte)args[1];
Fetch(resourceId, null).Then(r => Fetch(resourceId, null).Then(r =>
@@ -522,7 +522,7 @@ partial class DistributedConnection
var (_, value) = Codec.ParseSync(dataType, Instance.Warehouse); var (_, value) = Codec.ParseSync(dataType, Instance.Warehouse);
var resourceId = (uint)value; var resourceId = Convert.ToUInt32(value);
Instance.Warehouse.GetById(resourceId).Then((res) => Instance.Warehouse.GetById(resourceId).Then((res) =>
{ {
@@ -579,7 +579,8 @@ partial class DistributedConnection
DataDeserializer.LimitedCountListParser(data, dataType.Offset, DataDeserializer.LimitedCountListParser(data, dataType.Offset,
dataType.ContentLength, Instance.Warehouse, 2); dataType.ContentLength, Instance.Warehouse, 2);
var resourceId = (uint)args[0]; var resourceId = Convert.ToUInt32(args[0]);
var age = (ulong)args[1]; var age = (ulong)args[1];
Instance.Warehouse.GetById(resourceId).Then((res) => Instance.Warehouse.GetById(resourceId).Then((res) =>
@@ -635,7 +636,7 @@ partial class DistributedConnection
var (_, value) = Codec.ParseSync(dataType, Instance.Warehouse); var (_, value) = Codec.ParseSync(dataType, Instance.Warehouse);
var resourceId = (uint)value; var resourceId = Convert.ToUInt32(value);
Instance.Warehouse.GetById(resourceId).Then((res) => Instance.Warehouse.GetById(resourceId).Then((res) =>
{ {
@@ -727,7 +728,7 @@ partial class DistributedConnection
var (_, value) = Codec.ParseSync(dataType, Instance.Warehouse); var (_, value) = Codec.ParseSync(dataType, Instance.Warehouse);
var resourceId = (uint)value; var resourceId = Convert.ToUInt32(value);
Instance.Warehouse.GetById(resourceId).Then(r => Instance.Warehouse.GetById(resourceId).Then(r =>
{ {
@@ -757,7 +758,9 @@ partial class DistributedConnection
var (offset, length, args) = DataDeserializer.LimitedCountListParser(data, dataType.Offset, var (offset, length, args) = DataDeserializer.LimitedCountListParser(data, dataType.Offset,
dataType.ContentLength, Instance.Warehouse); dataType.ContentLength, Instance.Warehouse);
var resourceId = (uint)args[0];
var resourceId = Convert.ToUInt32(args[0]);
var name = (string)args[1]; var name = (string)args[1];
if (name.Contains("/")) if (name.Contains("/"))
@@ -874,7 +877,7 @@ partial class DistributedConnection
var (_, value) = Codec.ParseSync(dataType, Instance.Warehouse); var (_, value) = Codec.ParseSync(dataType, Instance.Warehouse);
var resourceId = (uint)value; var resourceId = Convert.ToUInt32(value);
Instance.Warehouse.GetById(resourceId).Then((r) => Instance.Warehouse.GetById(resourceId).Then((r) =>
{ {
@@ -1120,7 +1123,7 @@ partial class DistributedConnection
var (offset, length, args) = DataDeserializer.LimitedCountListParser(data, dataType.Offset, var (offset, length, args) = DataDeserializer.LimitedCountListParser(data, dataType.Offset,
dataType.ContentLength, Instance.Warehouse, 2); dataType.ContentLength, Instance.Warehouse, 2);
var resourceId = (uint)args[0]; var resourceId = Convert.ToUInt32(args[0]);
var index = (byte)args[1]; var index = (byte)args[1];
Instance.Warehouse.GetById(resourceId).Then((r) => Instance.Warehouse.GetById(resourceId).Then((r) =>
@@ -1239,7 +1242,7 @@ partial class DistributedConnection
for (byte i = 0; i < pis.Length - 1; i++) for (byte i = 0; i < pis.Length - 1; i++)
{ {
if (arguments.ContainsKey(i)) if (arguments.ContainsKey(i))
args[i] = DC.CastConvert(arguments[i], pis[i].ParameterType); args[i] = RuntimeCaster.Cast(arguments[i], pis[i].ParameterType);
else if (ft.Arguments[i].Type.Nullable) else if (ft.Arguments[i].Type.Nullable)
args[i] = null; args[i] = null;
else else
@@ -1256,7 +1259,7 @@ partial class DistributedConnection
for (byte i = 0; i < pis.Length - 1; i++) for (byte i = 0; i < pis.Length - 1; i++)
{ {
if (arguments.ContainsKey(i)) if (arguments.ContainsKey(i))
args[i] = DC.CastConvert(arguments[i], pis[i].ParameterType); args[i] = RuntimeCaster.Cast(arguments[i], pis[i].ParameterType);
else if (ft.Arguments[i].Type.Nullable) else if (ft.Arguments[i].Type.Nullable)
args[i] = null; args[i] = null;
else else
@@ -1272,7 +1275,7 @@ partial class DistributedConnection
for (byte i = 0; i < pis.Length; i++) for (byte i = 0; i < pis.Length; i++)
{ {
if (arguments.ContainsKey(i)) if (arguments.ContainsKey(i))
args[i] = DC.CastConvert(arguments[i], pis[i].ParameterType); args[i] = RuntimeCaster.Cast(arguments[i], pis[i].ParameterType);
else if (ft.Arguments[i].Type.Nullable) //Nullable.GetUnderlyingType(pis[i].ParameterType) != null) else if (ft.Arguments[i].Type.Nullable) //Nullable.GetUnderlyingType(pis[i].ParameterType) != null)
args[i] = null; args[i] = null;
else else
@@ -1377,7 +1380,7 @@ partial class DistributedConnection
var (offset, length, args) = DataDeserializer.LimitedCountListParser(data, dataType.Offset, var (offset, length, args) = DataDeserializer.LimitedCountListParser(data, dataType.Offset,
dataType.ContentLength, Instance.Warehouse); dataType.ContentLength, Instance.Warehouse);
var resourceId = (uint)args[0]; var resourceId = Convert.ToUInt32(args[0]);
var index = (byte)args[1]; var index = (byte)args[1];
Instance.Warehouse.GetById(resourceId).Then((r) => Instance.Warehouse.GetById(resourceId).Then((r) =>
@@ -1436,7 +1439,7 @@ partial class DistributedConnection
var (offset, length, args) = DataDeserializer.LimitedCountListParser(data, dataType.Offset, var (offset, length, args) = DataDeserializer.LimitedCountListParser(data, dataType.Offset,
dataType.ContentLength, Instance.Warehouse); dataType.ContentLength, Instance.Warehouse);
var resourceId = (uint)args[0]; var resourceId = Convert.ToUInt32(args[0]);
var index = (byte)args[1]; var index = (byte)args[1];
Instance.Warehouse.GetById(resourceId).Then((r) => Instance.Warehouse.GetById(resourceId).Then((r) =>
@@ -1576,7 +1579,7 @@ partial class DistributedConnection
else else
{ {
// cast new value type to property type // cast new value type to property type
value = DC.CastConvert(value, pi.PropertyType); value = RuntimeCaster.Cast(value, pi.PropertyType);
} }
try try
@@ -1601,7 +1604,7 @@ partial class DistributedConnection
else else
{ {
// cast new value type to property type // cast new value type to property type
parsed = DC.CastConvert(parsed, pi.PropertyType); parsed = RuntimeCaster.Cast(parsed, pi.PropertyType);
} }
try try
@@ -1805,7 +1808,7 @@ partial class DistributedConnection
// ClassId, Age, Link, Hops, PropertyValue[] // ClassId, Age, Link, Hops, PropertyValue[]
var args = (object[])result; var args = (object[])result;
var classId = (UUID)args[0]; var classId = (UUID)args[0];
var age = (ulong)args[1]; var age = Convert.ToUInt64(args[1]);
var link = (string)args[2]; var link = (string)args[2];
var hops = (byte)args[3]; var hops = (byte)args[3];
var pvData = (byte[])args[4]; var pvData = (byte[])args[4];
@@ -1820,7 +1823,7 @@ partial class DistributedConnection
if (template?.DefinedType != null && template.IsWrapper) if (template?.DefinedType != null && template.IsWrapper)
dr = Activator.CreateInstance(template.DefinedType, this, id, (ulong)args[1], (string)args[2]) as DistributedResource; dr = Activator.CreateInstance(template.DefinedType, this, id, (ulong)args[1], (string)args[2]) as DistributedResource;
else else
dr = new DistributedResource(this, id, (ulong)args[1], (string)args[2]); dr = new DistributedResource(this, id, Convert.ToUInt64( args[1]), (string)args[2]);
} }
else else
{ {
@@ -2063,7 +2066,7 @@ partial class DistributedConnection
dataType.ContentLength, Instance.Warehouse); dataType.ContentLength, Instance.Warehouse);
var peerTime = (DateTime)args[0]; var peerTime = (DateTime)args[0];
var interval = (uint)args[1]; var interval = Convert.ToUInt32(args[1]);
uint jitter = 0; uint jitter = 0;

View File

@@ -184,7 +184,7 @@ public class Instance
if (at != null) if (at != null)
if (at.PropertyInfo.CanWrite) if (at.PropertyInfo.CanWrite)
at.PropertyInfo.SetValue(res, DC.CastConvert(kv.Value, at.PropertyInfo.PropertyType)); at.PropertyInfo.SetValue(res, RuntimeCaster.Cast(kv.Value, at.PropertyInfo.PropertyType));
} }
} }
@@ -363,7 +363,7 @@ public class Instance
{ {
loading = true; loading = true;
pt.PropertyInfo.SetValue(res, DC.CastConvert(value, pt.PropertyInfo.PropertyType)); pt.PropertyInfo.SetValue(res, RuntimeCaster.Cast(value, pt.PropertyInfo.PropertyType));
} }
catch (Exception ex) catch (Exception ex)
{ {