diff --git a/Initialize.Benchmarks/Benchmarks.cs b/Initialize.Benchmarks/Benchmarks.cs index 692ff9a..e74fd6a 100644 --- a/Initialize.Benchmarks/Benchmarks.cs +++ b/Initialize.Benchmarks/Benchmarks.cs @@ -20,7 +20,6 @@ namespace Initialize.Benchmarks; [HideColumns(Column.StdDev, Column.Median)] public class ChampionChallengerBenchmarks { - private static readonly RecyclableMemoryStreamManager manager = new(); private MapperA _autoMapper; private List _testObjects; @@ -28,9 +27,11 @@ public ChampionChallengerBenchmarks() { //Initialize mapper var config = new MapperConfiguration(cfg => - cfg.CreateMap().ForMember(x=> - x.PropString, - x=>x.MapFrom(x=>x.PropString))); + cfg.CreateMap() + // .ForMember(x=> + // x.PropString, + // x=>x.MapFrom(x=>x.PropString)) + ); _autoMapper = new MapperA(config); //Initialize mapper @@ -74,13 +75,13 @@ public void InitializeMapper() Mapper.Map(testObj, test2); } - [Benchmark(Description = "MapperNoDestObject")] - public void InitializeMapperNoVarPassed() - { - var test2 = new Test2(); + //[Benchmark(Description = "MapperNoDestObject")] + //public void InitializeMapperNoVarPassed() + //{ + // var test2 = new Test2(); - var result = Mapper.Map(testObj); - } + // var result = Mapper.Map(testObj); + //} [Benchmark(Description = "AutoMapper")] public void AutoMapper() { @@ -88,13 +89,13 @@ public void AutoMapper() _autoMapper.Map(testObj, test2); } - [Benchmark(Description = "AutoMappeNoDestObject")] - public void AutoMapper2() - { - var test2 = new Test2(); + //[Benchmark(Description = "AutoMappeNoDestObject")] + //public void AutoMapper2() + //{ + // var test2 = new Test2(); - var result = _autoMapper.Map(testObj); - } + // var result = _autoMapper.Map(testObj); + //} } diff --git a/Initialize.Benchmarks/ChampionChallengerEnumerable.cs b/Initialize.Benchmarks/ChampionChallengerEnumerable.cs new file mode 100644 index 0000000..3569349 --- /dev/null +++ b/Initialize.Benchmarks/ChampionChallengerEnumerable.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using AutoMapper; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; +using Microsoft.IO; + +namespace Initialize.Benchmarks; + +[SimpleJob(runStrategy: RunStrategy.Throughput, launchCount: 1, invocationCount: 1, iterationCount:10, + runtimeMoniker: RuntimeMoniker.Net70)] +[MemoryDiagnoser] +[HideColumns(Column.StdDev, Column.Median, Column.Error, Column.RatioSD)] +public class ChampionChallengerEnumerable +{ + private static readonly RecyclableMemoryStreamManager manager = new(); + private Mapper _autoMapper; + private List _testObjects; + private IEnumerable _testEnumerable; + + public ChampionChallengerEnumerable() + { + //Initialize mapper + var config = new MapperConfiguration(cfg => + cfg.CreateMap()); + _autoMapper = new Mapper(config); + + //Initialize mapper + Mapper.Map(testObj); + + _testObjects = Enumerable.Range(0, 10000).Select(r => new Test() { PropString = r.ToString(), Prop = r }).ToList(); + } + [Params(100_000, 1_000_000)] + public int Iterations { get; set; } + + [IterationSetup] + public void Setup() + { + _testEnumerable = Enumerable.Range(0, Iterations) + .Select(r => new Test() { PropString = r.ToString(), Prop = r }); + + _testObjects = _testEnumerable.ToList(); + } + + private Test testObj = new Test() + { + Prop = 1, + PropString = "a", + }; + [Benchmark(Description = "Mapper", Baseline = true)] + public void Mapper() + { + var test2 = new Test2(); + + var result = Mapper.MapEnumerable(_testObjects); + + Debug.Assert(result.Count() == _testObjects.Count); + } + + [Benchmark(Description = "AutoMapper")] + public void AutoMapper() + { + var test2 = new Test2(); + + var result = _autoMapper.Map>(_testObjects); + + Debug.Assert(result.Count() == _testObjects.Count); + } + [Benchmark(Description = "MapperEnumerable")] + public void MapperEnumerable() + { + var test2 = new Test2(); + + var result = Mapper.MapEnumerable(_testEnumerable); + + Debug.Assert(result.Count() == _testObjects.Count); + } + + [Benchmark(Description = "AutoMapperEnumerable")] + public void AutoMapperEnumerable() + { + var test2 = new Test2(); + + var result = _autoMapper.Map>(_testEnumerable); + + Debug.Assert(result.Count() == _testObjects.Count); + } +} \ No newline at end of file diff --git a/Initialize.Benchmarks/CollectionBenchmarks.cs b/Initialize.Benchmarks/ChampionChallengerList.cs similarity index 55% rename from Initialize.Benchmarks/CollectionBenchmarks.cs rename to Initialize.Benchmarks/ChampionChallengerList.cs index 0324be0..7c39037 100644 --- a/Initialize.Benchmarks/CollectionBenchmarks.cs +++ b/Initialize.Benchmarks/ChampionChallengerList.cs @@ -5,7 +5,6 @@ using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Order; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using MapperA = AutoMapper.Mapper; using AutoMapper; @@ -42,7 +41,7 @@ public ChampionChallengerList() _testObjects = Enumerable.Range(0, 10000).Select(r => new Test() { PropString = r.ToString(), Prop = r }).ToList(); } - [Params(100_000, 1_000_000)] + [Params(10000)]//100_000, 1_000_000)] public int Iterations { get; set; } [IterationSetup] @@ -79,66 +78,4 @@ public void Setup() // Debug.Assert(result.Count == _testObjects.ToList().Count); //} -} - -[SimpleJob(runStrategy: RunStrategy.Throughput, launchCount: 1, invocationCount: 1, iterationCount:10, - runtimeMoniker: RuntimeMoniker.Net70)] -[MemoryDiagnoser] -[HideColumns(Column.StdDev, Column.Median, Column.Error, Column.RatioSD)] -public class ChampionChallengerEnumerable -{ - private static readonly RecyclableMemoryStreamManager manager = new(); - private MapperA _autoMapper; - private List _testObjects; - private IEnumerable _testEnumerable; - - public ChampionChallengerEnumerable() - { - //Initialize mapper - var config = new MapperConfiguration(cfg => - cfg.CreateMap()); - _autoMapper = new MapperA(config); - - //Initialize mapper - Mapper.Map(testObj); - - _testObjects = Enumerable.Range(0, 10000).Select(r => new Test() { PropString = r.ToString(), Prop = r }).ToList(); - } - [Params(100_000, 1_000_000)] - public int Iterations { get; set; } - - [IterationSetup] - public void Setup() - { - _testEnumerable = Enumerable.Range(0, Iterations) - .Select(r => new Test() { PropString = r.ToString(), Prop = r }); - - _testObjects = _testEnumerable.ToList(); - } - - private Test testObj = new Test() - { - Prop = 1, - PropString = "a", - }; - - //[Benchmark(Description = "Mapper", Baseline = true)] - //public void MapperEnumerable() - //{ - // var test2 = new Test2(); - - // var result = Mapper.Map(_testEnumerable); - - // Debug.Assert(result.Count() == _testObjects.Count); - //} - - //[Benchmark(Description = "AutoMapper")] - //public void AutoMapperEnumerable() - //{ - // var test2 = new Test2(); - - // var result = _autoMapper.Map>(_testEnumerable); - - // Debug.Assert(result.Count() == _testObjects.ToList().Count); - //} } \ No newline at end of file diff --git a/Initialize.Benchmarks/ExperimentBenchmarks.cs b/Initialize.Benchmarks/ExperimentBenchmarks.cs new file mode 100644 index 0000000..a5efd45 --- /dev/null +++ b/Initialize.Benchmarks/ExperimentBenchmarks.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using MapperA = AutoMapper.Mapper; +using AutoMapper; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Order; +using Microsoft.IO; +using Microsoft.Diagnostics.Runtime.Utilities; +using System.Buffers; + +namespace Initialize.Benchmarks; + +[SimpleJob(runStrategy: RunStrategy.Throughput, launchCount: 1, invocationCount: 1, + runtimeMoniker: RuntimeMoniker.Net70)] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +[MemoryDiagnoser] +[HideColumns(Column.StdDev, Column.Median)] +public class ExperimentBenchmarks +{ + private static readonly RecyclableMemoryStreamManager manager = new(); + private MapperA _autoMapper; + private IEnumerable _testEnumerable; + + public ExperimentBenchmarks() + { + //Initialize mapper + var config = new MapperConfiguration(cfg => + cfg.CreateMap().ForMember(x=> + x.PropString, + x=>x.MapFrom(x=>x.PropString))); + _autoMapper = new MapperA(config); + + //Initialize mapper + Mapper.Map(testObj); + + } + [Params(100_000, 1_000_000)] + public int Iterations { get; set; } + + [IterationSetup] + public void Setup() + { + _testEnumerable = Enumerable.Range(0, Iterations) + .Select(r => new Test() { PropString = r.ToString(), Prop = r }); + + } + private Test testObj = new Test() + { + Prop = 1, + PropString = "a", + }; + + //[Benchmark(Description = "InitializeMapperColdStart")] + //public void InitializeMapperColdStart() + //{ + // var test2 = new Test2(); + + // Mapper.Map(testObj, test2); + //} + + //[Benchmark(Description = "AutoMapperColdStart")] + //public void AutoMapperUnInitialized() + //{ + // //Initialize the mapper + // var config = new MapperConfiguration(cfg => + // cfg.CreateMap()); + + // var mapper = new MapperA(config); + + // var test2 = new Test2(); + + // mapper.Map(testObj, test2); + //} + + [Benchmark(Baseline = true)] + public void MapArray() + { + var test2 = new Test2(); + + var result = Mapper.MapArray(_testEnumerable); + + Debug.Assert(result.Count() == _testEnumerable.Count()); + } + //[Benchmark] + //public void Span() + //{ + // var test2 = new Test2(); + // int cnt = 0, current = 0; + // var span = _testObjects.ToArray().AsSpan(); + // Span spanTo = new Test2[span.Length]; + + // for (int i = 0; i < cnt; i++) + // spanTo[i] = Mapper.Map(span[i]); + + + // Debug.Assert(spanTo.ToArray().Count() == _testObjects.Count()); + //} + [Benchmark] + public void MapInline() + { + var test2 = new Test2(); + + var result = Mapper.MapArrayInline(_testEnumerable); + + Debug.Assert(result.Count() == _testEnumerable.Count()); + } + [Benchmark] + public void MapOptimized() + { + var test2 = new Test2(); + + var result = Mapper.MapArrayOpt(_testEnumerable); + + Debug.Assert(result.Count() == _testEnumerable.Count()); + } + + [Benchmark] + public void ArrayPool() + { + var test2 = new Test2(); + int cnt = 0, current = 0; + var span = _testEnumerable.ToArray(); + Test2[] spanTo = ArrayPool.Shared.Rent(span.Length); + + for (int i = 0; i < cnt; i++) + spanTo[i] = Mapper.Map(span[i]); + + ArrayPool.Shared.Return(spanTo); + Debug.Assert(spanTo.Count() == _testEnumerable.Count()); + } +} \ No newline at end of file diff --git a/Initialize.Benchmarks/ListExperimentBenchmarks.cs b/Initialize.Benchmarks/ListExperimentBenchmarks.cs new file mode 100644 index 0000000..8d5d2ea --- /dev/null +++ b/Initialize.Benchmarks/ListExperimentBenchmarks.cs @@ -0,0 +1,133 @@ +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using AutoMapper; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Order; +using Microsoft.IO; + +namespace Initialize.Benchmarks; + +[SimpleJob(runStrategy: RunStrategy.Throughput, launchCount: 1, invocationCount: 1, + runtimeMoniker: RuntimeMoniker.Net70)] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +[MemoryDiagnoser] +[HideColumns(Column.StdDev, Column.Median)] +public class ListExperimentBenchmarks +{ + private static readonly RecyclableMemoryStreamManager manager = new(); + private Mapper _autoMapper; + private List _testEnumerable; + + public ListExperimentBenchmarks() + { + //Initialize mapper + var config = new MapperConfiguration(cfg => + cfg.CreateMap().ForMember(x=> + x.PropString, + x=>x.MapFrom(x=>x.PropString))); + _autoMapper = new Mapper(config); + + //Initialize mapper + Mapper.Map(testObj); + + } + [Params(100_000, 1_000_000)] + public int Iterations { get; set; } + + [IterationSetup] + public void Setup() + { + _testEnumerable = Enumerable.Range(0, Iterations) + .Select(r => new Test() { PropString = r.ToString(), Prop = r }).ToList(); + + } + private Test testObj = new Test() + { + Prop = 1, + PropString = "a", + }; + + //[Benchmark(Description = "InitializeMapperColdStart")] + //public void InitializeMapperColdStart() + //{ + // var test2 = new Test2(); + + // Mapper.Map(testObj, test2); + //} + + //[Benchmark(Description = "AutoMapperColdStart")] + //public void AutoMapperUnInitialized() + //{ + // //Initialize the mapper + // var config = new MapperConfiguration(cfg => + // cfg.CreateMap()); + + // var mapper = new MapperA(config); + + // var test2 = new Test2(); + + // mapper.Map(testObj, test2); + //} + + [Benchmark(Baseline = true)] + public void MapArray() + { + var test2 = new Test2(); + + var result = Mapper.MapArray(_testEnumerable); + + Debug.Assert(result.Count() == _testEnumerable.Count()); + } + //[Benchmark] + //public void Span() + //{ + // var test2 = new Test2(); + // int cnt = 0, current = 0; + // var span = _testObjects.ToArray().AsSpan(); + // Span spanTo = new Test2[span.Length]; + + // for (int i = 0; i < cnt; i++) + // spanTo[i] = Mapper.Map(span[i]); + + + // Debug.Assert(spanTo.ToArray().Count() == _testObjects.Count()); + //} + [Benchmark] + public void MapInline() + { + var test2 = new Test2(); + + var result = Mapper.MapArrayInline(_testEnumerable); + + Debug.Assert(result.Count() == _testEnumerable.Count()); + } + [Benchmark] + public void MapOptimized() + { + var test2 = new Test2(); + + var result = Mapper.MapArrayOpt(_testEnumerable); + + Debug.Assert(result.Count() == _testEnumerable.Count()); + } + + [Benchmark] + public void ArrayPool() + { + var test2 = new Test2(); + int cnt = 0, current = 0; + var span = _testEnumerable.ToArray(); + Test2[] spanTo = ArrayPool.Shared.Rent(span.Length); + + for (int i = 0; i < cnt; i++) + spanTo[i] = Mapper.Map(span[i]); + + ArrayPool.Shared.Return(spanTo); + Debug.Assert(spanTo.Count() == _testEnumerable.Count()); + } +} \ No newline at end of file diff --git a/Initialize.Benchmarks/Program.cs b/Initialize.Benchmarks/Program.cs index e3d9ee9..13d5a2e 100644 --- a/Initialize.Benchmarks/Program.cs +++ b/Initialize.Benchmarks/Program.cs @@ -7,7 +7,8 @@ namespace Initialize.Benchmarks; class Program { private static IConfig BenchConfig => DefaultConfig.Instance.AddJob(Job.Default.AsDefault() - .WithRuntime(CoreRuntime.Core70) + + .WithRuntime(CoreRuntime.Core60) .WithJit(Jit.RyuJit) .WithArguments(new[] { new MsBuildArgument("/p:Optimize=true") })); static void Main(string[] args) diff --git a/Initialize.Tests/Initialize.Tests.csproj b/Initialize.Tests/Initialize.Tests.csproj index 83be0ea..6be59a0 100644 --- a/Initialize.Tests/Initialize.Tests.csproj +++ b/Initialize.Tests/Initialize.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/Initialize.Tests/SetupTrace.cs b/Initialize.Tests/SetupTrace.cs new file mode 100644 index 0000000..ec47137 --- /dev/null +++ b/Initialize.Tests/SetupTrace.cs @@ -0,0 +1,19 @@ +using System.Diagnostics; + +namespace Initialize.Tests; + +[SetUpFixture] +public class SetupTrace +{ + [OneTimeSetUp] + public void StartTest() + { + Trace.Listeners.Add(new ConsoleTraceListener()); + } + + [OneTimeTearDown] + public void EndTest() + { + Trace.Flush(); + } +} \ No newline at end of file diff --git a/Initialize.Tests/TestExtensions.cs b/Initialize.Tests/TestExtensions.cs new file mode 100644 index 0000000..c9454d5 --- /dev/null +++ b/Initialize.Tests/TestExtensions.cs @@ -0,0 +1,53 @@ +using System.ComponentModel.DataAnnotations; +using Shouldly; + +namespace Initialize.Tests; + +public static class TestExtensions +{ + // Asserts that all required properties (via the 'Required' attribute) +// be non null using Shouldly +// Optionally include all properties if desired + private static void AssertPropertiesAreNonNull(this T obj, bool onlyRequiredProperties = true) + { + if (obj == null) + { + return; + } + + var objType = obj.GetType(); + + // Get either only required properties or ALL properties + var properties = onlyRequiredProperties ? objType.GetProperties() + .Where(x => x.GetCustomAttributes(false).OfType().Any()) : + objType.GetProperties(); + + foreach (var property in properties) + { + var propValue = property.GetValue(obj, null); + var elems = propValue as IList; + + // Another layer + if (elems != null) + { + foreach (var item in elems) + { + AssertPropertiesAreNonNull(item, onlyRequiredProperties); + } + } + else + { + if (property.PropertyType.Assembly == objType.Assembly) + { + AssertPropertiesAreNonNull(propValue, onlyRequiredProperties); + } + // Reached the end of the tree + else + { + // Use Shouldly to assert that the propValue should not be null + propValue.ShouldNotBeNull(); + } + } + } + } +} \ No newline at end of file diff --git a/Initialize.Tests/UnitTest1.cs b/Initialize.Tests/UnitTest1.cs index 249ab1a..4f840be 100644 --- a/Initialize.Tests/UnitTest1.cs +++ b/Initialize.Tests/UnitTest1.cs @@ -2,6 +2,7 @@ using System.Buffers.Text; using System.Collections; using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.Linq.Expressions; using System.Reflection; using System.Security.Cryptography.X509Certificates; @@ -51,7 +52,7 @@ public void Setup() //[Test] //public void Should_equal_count_when_list_is_less_than_batch_size() //{ - // var items = Enumerable.Range(0, 1).Select(i => new Test { Prop = i, PropString = i.ToString() }).ToList(); + // var items = Enumerable.Range(0, 1).Select(i => new Test { Prop = i, PropString = i.ToUtf8String() }).ToList(); // var result = Mapper.Map(items); @@ -61,7 +62,7 @@ public void Setup() //[Test] //public void Should_equal_count_when_list_is_greater_than_batch_size() //{ - // var items = Enumerable.Range(0, 101).Select(i => new Test { Prop = i, PropString = i.ToString() }).ToList(); + // var items = Enumerable.Range(0, 101).Select(i => new Test { Prop = i, PropString = i.ToUtf8String() }).ToList(); // var result = Mapper.Map(items); @@ -70,7 +71,7 @@ public void Setup() //[Test] //public void Should_equal_count_when_list_is_larg() //{ - // var items = Enumerable.Range(0, 100000).Select(i => new Test { Prop = i, PropString = i.ToString() }).ToList(); + // var items = Enumerable.Range(0, 100000).Select(i => new Test { Prop = i, PropString = i.ToUtf8String() }).ToList(); // var result = Mapper.Map2(items); @@ -79,19 +80,20 @@ public void Setup() [Test] public void Should_equal_with_manual_config() { - - MapperConfiguration + MapperConfiguration .Configure(x => x .For(tLeft => tLeft.PropString, tRight => tRight.PropString) .For(tLeft => tLeft.FieldNullable, tRight => tRight.FieldNullable) + .For(tLeft => tLeft.FieldNullable, tRight => tRight.FieldNullable) ); var obj = new Test(); + var objTo = new Test2(); obj.PropString = "1"; - var result = Mapper.Map(obj); + Mapper.Map(obj, objTo); - Assert.That(result.PropString, Is.EqualTo(obj.PropString)); + Assert.That(objTo.PropString, Is.EqualTo(obj.PropString)); } [Test] @@ -123,7 +125,7 @@ public void Should_equal_count_when_collections_are_not_null() } [Test] - public void Should_equal_with_manual_config_with_indexer() + public void Should_equal_after_parse_utf8_with_manual_config() { Memory[] bytes = { Encoding.UTF8.GetBytes(DateTime.Now.ToString()), @@ -153,7 +155,7 @@ public void Should_equal_with_manual_config_with_indexer() // equality values var equalToDt = DateTime.Parse(Encoding.UTF8.GetString(bytes[0].Span)); - var equalToInt = int.Parse(Encoding.UTF8.GetString("8"u8.ToArray())); + var equalToInt = int.Parse(Encoding.UTF8.GetString(bytes[1].Span)); var equalToDouble = double.Parse(Encoding.UTF8.GetString(bytes[3].Span)); var equalToString = Encoding.UTF8.GetString(bytes[4].Span); @@ -164,7 +166,7 @@ public void Should_equal_with_manual_config_with_indexer() Assert.That(result.PropString, Is.EqualTo(equalToString)); } [Test] - public void Should_equal_with_manual_config_with_indexer_from_ParseUT8() + public void Should_equal_after_ParseUTF8_from_MemoryByte() { var dtBytes = Encoding.UTF8.GetBytes(DateTime.Now.ToString()); Memory[] bytes = { dtBytes, "8"u8.ToArray(), ""u8.ToArray(), "1.0"u8.ToArray(), "hello"u8.ToArray() }; @@ -174,16 +176,16 @@ public void Should_equal_with_manual_config_with_indexer_from_ParseUT8() .Configure(x => x // index 0 .ParseFor(t => t.PropDateTimeNullable, - parse => parse.MemoryByteToDateTimeNullable) + parse => parse.ToDateTimeNullable) // index 1 .ParseFor(t => t.Prop, - parse => parse.MemoryByteToInt) + parse => parse.ToInt) // index 3 - skipping index 2 .ParseFor(t => t.PropDouble, - parse => parse.MemoryByteToDoubleNullable, ++indexOffset) + parse => parse.ToDoubleNullable, ++indexOffset) // index 4 .ParseFor(t => t.PropString, - parse => parse.MemoryByteToStringNullIfEmpty, indexOffset) + parse => parse.ToStringNullIfEmpty, indexOffset) ); var result = Mapper[], ParseTest>.Map(bytes); @@ -201,6 +203,43 @@ public void Should_equal_with_manual_config_with_indexer_from_ParseUT8() Assert.That(result.PropString, Is.EqualTo(equalToString)); } [Test] + public void Should_equal_after_ParseUTF8_from_ByteArray() + { + var dtBytes = Encoding.UTF8.GetBytes(DateTime.Now.ToString()); + byte[][] bytes = { dtBytes, "8"u8.ToArray(), ""u8.ToArray(), "1.0"u8.ToArray(), "hello"u8.ToArray() }; + + int indexOffset = 0; + MapperConfiguration + .Configure(x => x + // index 0 + .ParseFor(t => t.PropDateTimeNullable, + parse => parse.ToDateTimeNullable) + // index 1 + .ParseFor(t => t.Prop, + parse => parse.ToInt) + // index 3 - skipping index 2 + .ParseFor(t => t.PropDouble, + parse => parse.ToDoubleNullable, ++indexOffset) + // index 4 + .ParseFor(t => t.PropString, + parse => parse.ToStringNullIfEmpty, indexOffset) + ); + + var result = Mapper.Map(bytes); + + // equality values + var equalToDt = DateTime.Parse(Encoding.UTF8.GetString(bytes[0])); + var equalToInt = int.Parse(Encoding.UTF8.GetString(bytes[1])); + var equalToDouble = double.Parse(Encoding.UTF8.GetString(bytes[3])); + var equalToString = Encoding.UTF8.GetString(bytes[4]); + + // equality tests + Assert.That(result.PropDateTimeNullable, Is.EqualTo(equalToDt)); + Assert.That(result.Prop, Is.EqualTo(equalToInt)); + Assert.That(result.PropDouble, Is.EqualTo(equalToDouble)); + Assert.That(result.PropString, Is.EqualTo(equalToString)); + } + [Test] public void GenerateDtoWithoutNotifyPropertyChanged() { var text = CSharpGeneratorFactory.GenerateDto("Tester"); @@ -256,7 +295,6 @@ public class Test2 public class ParseTest { public int Prop { get; set; } - public int? PropNullable { get; set; } public string PropString { get; set; } public DateTime? PropDateTimeNullable { get; set; } public double? PropDouble { get; set; } diff --git a/Initialize/Initialize.csproj b/Initialize/Initialize.csproj index c0dc9ff..38543fd 100644 --- a/Initialize/Initialize.csproj +++ b/Initialize/Initialize.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net7.0 enable enable true @@ -20,14 +20,14 @@ true true https://github.com/adam-dot-cohen/ResizableSpanWriter - 0.2.7 + 0.2.8 README.md git AnyCPU favicon.png latest true - High-Performance auto DTO initialier and property mapper / deep copier + Initializer - 200% faster than AutoMapper 6 @@ -53,6 +53,7 @@ + diff --git a/Initialize/Mapper.cs b/Initialize/Mapper.cs index ad3f8aa..98a1f1e 100644 --- a/Initialize/Mapper.cs +++ b/Initialize/Mapper.cs @@ -1,116 +1,159 @@ using System.Buffers; -using System.Collections.Immutable; using System.Diagnostics; -using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Loader; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace Initialize; - +/// +/// Maps from to . +/// +/// +/// public static class Mapper { private static string _proxyTypeName; private static Assembly _generatedAssembly; private static AssemblyLoadContext _context; private static Type _proxyType; - private delegate void MapperDelegate(TFrom from, TTo to); - private static MapperDelegate CacheDel; + private static Action _mapFromTo; + private static Func _mapFrom; - static Mapper() - => Compile(); + static Mapper() => Compile(); /// - /// Initiaze properties on to non-null values. + /// Maps caller supplied to caller supplied object. /// - /// object or value type to be serialized - /// + /// Source object + /// Destination object [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Map(TFrom objFrom, TTo objTo) - => CacheDel(objFrom, objTo); + public static void Map(TFrom objFrom, TTo objTo) => _mapFromTo(objFrom, objTo); /// - /// Initiaze properties on to non-null values. + /// Maps caller supplied to returned object. /// - /// object or value type to be serialized - /// + /// Source object + /// A object with hydrated properties. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TTo Map(TFrom objFrom) - { - var objTo = Activator.CreateInstance(); - CacheDel(objFrom, objTo); - return objTo; - } + public static TTo Map(TFrom objFrom) => _mapFrom(objFrom); - /// - /// Initiaze properties on to non-null values. - /// - /// object or value type to be serialized - /// //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static KeyValuePair Map(KeyValuePair objFrom) + //public static IEnumerable MapEnumerable(List objFrom) //{ - // KeyValuePair kvPair = new (); - // Mapper - // return objTo; - //} + // var span = objFrom.ToArray(); + // TTo[] spanTo = ArrayPool.Shared.Rent(objFrom.Count); + // var cnt = objFrom.Count; + // var current = 0; - /// - /// Initiaze properties on to non-null values. - /// - /// object or value type to be serialized - /// + // foreach (var item in CollectionsMarshal.AsSpan(objFrom)) + // spanTo[current++] = Map(item); + // //for (int i = 0; i < cnt; i++) + // // spanTo[i] = Map(span[i]); + + // ArrayPool.Shared.Return(spanTo); + + // return spanTo; + //} //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static List Map(IList objFrom) + //public static IEnumerable MapEnumerable(IList objFrom) //{ - // var pool = ArrayPool.Shared; - // int cnt = 0, cntTotal = 0, size = 100; - // var list = new List(objFrom.Count); - // var array = pool.Rent(size); + // var span = objFrom.ToArray(); + // TTo[] spanTo = new TTo[span.Length]; + // var cnt = span.Length; - // while (cntTotal < objFrom.Count) - // { - // while (cnt < size) - // { - // var objTo = new TTo(); - // CacheDel(objFrom[cnt], objTo); - - // array[cnt] = objTo; - // cnt++; - // cntTotal++; - // } - // cnt = 0; - // list.AddRange(array); - // pool.Return(array); - // array = pool.Rent(size); - // } + // for (int i = 0; i < cnt; i++) + // spanTo[i] = Map(span[i]); + + // //ArrayPool.Shared.Return(spanTo); - // return list; + // return spanTo; //} - /// - ///// Initiaze properties on to non-null values. - ///// - ///// object or value type to be serialized - ///// //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static IEnumerable Map(IEnumerable objFrom) + //public static IEnumerable MapEnumerable(TFrom[] objFrom) //{ - // int cnt = 0, cntTotal = 0, size = 100; - // Span spFrom = objFrom.ToArray(); - // var list = new TTo[spFrom.Length]; - // Span spTo = list; + // var span = objFrom; + // TTo[] spanTo = new TTo[span.Length]; + // var cnt = span.Length; - // while (cnt < spFrom.Length) - // { - // spTo[cnt] = Map(spFrom[cnt]); - // cnt++; - // } + // for (int i = 0; i < cnt; i++) + // spanTo[i] = Map(span[i]); + + // //ArrayPool.Shared.Return(spanTo); - // return list;// Map(objFrom.ToList()); + // return spanTo; //} + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable MapEnumerable(IEnumerable objFrom) + { + //if (objFrom is TFrom[] array) + //{ + // return MapEnumerable(array); + //} + + //if (objFrom is IList iList) + //{ + // if (iList is List list) + // { + // return MapEnumerable(list); + // } + // return MapEnumerable(iList); + //} + + var span = objFrom.ToArray().AsSpan(); + var spanTo = ArrayPool.Shared.Rent(span.Length); + var cnt = span.Length; + + for (int i = 0; i < cnt; i++) + spanTo[i] = Map(span[i]); + + ArrayPool.Shared.Return(spanTo); + + return spanTo; + } + + public static IEnumerable MapArray(IEnumerable objFrom) + { + int cnt = 0, current = 0; + var span = objFrom.ToArray().AsSpan(); + Span spanTo = new TTo[span.Length];// ArrayPool.Shared.Rent(span.Length); + + for (int i = 0; i < cnt; i++) + { + spanTo[i] = Map(span[i]); + } + + //ArrayPool.Shared.Return(spanTo); + + return spanTo.ToArray();// Map(objFrom.ToList()); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable MapArrayInline(IEnumerable objFrom) + { + int cnt = 0, current = 0; + var span = objFrom.ToArray(); + TTo[] spanTo = ArrayPool.Shared.Rent(span.Length); + for (int i = 0; i < cnt; i++) + spanTo[i] = Map(span[i]); + + ArrayPool.Shared.Return(spanTo); + + return spanTo; + } + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static IEnumerable MapArrayOpt(IEnumerable objFrom) + { + int cnt = 0, current = 0; + var span = objFrom.ToArray().AsSpan(); + TTo[] spanTo = ArrayPool.Shared.Rent(span.Length); + for (int i = 0; i < cnt; i++) + spanTo[i] = Map(span[i]); + + ArrayPool.Shared.Return(spanTo); + + return spanTo;// Map(objFrom.ToList()); + } ///// ///// Initiaze properties on to non-null values. ///// @@ -142,7 +185,7 @@ public static TTo Map(TFrom objFrom) // // array = pool.Rent(size); // // } // // var objTo = new TTo(); - // // CacheDel(from[i], objTo); + // // MapFromTo(from[i], objTo); // // array[cnt] = objTo; // // cnt++; @@ -159,8 +202,19 @@ public static TTo Map(TFrom objFrom) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void BuildDelegates() { - var infos = _proxyType.GetMethod(nameof(Map)); - CacheDel = infos.CreateDelegate(); + var methodInfo = _proxyType.GetMethod( + name: nameof(Map), + bindingAttr: BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.Public, + types: new []{ typeof(TFrom), typeof(TTo)}); + + _mapFromTo = methodInfo.CreateDelegate>(); + + methodInfo = _proxyType.GetMethod( + name: nameof(Map), + bindingAttr: BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.Public, + types: new []{ typeof(TFrom)}); + + _mapFrom = methodInfo.CreateDelegate>(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -211,7 +265,7 @@ private static void Compile() [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Emit(CSharpCompilation _compilation) { - if (CacheDel != null) return; + if (_mapFromTo != null) return; using (var ms = new MemoryStream()) { var result = _compilation.Emit(ms); diff --git a/Initialize/MapperDefaultTemplate.cs b/Initialize/MapperDefaultTemplate.cs index a6191ed..864eb25 100644 --- a/Initialize/MapperDefaultTemplate.cs +++ b/Initialize/MapperDefaultTemplate.cs @@ -22,11 +22,13 @@ public class MapperDefaultTemplate : MapperTemplateBase public const string SyntaxComplexAddBody = "Mapper<{1}, {1}>.Map({0})"; - protected override void GenerateBody(StringBuilder syntaxBuilder) + protected override void GenerateBody(out StringBuilder syntaxBuilder) { var type = typeof(TFrom); var typeTo = typeof(TTo); + syntaxBuilder = new StringBuilder(); + var propsFrom = type .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.SetProperty).ToDictionary(x => x.Name); @@ -44,10 +46,10 @@ protected override void GenerateBody(StringBuilder syntaxBuilder) if (ptype.IsValueType || ptype == typeof(string) || ptype is { IsArray: true, UnderlyingSystemType.IsValueType: true }) - syntaxBuilder.Append(string.Format(Syntax, propTo.Name, propFrom.Name)); + syntaxBuilder.Append(string.Format(Syntax, propTo.Name, propFrom.Name).AsSpan()); else if (!ptype.IsAssignableTo(typeof(IEnumerable))) syntaxBuilder.Append(string.Format(SyntaxComplex, propTo.Name, propFrom.Name, - GetTypeSyntax(ptype))); + GetTypeSyntax(ptype)).AsSpan()); else if (ptype.IsAssignableTo(typeof(IDictionary))) { Type[] types = ptype.GetGenericArguments(); @@ -61,8 +63,7 @@ protected override void GenerateBody(StringBuilder syntaxBuilder) ? SyntaxDictionaryValue : string.Format(SyntaxComplexAddBody, SyntaxDictionaryValue, GetTypeSyntax(valueType)); - syntaxBuilder.Append(string.Format(SyntaxDictionary, propTo.Name, propFrom.Name, keySyntax, - valueSyntax)); + syntaxBuilder.Append(string.Format(SyntaxDictionary, propTo.Name, propFrom.Name, keySyntax, valueSyntax).AsSpan()); } else if ((propFrom.PropertyType.IsAssignableTo(typeof(ICollection)) || ptype.GetGenericTypeDefinition().IsAssignableTo((typeof(HashSet<>)))) && @@ -77,13 +78,13 @@ protected override void GenerateBody(StringBuilder syntaxBuilder) GetTypeSyntax(valueType)); var str = string.Format(SyntaxComplexCollection, propTo.Name, propFrom.Name, valueSyntax); - syntaxBuilder.Append(str); + syntaxBuilder.Append(str.AsSpan()); } else if (propFrom.PropertyType.IsArray) { var str = string.Format(SyntaxComplexArray, propTo.Name, propFrom.Name, GetTypeSyntax(ptype).Replace("[ ]", string.Empty).Replace("[]", string.Empty)); - syntaxBuilder.Append(str); + syntaxBuilder.Append(str.AsSpan()); } } } diff --git a/Initialize/MapperManualTemplate.cs b/Initialize/MapperManualTemplate.cs index eb713b8..8b7507c 100644 --- a/Initialize/MapperManualTemplate.cs +++ b/Initialize/MapperManualTemplate.cs @@ -6,28 +6,26 @@ namespace Initialize; public class MapperManualTemplate : MapperTemplateBase { - const string SyntaxPropertyBind = "objTo.{0} = objFrom.{1};"; - const string SyntaxPropertyAssign = "objTo.{0} = {1};"; - const string SyntaxFrom = "objFrom"; - - StringBuilder _maps = new StringBuilder(); + private static readonly string SyntaxPropertyBind = $"{SyntaxVarTo}.{{0}} = {SyntaxVarFrom}.{{1}};"; + private static readonly string SyntaxPropertyAssign = $"{SyntaxVarTo}.{{0}} = {{1}};"; + private readonly StringBuilder _syntaxBuilder = new (); public MapperManualTemplate For(Expression> propTo, Expression> propFrom) { var fromName = GetPropertyName(propFrom); var toName = GetPropertyName(propTo); - _maps.AppendFormat(SyntaxPropertyBind , toName, fromName); + _syntaxBuilder.AppendFormat(SyntaxPropertyBind , toName, fromName); return this; } private static int index = 0; - public MapperManualTemplate AddMap(Expression> propTo, string rightSideOfAssignment) + public MapperManualTemplate For(Expression> propTo, string rightSideOfAssignment) { var toName = GetPropertyName(propTo); - _maps.AppendFormat(SyntaxPropertyAssign, toName, rightSideOfAssignment); + _syntaxBuilder.AppendFormat(SyntaxPropertyAssign, toName, rightSideOfAssignment); return this; } @@ -38,9 +36,9 @@ public MapperManualTemplate For(Expression ParseFor(Expression ParseFor(Expression syntaxBuilder.Append(_maps.ToString()); + protected override void GenerateBody(out StringBuilder syntaxBuilder) + => syntaxBuilder =_syntaxBuilder; } \ No newline at end of file diff --git a/Initialize/MapperTemplateBase.cs b/Initialize/MapperTemplateBase.cs index cfa436a..953df71 100644 --- a/Initialize/MapperTemplateBase.cs +++ b/Initialize/MapperTemplateBase.cs @@ -23,6 +23,8 @@ public abstract class MapperTemplateBase : IMapperTemplate(Expression> propertyExpression) => (propertyExpression.Body as MemberExpression).Member.Name; diff --git a/Initialize/ParseExtensions.cs b/Initialize/ParseExtensions.cs index 73935a1..4bc5578 100644 --- a/Initialize/ParseExtensions.cs +++ b/Initialize/ParseExtensions.cs @@ -10,24 +10,136 @@ namespace Initialize; public class ParseUTF8 { - public string MemoryByteToString(int index) => $"{{0}} [{index}].Span.ToString()"; - public string MemoryByteToStringNullIfEmpty(int index) => $"{{0}} [{index}].Span.ToStringNullIfEmpty()"; - public string MemoryByteToIntNullable(int index) => $"{{0}} [{index}].Span.ToNullableInt()"; - public string MemoryByteToInt(int index) => $"{{0}} [{index}].Span.ToInt()"; - public string MemoryByteToLongNullable(int index) => $"{{0}} [{index}].Span.ToNullableLong()"; - public string MemoryByteToLong(int index) => $"{{0}} [{index}].Span.ToLong()"; - public string MemoryByteToDoubleNullable(int index) => $"{{0}} [{index}].Span.ToNullableDouble()"; - public string MemoryByteToDouble(int index) => $"{{0}} [{index}].Span.ToDouble()"; - public string MemoryByteToDateTimeNullable(int index) => $"{{0}} [{index}].Span.ToNullableDateTime()"; - public string MemoryByteToDateTimeNullable(int index, string parseFormat) => $"{{0}} [{index}].Span.ToNullableDateTime({parseFormat})"; - public string MemoryByteToDateTime(int index) => $"{{0}} [{index}].Span.ToDateTime()"; - public string MemoryByteToTimeSpanNullable(int index) => $"{{0}} [{index}].Span.ToNullableTimeSpan()"; - public string MemoryByteToTimeSpan(int index) => $"{{0}} [{index}].Span.ToTimeSpan()"; + public string ToString(int index) => $"{{0}} [{index}].ToUtf8String()"; + public string ToStringNullIfEmpty(int index) => $"{{0}} [{index}].ToStringNullIfEmpty()"; + public string ToIntNullable(int index) => $"{{0}} [{index}].ToNullableInt()"; + public string ToInt(int index) => $"{{0}} [{index}].ToInt()"; + public string ToLongNullable(int index) => $"{{0}} [{index}].ToNullableLong()"; + public string ToLong(int index) => $"{{0}} [{index}].ToLong()"; + public string ToDoubleNullable(int index) => $"{{0}} [{index}].ToNullableDouble()"; + public string ToDouble(int index) => $"{{0}} [{index}].ToDouble()"; + public string ToDateTimeNullable(int index) => $"{{0}} [{index}].ToNullableDateTime()"; + public string ToDateTimeNullable(int index, string parseFormat) => $"{{0}} [{index}].ToNullableDateTime({parseFormat})"; + public string ToDateTime(int index) => $"{{0}} [{index}].ToDateTime()"; + public string ToTimeSpanNullable(int index) => $"{{0}} [{index}].ToNullableTimeSpan()"; + public string ToTimeSpan(int index) => $"{{0}} [{index}].ToTimeSpan()"; } -public static class ParseExtensions +public static partial class ParseExtensions { - public static string ToString(this Span span) + public static string ToUtf8String(this Memory span) + { + return Encoding.UTF8.GetString(span.Span); + } + + public static string ToStringNullIfEmpty(this Memory span) + { + if (span.Length > 0) + return Encoding.UTF8.GetString(span.Span); + return null; + } + + public static double ToDouble(this Memory span) + { + if (Utf8Parser.TryParse(span.Span, out double value, out int bytesConsumed)) + return value; + return default; + } + + public static double? ToNullableDouble(this Memory span) + { + if (Utf8Parser.TryParse(span.Span, out double value, out int bytesConsumed)) + return value; + return null; + } + + public static int ToInt(this Memory span) + { + if (Utf8Parser.TryParse(span.Span, out int value, out int bytesConsumed)) + return value; + return default; + } + + public static int? ToNullableInt(this Memory span) + { + if (Utf8Parser.TryParse(span.Span, out int value, out int bytesConsumed)) + return value; + return null; + } + + public static long ToLong(this Memory span) + { + if (Utf8Parser.TryParse(span.Span, out long value, out int bytesConsumed)) + return value; + return default; + } + + public static long? ToNullableLong(this Memory span) + { + if (Utf8Parser.TryParse(span.Span, out long value, out int bytesConsumed)) + return value; + return null; + } + + ///// + ///// https://learn.microsoft.com/en-us/dotnet/api/system.buffers.text.utf8parser.tryparse?view=net-7.0#system-buffers-text-utf8parser-tryparse(system-readonlyspan((system-byte))-system-datetime@-system-int32@-system-char) + ///// + //public static DateTime? ToNullableDateTime(this Memory span) + //{ + // if (Utf8Parser.TryParse(span.Span, out DateTime value, out int bytesConsumed)) + // return value; + // return null; + //} + + public static DateTime? ToNullableDateTime(this Memory span) + { + var dtString = span.ToUtf8String(); + if (DateTime.TryParse(dtString, null, DateTimeStyles.AssumeLocal, out var value)) + return value; + return null; + } + public static DateTime? ToNullableDateTime(this Memory span, string format) + { + var dtString = span.ToUtf8String(); + if (DateTime.TryParseExact(dtString, format, null, DateTimeStyles.AssumeLocal, out var value)) + return value; + return null; + } + + public static DateTime ToDateTime(this Memory span, string format) + { + var dtString = span.ToUtf8String(); + if (DateTime.TryParseExact(dtString, format, null, DateTimeStyles.AssumeLocal, out var value)) + return value; + return default; + } + + public static TimeSpan? ToNullableTimeSpan(this Memory span) + { + var dtString = span.ToUtf8String(); + if (TimeSpan.TryParse(dtString, out var d)) + return d; + return null; + } + public static TimeSpan ToTimeSpan(this Memory span) + { + var dtString = span.ToUtf8String(); + if (TimeSpan.TryParse(dtString, out var d)) + return d; + return default; + } + public static char? ToNullableChar(this Memory span) + { + var dtString = span.ToUtf8String(); + if (Char.TryParse(dtString, out char value)) + return value; + return null; + } +} + +public static partial class ParseExtensions +{ + public static string ToUtf8String(this Span span) { return Encoding.UTF8.GetString(span); } @@ -86,21 +198,21 @@ public static long ToLong(this Span span) ///// //public static DateTime? ToNullableDateTime(this Span span) //{ - // if (Utf8Parser.TryParse(span, out DateTime value, out int bytesConsumed)) + // if (Utf8Parser.TryParse(span.Span, out DateTime value, out int bytesConsumed)) // return value; // return null; //} public static DateTime? ToNullableDateTime(this Span span) { - var dtString = Encoding.UTF8.GetString(span); + var dtString = span.ToUtf8String(); if (DateTime.TryParse(dtString, null, DateTimeStyles.AssumeLocal, out var value)) return value; return null; } public static DateTime? ToNullableDateTime(this Span span, string format) { - var dtString = Encoding.UTF8.GetString(span); + var dtString = span.ToUtf8String(); if (DateTime.TryParseExact(dtString, format, null, DateTimeStyles.AssumeLocal, out var value)) return value; return null; @@ -108,7 +220,7 @@ public static long ToLong(this Span span) public static DateTime ToDateTime(this Span span, string format) { - var dtString = Encoding.UTF8.GetString(span); + var dtString = span.ToUtf8String(); if (DateTime.TryParseExact(dtString, format, null, DateTimeStyles.AssumeLocal, out var value)) return value; return default; @@ -116,23 +228,134 @@ public static DateTime ToDateTime(this Span span, string format) public static TimeSpan? ToNullableTimeSpan(this Span span) { - var dtString = Encoding.UTF8.GetString(span); + var dtString = span.ToUtf8String(); if (TimeSpan.TryParse(dtString, out var d)) return d; return null; } public static TimeSpan ToTimeSpan(this Span span) { - var dtString = Encoding.UTF8.GetString(span); + var dtString = span.ToUtf8String(); if (TimeSpan.TryParse(dtString, out var d)) return d; return default; } public static char? ToNullableChar(this Span span) { - var dtString = Encoding.UTF8.GetString(span); + var dtString = span.ToUtf8String(); if (Char.TryParse(dtString, out char value)) return value; return null; } } +public static partial class ParseExtensions +{ + public static string ToUtf8String(this byte[] span) + { + return Encoding.UTF8.GetString(span); + } + + public static string ToStringNullIfEmpty(this byte[] span) + { + if (span.Length > 0) + return Encoding.UTF8.GetString(span); + return null; + } + + public static double ToDouble(this byte[] span) + { + if (Utf8Parser.TryParse(span, out double value, out int bytesConsumed)) + return value; + return default; + } + + public static double? ToNullableDouble(this byte[] span) + { + if (Utf8Parser.TryParse(span, out double value, out int bytesConsumed)) + return value; + return null; + } + + public static int ToInt(this byte[] span) + { + if (Utf8Parser.TryParse(span, out int value, out int bytesConsumed)) + return value; + return default; + } + + public static int? ToNullableInt(this byte[] span) + { + if (Utf8Parser.TryParse(span, out int value, out int bytesConsumed)) + return value; + return null; + } + + public static long ToLong(this byte[] span) + { + if (Utf8Parser.TryParse(span, out long value, out int bytesConsumed)) + return value; + return default; + } + + public static long? ToNullableLong(this byte[] span) + { + if (Utf8Parser.TryParse(span, out long value, out int bytesConsumed)) + return value; + return null; + } + + ///// + ///// https://learn.microsoft.com/en-us/dotnet/api/system.buffers.text.utf8parser.tryparse?view=net-7.0#system-buffers-text-utf8parser-tryparse(system-readonlyspan((system-byte))-system-datetime@-system-int32@-system-char) + ///// + //public static DateTime? ToNullableDateTime(this byte[] span) + //{ + // if (Utf8Parser.TryParse(span.Span, out DateTime value, out int bytesConsumed)) + // return value; + // return null; + //} + + public static DateTime? ToNullableDateTime(this byte[] span) + { + var dtString = span.ToUtf8String(); + if (DateTime.TryParse(dtString, null, DateTimeStyles.AssumeLocal, out var value)) + return value; + return null; + } + public static DateTime? ToNullableDateTime(this byte[] span, string format) + { + var dtString = span.ToUtf8String(); + if (DateTime.TryParseExact(dtString, format, null, DateTimeStyles.AssumeLocal, out var value)) + return value; + return null; + } + + public static DateTime ToDateTime(this byte[] span, string format) + { + var dtString = span.ToUtf8String(); + if (DateTime.TryParseExact(dtString, format, null, DateTimeStyles.AssumeLocal, out var value)) + return value; + return default; + } + + public static TimeSpan? ToNullableTimeSpan(this byte[] span) + { + var dtString = span.ToUtf8String(); + if (TimeSpan.TryParse(dtString, out var d)) + return d; + return null; + } + public static TimeSpan ToTimeSpan(this byte[] span) + { + var dtString = span.ToUtf8String(); + if (TimeSpan.TryParse(dtString, out var d)) + return d; + return default; + } + public static char? ToNullableChar(this byte[] span) + { + var dtString = span.ToUtf8String(); + if (Char.TryParse(dtString, out char value)) + return value; + return null; + } +} \ No newline at end of file