diff --git a/README.md b/README.md index 362e43fe..8952b805 100644 --- a/README.md +++ b/README.md @@ -854,6 +854,22 @@ There are a few restrictions on the types that can be generated. Among the primi `[GenerateTypeScript]` can only be applied to classes and is currently not supported by struct. +### Configure import file extension + +In default, MemoryPack generates file extension as `.js` like `import { MemoryPackWriter } from "./MemoryPackWriter.js";`. If you want to change other extension or empty, use `MemoryPackGenerator_TypeScriptImportExtension` to configure it. + +```xml + + + + + + $(MSBuildProjectDirectory)\wwwroot\js\memorypack + + + +``` + Streaming Serialization --- `MemoryPack.Streaming` provides additional `MemoryPackStreamingSerializer`, it serialize/deserialize collection data streamingly. diff --git a/sandbox/Benchmark/Benchmarks/ListFormatterVsDirect.cs b/sandbox/Benchmark/Benchmarks/ListFormatterVsDirect.cs new file mode 100644 index 00000000..dc715cff --- /dev/null +++ b/sandbox/Benchmark/Benchmarks/ListFormatterVsDirect.cs @@ -0,0 +1,75 @@ +using Benchmark.BenchmarkNetUtilities; +using Benchmark.Models; +using MemoryPack; +using MemoryPack.Formatters; +using Orleans.Serialization.Buffers; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Benchmark.Benchmarks; + +public class ListFormatterVsDirect +{ + List value; + byte[] bytes; + IMemoryPackFormatter> formatter; + ArrayBufferWriter buffer; + MemoryPackWriterOptionalState state; + MemoryPackReaderOptionalState state2; + + public ListFormatterVsDirect() + { + value = Enumerable.Range(0, 100) + .Select(_ => new MyClass { X = 100, Y = 99999999, Z = 4444, FirstName = "Hoge Huga Tako", LastName = "あいうえおかきくけこ" }) + .ToList(); + bytes = MemoryPackSerializer.Serialize(value); + formatter = new ListFormatter(); + buffer = new ArrayBufferWriter(bytes.Length); + + state = MemoryPackWriterOptionalStatePool.Rent(null); + state2 = MemoryPackReaderOptionalStatePool.Rent(null); + } + + [Benchmark, BenchmarkCategory(Categories.Serialize)] + public void SerializeFormatter() + { + var writer = new MemoryPackWriter>(ref buffer, state); + formatter.Serialize(ref writer, ref value!); + writer.Flush(); + buffer.Clear(); + } + + [Benchmark, BenchmarkCategory(Categories.Serialize)] + public void SerializePackable() + { + var writer = new MemoryPackWriter>(ref buffer, state); + MemoryPack.Formatters.ListFormatter.SerializePackable(ref writer, ref value!); + writer.Flush(); + buffer.Clear(); + } + + + [Benchmark, BenchmarkCategory(Categories.Deserialize)] + public void DeserializeFormatter() + { + List? list = null; + var reader = new MemoryPackReader(bytes, state2); + //reader.ReadPackableArray + // var a = MemoryPack.Formatters.ListFormatter.DeserializePackable<(ref reader); + formatter.Deserialize(ref reader, ref list); + reader.Dispose(); + } + + [Benchmark, BenchmarkCategory(Categories.Deserialize)] + public void DeserializePackable() + { + List? list = null; + var reader = new MemoryPackReader(bytes, state2); + ListFormatter.DeserializePackable(ref reader, ref list!); + reader.Dispose(); + } +} diff --git a/sandbox/Benchmark/Program.cs b/sandbox/Benchmark/Program.cs index 7d6db548..4592d552 100644 --- a/sandbox/Benchmark/Program.cs +++ b/sandbox/Benchmark/Program.cs @@ -45,7 +45,7 @@ // BenchmarkRunner.Run(config, args); - +BenchmarkRunner.Run(config, args); //BenchmarkRunner.Run(config, args); @@ -55,7 +55,7 @@ // BenchmarkRunner.Run>(config, args); -BenchmarkRunner.Run(config, args); +//BenchmarkRunner.Run(config, args); //BenchmarkRunner.Run>(config, args); //BenchmarkRunner.Run>(config, args); //BenchmarkRunner.Run>(config, args); diff --git a/sandbox/SandboxConsoleApp/Models.cs b/sandbox/SandboxConsoleApp/Models.cs index 7507a6c1..209fc2f7 100644 --- a/sandbox/SandboxConsoleApp/Models.cs +++ b/sandbox/SandboxConsoleApp/Models.cs @@ -1,6 +1,7 @@ using MemoryPack; using MemoryPack.Formatters; using MemoryPack.Internal; +using Newtonsoft.Json.Linq; using System; using System.Buffers; using System.Collections.Generic; @@ -13,6 +14,15 @@ namespace SandboxConsoleApp; +[MemoryPackable] +public partial class Mop +{ + public NoGen? MyProperty { get; set; } + public LisList? MyLisList { get; set; } + public List? SuageMan { get; set; } +} + + [MemoryPackable] public partial class NotSample { @@ -53,3 +63,16 @@ public partial class Suage // this.Prop2 = prop2; //} } + + + +[MemoryPackable(GenerateType.NoGenerate)] +public partial class NoGen +{ +} + +[MemoryPackable(GenerateType.Collection)] +public partial class LisList : List +{ + +} diff --git a/sandbox/SandboxWebApp/SandboxWebApp.csproj b/sandbox/SandboxWebApp/SandboxWebApp.csproj index 3dcbcb47..d6103652 100644 --- a/sandbox/SandboxWebApp/SandboxWebApp.csproj +++ b/sandbox/SandboxWebApp/SandboxWebApp.csproj @@ -16,9 +16,11 @@ + $(MSBuildProjectDirectory)\wwwroot\js\memorypack + .js diff --git a/src/MemoryPack.Core/Formatters/CollectionFormatters.cs b/src/MemoryPack.Core/Formatters/CollectionFormatters.cs index 256474b5..f0863471 100644 --- a/src/MemoryPack.Core/Formatters/CollectionFormatters.cs +++ b/src/MemoryPack.Core/Formatters/CollectionFormatters.cs @@ -3,7 +3,6 @@ using System.Buffers; using System.Collections.Concurrent; using System.Collections.ObjectModel; -using System.Drawing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -49,6 +48,87 @@ public static partial class MemoryPackFormatterProvider namespace MemoryPack.Formatters { + [Preserve] + public static class ListFormatter + { + [Preserve] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializePackable(ref MemoryPackWriter writer, scoped ref List? value) + where T : IMemoryPackable +#if NET7_0_OR_GREATER + where TBufferWriter : IBufferWriter +#else + where TBufferWriter : class, IBufferWriter +#endif + { + if (value == null) + { + writer.WriteNullCollectionHeader(); + return; + } + +#if NET7_0_OR_GREATER + writer.WritePackableSpan(CollectionsMarshal.AsSpan(value)); +#else + var formatter = writer.GetFormatter(); + writer.WriteCollectionHeader(value.Count); + foreach (var item in value) + { + var v = item; + formatter.Serialize(ref writer, ref v); + } +#endif + } + + [Preserve] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List? DeserializePackable(ref MemoryPackReader reader) + where T : IMemoryPackable + { + List? value = default; + DeserializePackable(ref reader, ref value); + return value; + } + + [Preserve] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void DeserializePackable(ref MemoryPackReader reader, scoped ref List? value) + where T : IMemoryPackable + { + if (!reader.TryReadCollectionHeader(out var length)) + { + value = null; + return; + } + + if (value == null) + { + value = new List(length); + } +#if NET7_0_OR_GREATER + else if (value.Count == length) + { + value.Clear(); + } + + var span = CollectionsMarshalEx.CreateSpan(value, length); + reader.ReadPackableSpanWithoutReadLengthHeader(length, ref span); +#else + else + { + value.Clear(); + } + var formatter = reader.GetFormatter(); + for (var i = 0; i < length; i++) + { + T? v = default; + formatter.Deserialize(ref reader, ref v); + value.Add(v); + } +#endif + } + } + [Preserve] public sealed class ListFormatter : MemoryPackFormatter> { diff --git a/src/MemoryPack.Core/MemoryPackReader.cs b/src/MemoryPack.Core/MemoryPackReader.cs index fe4bee71..54871947 100644 --- a/src/MemoryPack.Core/MemoryPackReader.cs +++ b/src/MemoryPack.Core/MemoryPackReader.cs @@ -420,6 +420,7 @@ public void ReadValueWithFormatter(TFormatter formatter, scoped r #region ReadArray/Span + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T?[]? ReadArray() { T?[]? value = default; @@ -427,6 +428,7 @@ public void ReadValueWithFormatter(TFormatter formatter, scoped r return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadArray(scoped ref T?[]? value) { if (!RuntimeHelpers.IsReferenceOrContainsReferences()) @@ -460,6 +462,7 @@ public void ReadArray(scoped ref T?[]? value) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadSpan(scoped ref Span value) { if (!RuntimeHelpers.IsReferenceOrContainsReferences()) @@ -492,22 +495,111 @@ public void ReadSpan(scoped ref Span value) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T?[]? ReadPackableArray() + where T : IMemoryPackable + { + T?[]? value = default; + ReadPackableArray(ref value); + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadPackableArray(scoped ref T?[]? value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + ReadArray(ref value); + return; +#else + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousReadUnmanagedArray(ref value); + return; + } + + if (!TryReadCollectionHeader(out var length)) + { + value = null; + return; + } + + if (length == 0) + { + value = Array.Empty(); + return; + } + + // T[] support overwrite + if (value == null || value.Length != length) + { + value = new T[length]; + } + + for (int i = 0; i < length; i++) + { + T.Deserialize(ref this, ref value[i]); + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadPackableSpan(scoped ref Span value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + ReadSpan(ref value); + return; +#else + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousReadUnmanagedSpan(ref value); + return; + } + + if (!TryReadCollectionHeader(out var length)) + { + value = default; + return; + } + + if (length == 0) + { + value = Array.Empty(); + return; + } + + if (value.Length != length) + { + value = new T[length]; + } + + for (int i = 0; i < length; i++) + { + T.Deserialize(ref this, ref value[i]); + } +#endif + } + #endregion #region UnmanagedArray/Span + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T[]? ReadUnmanagedArray() where T : unmanaged { return DangerousReadUnmanagedArray(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadUnmanagedArray(scoped ref T[]? value) where T : unmanaged { DangerousReadUnmanagedArray(ref value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadUnmanagedSpan(scoped ref Span value) where T : unmanaged { @@ -515,6 +607,7 @@ public void ReadUnmanagedSpan(scoped ref Span value) } // T: should be unamanged type + [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe T[]? DangerousReadUnmanagedArray() { if (!TryReadCollectionHeader(out var length)) @@ -533,6 +626,7 @@ public void ReadUnmanagedSpan(scoped ref Span value) return dest; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void DangerousReadUnmanagedArray(scoped ref T[]? value) { if (!TryReadCollectionHeader(out var length)) @@ -561,6 +655,7 @@ public unsafe void DangerousReadUnmanagedArray(scoped ref T[]? value) Advance(byteCount); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void DangerousReadUnmanagedSpan(scoped ref Span value) { if (!TryReadCollectionHeader(out var length)) @@ -591,6 +686,7 @@ public unsafe void DangerousReadUnmanagedSpan(scoped ref Span value) #endregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadSpanWithoutReadLengthHeader(int length, scoped ref Span value) { if (length == 0) @@ -627,4 +723,47 @@ public void ReadSpanWithoutReadLengthHeader(int length, scoped ref Span v } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadPackableSpanWithoutReadLengthHeader(int length, scoped ref Span value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + ReadSpanWithoutReadLengthHeader(length, ref value); + return; +#else + if (length == 0) + { + value = Array.Empty(); + return; + } + + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + if (value.Length != length) + { + value = AllocateUninitializedArray(length); + } + + var byteCount = length * Unsafe.SizeOf(); + ref var src = ref GetSpanReference(byteCount); + ref var dest = ref Unsafe.As(ref MemoryMarshal.GetReference(value)!); + Unsafe.CopyBlockUnaligned(ref dest, ref src, (uint)byteCount); + + Advance(byteCount); + } + else + { + if (value.Length != length) + { + value = new T[length]; + } + + for (int i = 0; i < length; i++) + { + T.Deserialize(ref this, ref value[i]); + } + } +#endif + } } diff --git a/src/MemoryPack.Core/MemoryPackWriter.cs b/src/MemoryPack.Core/MemoryPackWriter.cs index b58226be..98a2a3c4 100644 --- a/src/MemoryPack.Core/MemoryPackWriter.cs +++ b/src/MemoryPack.Core/MemoryPackWriter.cs @@ -496,9 +496,82 @@ public void WriteSpan(scoped ReadOnlySpan value) } } - #endregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackableArray(T?[]? value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + WriteArray(value); + return; +#else + + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousWriteUnmanagedArray(value); + return; + } + + if (value == null) + { + WriteNullCollectionHeader(); + return; + } + + WriteCollectionHeader(value.Length); + for (int i = 0; i < value.Length; i++) + { + T.Serialize(ref this, ref value[i]); + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackableSpan(scoped Span value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + WriteSpan(value); + return; +#else + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousWriteUnmanagedSpan(value); + return; + } + + WriteCollectionHeader(value.Length); + for (int i = 0; i < value.Length; i++) + { + T.Serialize(ref this, ref value[i]); + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackableSpan(scoped ReadOnlySpan value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + WriteSpan(value); + return; +#else + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousWriteUnmanagedSpan(value); + return; + } + + WriteCollectionHeader(value.Length); + for (int i = 0; i < value.Length; i++) + { + T.Serialize(ref this, ref Unsafe.AsRef(value[i])); + } +#endif + } + +#endregion - #region WriteUnmanagedArray/Span +#region WriteUnmanagedArray/Span [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteUnmanagedArray(T[]? value) @@ -589,7 +662,7 @@ public void DangerousWriteUnmanagedSpan(scoped ReadOnlySpan value) Advance(allocSize); } - #endregion +#endregion [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/MemoryPack.Generator/Extensions.cs b/src/MemoryPack.Generator/Extensions.cs index 18bde486..a017f607 100644 --- a/src/MemoryPack.Generator/Extensions.cs +++ b/src/MemoryPack.Generator/Extensions.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace MemoryPack.Generator; @@ -61,10 +62,46 @@ public static IEnumerable GetAllMembers(this INamedTypeSymbol symbol, b } } - public static bool IsWillImplementIMemoryPackable(this ITypeSymbol symbol, ReferenceSymbols references) + public static bool TryGetMemoryPackableType(this ITypeSymbol symbol, ReferenceSymbols references, out GenerateType generateType, out SerializeLayout serializeLayout) { - // [MemoryPackable] and not interface/abstract, generator will implmement IMemoryPackable - return !symbol.IsAbstract && symbol.ContainsAttribute(references.MemoryPackableAttribute); + var packableCtorArgs = symbol.GetAttribute(references.MemoryPackableAttribute)?.ConstructorArguments; + generateType = GenerateType.Object; + serializeLayout = SerializeLayout.Sequential; + if (packableCtorArgs == null) + { + generateType = GenerateType.NoGenerate; + serializeLayout = SerializeLayout.Sequential; + return false; + } + else if (packableCtorArgs.Value.Length != 0) + { + // MemoryPackable has two attribtue + if (packableCtorArgs.Value.Length == 1) + { + // (SerializeLayout serializeLayout) + var ctorValue = packableCtorArgs.Value[0]; + serializeLayout = (SerializeLayout)(ctorValue.Value ?? SerializeLayout.Sequential); + generateType = GenerateType.Object; + } + else + { + // (GenerateType generateType = GenerateType.Object, SerializeLayout serializeLayout = SerializeLayout.Sequential) + generateType = (GenerateType)(packableCtorArgs.Value[0].Value ?? GenerateType.Object); + serializeLayout = (SerializeLayout)(packableCtorArgs.Value[1].Value ?? SerializeLayout.Sequential); + if (generateType is GenerateType.VersionTolerant or GenerateType.CircularReference) + { + serializeLayout = SerializeLayout.Explicit; // version-torelant, always explicit. + } + } + } + + if (symbol.IsStatic || symbol.IsAbstract) + { + // static or abstract class is Union + return false; + } + + return true; } public static bool IsWillImplementMemoryPackUnion(this ITypeSymbol symbol, ReferenceSymbols references) diff --git a/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs b/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs index dcafa415..76b7730b 100644 --- a/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs +++ b/src/MemoryPack.Generator/MemoryPackGenerator.Emitter.cs @@ -948,7 +948,7 @@ string EmitUnionSerializeBody() var writeBody = UnionTags .Select(x => { - var method = x.Type.IsWillImplementIMemoryPackable(reference) + var method = (x.Type.TryGetMemoryPackableType(reference, out var genType, out _) && genType is GenerateType.Object or GenerateType.VersionTolerant or GenerateType.CircularReference) ? "WritePackable" : "WriteValue"; return $" case {x.Tag}: writer.{method}(System.Runtime.CompilerServices.Unsafe.As<{TypeName}?, {ToUnionTagTypeFullyQualifiedToString(x.Type)}>(ref value)); break;"; @@ -985,7 +985,7 @@ string EmitUnionDeserializeBody() { var readBody = UnionTags.Select(x => { - var method = x.Type.IsWillImplementIMemoryPackable(reference) + var method = (x.Type.TryGetMemoryPackableType(reference, out var genType, out _) && genType is GenerateType.Object or GenerateType.VersionTolerant or GenerateType.CircularReference) ? "ReadPackable" : "ReadValue"; return $$""" @@ -1099,6 +1099,10 @@ public string EmitSerialize(string writer) return $"{writer}.WriteString(value.{Name});"; case MemberKind.UnmanagedArray: return $"{writer}.WriteUnmanagedArray(value.{Name});"; + case MemberKind.MemoryPackableArray: + return $"{writer}.WritePackableArray(value.{Name});"; + case MemberKind.MemoryPackableList: + return $"MemoryPack.Formatters.ListFormatter.SerializePackable(ref {writer}, ref System.Runtime.CompilerServices.Unsafe.AsRef(value.{Name}));"; case MemberKind.Array: return $"{writer}.WriteArray(value.{Name});"; case MemberKind.Blank: @@ -1147,6 +1151,10 @@ public string EmitReadToDeserialize(int i, bool requireDeltaCheck) return $"{pre}__{Name} = reader.ReadString();"; case MemberKind.UnmanagedArray: return $"{pre}__{Name} = reader.ReadUnmanagedArray<{(MemberType as IArrayTypeSymbol)!.ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();"; + case MemberKind.MemoryPackableArray: + return $"{pre}__{Name} = reader.ReadPackableArray<{(MemberType as IArrayTypeSymbol)!.ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();"; + case MemberKind.MemoryPackableList: + return $"{pre}__{Name} = MemoryPack.Formatters.ListFormatter.DeserializePackable<{(MemberType as INamedTypeSymbol)!.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>(ref reader);"; case MemberKind.Array: return $"{pre}__{Name} = reader.ReadArray<{(MemberType as IArrayTypeSymbol)!.ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();"; case MemberKind.Blank: @@ -1177,6 +1185,10 @@ public string EmitReadRefDeserialize(int i, bool requireDeltaCheck) return $"{pre}__{Name} = reader.ReadString();"; case MemberKind.UnmanagedArray: return $"{pre}reader.ReadUnmanagedArray(ref __{Name});"; + case MemberKind.MemoryPackableArray: + return $"{pre}reader.ReadPackableArray(ref __{Name});"; + case MemberKind.MemoryPackableList: + return $"{pre}MemoryPack.Formatters.ListFormatter.DeserializePackable(ref reader, ref __{Name});"; case MemberKind.Array: return $"{pre}reader.ReadArray(ref __{Name});"; case MemberKind.Blank: diff --git a/src/MemoryPack.Generator/MemoryPackGenerator.Parser.cs b/src/MemoryPack.Generator/MemoryPackGenerator.Parser.cs index a8246495..db1af342 100644 --- a/src/MemoryPack.Generator/MemoryPackGenerator.Parser.cs +++ b/src/MemoryPack.Generator/MemoryPackGenerator.Parser.cs @@ -21,6 +21,10 @@ public enum MemberKind String, Array, UnmanagedArray, + MemoryPackableArray, // T[] where T: IMemoryPackable + MemoryPackableList, // List where T: IMemoryPackable + MemoryPackableCollection, // GenerateType.Collection + MemoryPackableNoGenerate, // GenerateType.NoGenerate Enum, // from attribute @@ -62,34 +66,9 @@ public TypeMeta(INamedTypeSymbol symbol, ReferenceSymbols reference) this.reference = reference; this.Symbol = symbol; - var packableCtorArgs = symbol.GetAttribute(reference.MemoryPackableAttribute)?.ConstructorArguments; - this.GenerateType = GenerateType.Object; - if (packableCtorArgs == null) - { - this.GenerateType = GenerateType.NoGenerate; - this.SerializeLayout = SerializeLayout.Sequential; - } - else if (packableCtorArgs.Value.Length != 0) - { - // MemoryPackable has two attribtue - if (packableCtorArgs.Value.Length == 1) - { - // (SerializeLayout serializeLayout) - var ctorValue = packableCtorArgs.Value[0]; - this.SerializeLayout = (SerializeLayout)(ctorValue.Value ?? SerializeLayout.Sequential); - this.GenerateType = GenerateType.Object; - } - else - { - // (GenerateType generateType = GenerateType.Object, SerializeLayout serializeLayout = SerializeLayout.Sequential) - this.GenerateType = (GenerateType)(packableCtorArgs.Value[0].Value ?? GenerateType.Object); - this.SerializeLayout = (SerializeLayout)(packableCtorArgs.Value[1].Value ?? SerializeLayout.Sequential); - if (this.GenerateType is GenerateType.VersionTolerant or GenerateType.CircularReference) - { - this.SerializeLayout = SerializeLayout.Explicit; // version-torelant, always explicit. - } - } - } + symbol.TryGetMemoryPackableType(reference, out var generateType, out var serializeLayout); + this.GenerateType = generateType; + this.SerializeLayout = serializeLayout; this.TypeName = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); this.Constructor = ChooseConstructor(symbol, reference); @@ -616,9 +595,20 @@ static MemberKind ParseMemberKind(ISymbol? memberSymbol, ITypeSymbol memberType, { return MemberKind.MemoryPackable; } - else if (memberType.IsWillImplementIMemoryPackable(references)) + else if (memberType.TryGetMemoryPackableType(references, out var generateType, out var serializeLayout)) { - return MemberKind.MemoryPackable; + switch (generateType) + { + case GenerateType.Object: + case GenerateType.VersionTolerant: + case GenerateType.CircularReference: + return MemberKind.MemoryPackable; + case GenerateType.Collection: + return MemberKind.MemoryPackableCollection; + case GenerateType.NoGenerate: + default: + return MemberKind.MemoryPackableNoGenerate; + } } else if (memberType.IsWillImplementMemoryPackUnion(references)) { @@ -645,6 +635,11 @@ static MemberKind ParseMemberKind(ISymbol? memberSymbol, ITypeSymbol memberType, } else { + if (elemType.TryGetMemoryPackableType(references, out var elemGenerateType, out _) && elemGenerateType is GenerateType.Object or GenerateType.VersionTolerant or GenerateType.CircularReference) + { + return MemberKind.MemoryPackableArray; + } + return MemberKind.Array; } } @@ -677,6 +672,15 @@ static MemberKind ParseMemberKind(ISymbol? memberSymbol, ITypeSymbol memberType, { return MemberKind.Nullable; } + + if (nts.EqualsUnconstructedGenericType(references.KnownTypes.System_Collections_Generic_List_T)) + { + if (nts.TypeArguments[0].TryGetMemoryPackableType(references, out var elemGenerateType, out _) && elemGenerateType is GenerateType.Object or GenerateType.VersionTolerant or GenerateType.CircularReference) + { + return MemberKind.MemoryPackableList; + } + return MemberKind.KnownType; + } } if (references.KnownTypes.Contains(memberType)) diff --git a/src/MemoryPack.Generator/MemoryPackGenerator.TypeScript.cs b/src/MemoryPack.Generator/MemoryPackGenerator.TypeScript.cs index 4df399cb..ebd341c4 100644 --- a/src/MemoryPack.Generator/MemoryPackGenerator.TypeScript.cs +++ b/src/MemoryPack.Generator/MemoryPackGenerator.TypeScript.cs @@ -11,7 +11,7 @@ namespace MemoryPack.Generator; partial class MemoryPackGenerator { - static TypeMeta? GenerateTypeScript(TypeDeclarationSyntax syntax, Compilation compilation, string typeScriptOutputDirectoryPath, in SourceProductionContext context, + static TypeMeta? GenerateTypeScript(TypeDeclarationSyntax syntax, Compilation compilation, string typeScriptOutputDirectoryPath,string importExt, in SourceProductionContext context, ReferenceSymbols reference, IReadOnlyDictionary unionMap) { var semanticModel = compilation.GetSemanticModel(syntax.SyntaxTree); @@ -44,9 +44,9 @@ partial class MemoryPackGenerator var sb = new StringBuilder(); - sb.AppendLine(""" -import { MemoryPackWriter } from "./MemoryPackWriter.js"; -import { MemoryPackReader } from "./MemoryPackReader.js"; + sb.AppendLine($$""" +import { MemoryPackWriter } from "./MemoryPackWriter{{importExt}}"; +import { MemoryPackReader } from "./MemoryPackReader{{importExt}}"; """); var collector = new TypeCollector(); @@ -68,17 +68,17 @@ partial class MemoryPackGenerator // add import(enum, union, memorypackable) foreach (var item in collector.GetEnums()) { - sb.AppendLine($"import {{ {item.Name} }} from \"./{item.Name}.js\"; "); + sb.AppendLine($"import {{ {item.Name} }} from \"./{item.Name}{importExt}\"; "); } foreach (var item in collector.GetMemoryPackableTypes(reference).Where(x => !SymbolEqualityComparer.Default.Equals(x, typeSymbol))) { - sb.AppendLine($"import {{ {item.Name} }} from \"./{item.Name}.js\"; "); + sb.AppendLine($"import {{ {item.Name} }} from \"./{item.Name}{importExt}\"; "); } sb.AppendLine(); try { - typeMeta.EmitTypescript(sb, unionMap); + typeMeta.EmitTypescript(sb, unionMap, importExt); } catch (NotSupportedTypeException ex) { @@ -165,11 +165,11 @@ static bool Validate(TypeMeta type, TypeDeclarationSyntax syntax, in SourceProdu public partial class TypeMeta { - public void EmitTypescript(StringBuilder sb, IReadOnlyDictionary unionMap) + public void EmitTypescript(StringBuilder sb, IReadOnlyDictionary unionMap, string importExt) { if (IsUnion) { - EmitTypeScriptUnion(sb); + EmitTypeScriptUnion(sb, importExt); return; } @@ -249,7 +249,7 @@ static deserializeArrayCore(reader: MemoryPackReader): ({{TypeName}} | null)[] | sb.AppendLine(code); } - public void EmitTypeScriptUnion(StringBuilder sb) + public void EmitTypeScriptUnion(StringBuilder sb, string importExt) { string EmitUnionSerialize() { @@ -282,7 +282,7 @@ string EmitUnionDeserialize() foreach (var item in UnionTags) { - sb.AppendLine($"import {{ {item.Type.Name} }} from \"./{item.Type.Name}.js\"; "); + sb.AppendLine($"import {{ {item.Type.Name} }} from \"./{item.Type.Name}{importExt}\"; "); } sb.AppendLine(); diff --git a/src/MemoryPack.Generator/MemoryPackGenerator.cs b/src/MemoryPack.Generator/MemoryPackGenerator.cs index 492d0210..2ba55e58 100644 --- a/src/MemoryPack.Generator/MemoryPackGenerator.cs +++ b/src/MemoryPack.Generator/MemoryPackGenerator.cs @@ -100,12 +100,18 @@ void RegisterTypeScript(IncrementalGeneratorInitializationContext context) var typeScriptEnabled = context.AnalyzerConfigOptionsProvider .Select((configOptions, token) => { - if (configOptions.GlobalOptions.TryGetValue("build_property.MemoryPackGenerator_TypeScriptOutputDirectory", out var path)) + string? path; + if (!configOptions.GlobalOptions.TryGetValue("build_property.MemoryPackGenerator_TypeScriptOutputDirectory", out path)) { - return path; + path = null; + } + string ext; + if (!configOptions.GlobalOptions.TryGetValue("build_property.MemoryPackGenerator_TypeScriptImportExtension", out ext!)) + { + ext = ".js"; } - return (string?)null; + return (path, ext); }); var typeScriptDeclarations = context.SyntaxProvider.ForAttributeWithMetadataName( @@ -125,7 +131,7 @@ or RecordDeclarationSyntax .Combine(context.CompilationProvider) .WithComparer(Comparer.Instance) .Combine(typeScriptEnabled) - .Where(x => x.Right != null) // filter, exists TypeScriptOutputDirectory + .Where(x => x.Right.path != null) // filter, exists TypeScriptOutputDirectory .Collect(); context.RegisterSourceOutput(typeScriptGenerateSource, static (context, source) => @@ -166,14 +172,15 @@ or RecordDeclarationSyntax { var typeDeclaration = item.Left.Item1; var compilation = item.Left.Item2; - var path = generatePath = item.Right!; + var path = generatePath = item.Right.path!; + var importExt = item.Right.ext; if (reference == null) { reference = new ReferenceSymbols(compilation); } - var meta = GenerateTypeScript(typeDeclaration, compilation, path, context, reference, unionMap); + var meta = GenerateTypeScript(typeDeclaration, compilation, path, importExt, context, reference, unionMap); if (meta != null) { collector.Visit(meta, false); diff --git a/src/MemoryPack.Generator/ReferenceSymbols.cs b/src/MemoryPack.Generator/ReferenceSymbols.cs index 88867b01..7abc8aad 100644 --- a/src/MemoryPack.Generator/ReferenceSymbols.cs +++ b/src/MemoryPack.Generator/ReferenceSymbols.cs @@ -65,6 +65,7 @@ public class WellKnownTypes public INamedTypeSymbol System_Collections_Generic_ICollection_T { get; } public INamedTypeSymbol System_Collections_Generic_ISet_T { get; } public INamedTypeSymbol System_Collections_Generic_IDictionary_T { get; } + public INamedTypeSymbol System_Collections_Generic_List_T { get; } public INamedTypeSymbol System_Guid { get; } public INamedTypeSymbol System_Version { get; } @@ -177,6 +178,7 @@ public WellKnownTypes(ReferenceSymbols parent) System_Collections_Generic_ICollection_T = GetTypeByMetadataName("System.Collections.Generic.ICollection`1").ConstructUnboundGenericType(); System_Collections_Generic_ISet_T = GetTypeByMetadataName("System.Collections.Generic.ISet`1").ConstructUnboundGenericType(); System_Collections_Generic_IDictionary_T = GetTypeByMetadataName("System.Collections.Generic.IDictionary`2").ConstructUnboundGenericType(); + System_Collections_Generic_List_T = GetTypeByMetadataName("System.Collections.Generic.List`1").ConstructUnboundGenericType(); System_Guid = GetTypeByMetadataName("System.Guid"); System_Version = GetTypeByMetadataName("System.Version"); System_Uri = GetTypeByMetadataName("System.Uri"); diff --git a/src/MemoryPack.Generator/TypeScriptMember.cs b/src/MemoryPack.Generator/TypeScriptMember.cs index 281ea5af..d3a9b072 100644 --- a/src/MemoryPack.Generator/TypeScriptMember.cs +++ b/src/MemoryPack.Generator/TypeScriptMember.cs @@ -164,7 +164,7 @@ TypeScriptType ConvertToTypeScriptType(ITypeSymbol symbol, ReferenceSymbols refe break; } - if (symbol.IsWillImplementIMemoryPackable(references) || symbol.IsWillImplementMemoryPackUnion(references)) + if (symbol.TryGetMemoryPackableType(references, out _, out _) || symbol.IsWillImplementMemoryPackUnion(references)) { return new TypeScriptType { diff --git a/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/Formatters/CollectionFormatters.cs b/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/Formatters/CollectionFormatters.cs index 5f915891..111988ec 100644 --- a/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/Formatters/CollectionFormatters.cs +++ b/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/Formatters/CollectionFormatters.cs @@ -12,7 +12,6 @@ using System.Buffers; using System.Collections.Concurrent; using System.Collections.ObjectModel; -using System.Drawing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -58,6 +57,87 @@ public static partial class MemoryPackFormatterProvider namespace MemoryPack.Formatters { + [Preserve] + public static class ListFormatter + { + [Preserve] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializePackable(ref MemoryPackWriter writer, ref List? value) + where T : IMemoryPackable +#if NET7_0_OR_GREATER + +#else + +#endif + { + if (value == null) + { + writer.WriteNullCollectionHeader(); + return; + } + +#if NET7_0_OR_GREATER + writer.WritePackableSpan(CollectionsMarshal.AsSpan(value)); +#else + var formatter = writer.GetFormatter(); + writer.WriteCollectionHeader(value.Count); + foreach (var item in value) + { + var v = item; + formatter.Serialize(ref writer, ref v); + } +#endif + } + + [Preserve] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List? DeserializePackable(ref MemoryPackReader reader) + where T : IMemoryPackable + { + List? value = default; + DeserializePackable(ref reader, ref value); + return value; + } + + [Preserve] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void DeserializePackable(ref MemoryPackReader reader, ref List? value) + where T : IMemoryPackable + { + if (!reader.TryReadCollectionHeader(out var length)) + { + value = null; + return; + } + + if (value == null) + { + value = new List(length); + } +#if NET7_0_OR_GREATER + else if (value.Count == length) + { + value.Clear(); + } + + var span = CollectionsMarshalEx.CreateSpan(value, length); + reader.ReadPackableSpanWithoutReadLengthHeader(length, ref span); +#else + else + { + value.Clear(); + } + var formatter = reader.GetFormatter(); + for (var i = 0; i < length; i++) + { + T? v = default; + formatter.Deserialize(ref reader, ref v); + value.Add(v); + } +#endif + } + } + [Preserve] public sealed class ListFormatter : MemoryPackFormatter> { diff --git a/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/MemoryPackReader.cs b/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/MemoryPackReader.cs index 3da20b34..6a533b3b 100644 --- a/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/MemoryPackReader.cs +++ b/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/MemoryPackReader.cs @@ -429,6 +429,7 @@ public void ReadValueWithFormatter(TFormatter formatter, ref T? v #region ReadArray/Span + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T?[]? ReadArray() { T?[]? value = default; @@ -436,6 +437,7 @@ public void ReadValueWithFormatter(TFormatter formatter, ref T? v return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadArray(ref T?[]? value) { if (!RuntimeHelpers.IsReferenceOrContainsReferences()) @@ -469,6 +471,7 @@ public void ReadArray(ref T?[]? value) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadSpan(ref Span value) { if (!RuntimeHelpers.IsReferenceOrContainsReferences()) @@ -501,22 +504,111 @@ public void ReadSpan(ref Span value) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T?[]? ReadPackableArray() + where T : IMemoryPackable + { + T?[]? value = default; + ReadPackableArray(ref value); + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadPackableArray(ref T?[]? value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + ReadArray(ref value); + return; +#else + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousReadUnmanagedArray(ref value); + return; + } + + if (!TryReadCollectionHeader(out var length)) + { + value = null; + return; + } + + if (length == 0) + { + value = Array.Empty(); + return; + } + + // T[] support overwrite + if (value == null || value.Length != length) + { + value = new T[length]; + } + + for (int i = 0; i < length; i++) + { + T.Deserialize(ref this, ref value[i]); + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadPackableSpan(ref Span value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + ReadSpan(ref value); + return; +#else + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousReadUnmanagedSpan(ref value); + return; + } + + if (!TryReadCollectionHeader(out var length)) + { + value = default; + return; + } + + if (length == 0) + { + value = Array.Empty(); + return; + } + + if (value.Length != length) + { + value = new T[length]; + } + + for (int i = 0; i < length; i++) + { + T.Deserialize(ref this, ref value[i]); + } +#endif + } + #endregion #region UnmanagedArray/Span + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T[]? ReadUnmanagedArray() where T : unmanaged { return DangerousReadUnmanagedArray(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadUnmanagedArray(ref T[]? value) where T : unmanaged { DangerousReadUnmanagedArray(ref value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadUnmanagedSpan(ref Span value) where T : unmanaged { @@ -524,6 +616,7 @@ public void ReadUnmanagedSpan(ref Span value) } // T: should be unamanged type + [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe T[]? DangerousReadUnmanagedArray() { if (!TryReadCollectionHeader(out var length)) @@ -542,6 +635,7 @@ public void ReadUnmanagedSpan(ref Span value) return dest; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void DangerousReadUnmanagedArray(ref T[]? value) { if (!TryReadCollectionHeader(out var length)) @@ -570,6 +664,7 @@ public unsafe void DangerousReadUnmanagedArray(ref T[]? value) Advance(byteCount); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void DangerousReadUnmanagedSpan(ref Span value) { if (!TryReadCollectionHeader(out var length)) @@ -600,6 +695,7 @@ public unsafe void DangerousReadUnmanagedSpan(ref Span value) #endregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ReadSpanWithoutReadLengthHeader(int length, ref Span value) { if (length == 0) @@ -636,6 +732,49 @@ public void ReadSpanWithoutReadLengthHeader(int length, ref Span value) } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ReadPackableSpanWithoutReadLengthHeader(int length, ref Span value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + ReadSpanWithoutReadLengthHeader(length, ref value); + return; +#else + if (length == 0) + { + value = Array.Empty(); + return; + } + + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + if (value.Length != length) + { + value = AllocateUninitializedArray(length); + } + + var byteCount = length * Unsafe.SizeOf(); + ref var src = ref GetSpanReference(byteCount); + ref var dest = ref Unsafe.As(ref MemoryMarshal.GetReference(value)!); + Unsafe.CopyBlockUnaligned(ref dest, ref src, (uint)byteCount); + + Advance(byteCount); + } + else + { + if (value.Length != length) + { + value = new T[length]; + } + + for (int i = 0; i < length; i++) + { + T.Deserialize(ref this, ref value[i]); + } + } +#endif + } } } \ No newline at end of file diff --git a/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/MemoryPackWriter.cs b/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/MemoryPackWriter.cs index e59d2366..3c629e7e 100644 --- a/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/MemoryPackWriter.cs +++ b/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/MemoryPackWriter.cs @@ -505,9 +505,82 @@ public void WriteSpan(ReadOnlySpan value) } } - #endregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackableArray(T?[]? value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + WriteArray(value); + return; +#else + + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousWriteUnmanagedArray(value); + return; + } + + if (value == null) + { + WriteNullCollectionHeader(); + return; + } + + WriteCollectionHeader(value.Length); + for (int i = 0; i < value.Length; i++) + { + T.Serialize(ref this, ref value[i]); + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackableSpan(Span value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + WriteSpan(value); + return; +#else + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousWriteUnmanagedSpan(value); + return; + } + + WriteCollectionHeader(value.Length); + for (int i = 0; i < value.Length; i++) + { + T.Serialize(ref this, ref value[i]); + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePackableSpan(ReadOnlySpan value) + where T : IMemoryPackable + { +#if !NET7_0_OR_GREATER + WriteSpan(value); + return; +#else + if (!RuntimeHelpers.IsReferenceOrContainsReferences()) + { + DangerousWriteUnmanagedSpan(value); + return; + } + + WriteCollectionHeader(value.Length); + for (int i = 0; i < value.Length; i++) + { + T.Serialize(ref this, ref Unsafe.AsRef(value[i])); + } +#endif + } + +#endregion - #region WriteUnmanagedArray/Span +#region WriteUnmanagedArray/Span [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteUnmanagedArray(T[]? value) @@ -598,7 +671,7 @@ public void DangerousWriteUnmanagedSpan(ReadOnlySpan value) Advance(allocSize); } - #endregion +#endregion [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Generator/MemoryPack.Generator.Roslyn3.dll b/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Generator/MemoryPack.Generator.Roslyn3.dll index 32fb1ca1..c7bcdf31 100644 Binary files a/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Generator/MemoryPack.Generator.Roslyn3.dll and b/src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Generator/MemoryPack.Generator.Roslyn3.dll differ diff --git a/tests/MemoryPack.Tests/ArrayTest.cs b/tests/MemoryPack.Tests/ArrayTest.cs index f3b26ccc..d6b13e08 100644 --- a/tests/MemoryPack.Tests/ArrayTest.cs +++ b/tests/MemoryPack.Tests/ArrayTest.cs @@ -28,4 +28,27 @@ public void Check() v2!.Array3.Should().Equal(checker.Array3); v2!.Array4.Should().Equal(checker.Array4); } + + [Fact] + public void Check2() + { + var checker = new ArrayOptimizeCheck() + { + Array1 = new[] { new StandardTypeTwo { One = 9, Two = 2 }, new StandardTypeTwo { One = 999, Two = 444 } }, + List1 = new List { new StandardTypeTwo { One = 93, Two = 12 }, new StandardTypeTwo { One = 9499, Two = 45344 } } + }; + + var bin = MemoryPackSerializer.Serialize(checker); + var v2 = MemoryPackSerializer.Deserialize(bin); +#pragma warning disable CS8602 + v2!.Array1[0].One.Should().Be(checker.Array1[0].One); + v2!.Array1[0].Two.Should().Be(checker.Array1[0].Two); + v2!.Array1[1].One.Should().Be(checker.Array1[1].One); + v2!.Array1[1].Two.Should().Be(checker.Array1[1].Two); + + v2!.List1[0].One.Should().Be(checker.List1[0].One); + v2!.List1[0].Two.Should().Be(checker.List1[0].Two); + v2!.List1[1].One.Should().Be(checker.List1[1].One); + v2!.List1[1].Two.Should().Be(checker.List1[1].Two); + } } diff --git a/tests/MemoryPack.Tests/Models/Arrays.cs b/tests/MemoryPack.Tests/Models/Arrays.cs index 322451e4..c2d4eb5f 100644 --- a/tests/MemoryPack.Tests/Models/Arrays.cs +++ b/tests/MemoryPack.Tests/Models/Arrays.cs @@ -15,3 +15,11 @@ public partial class ArrayCheck public string[]? Array3 { get; set; } public string?[]? Array4 { get; set; } } + + +[MemoryPackable] +public partial class ArrayOptimizeCheck +{ + public StandardTypeTwo?[]? Array1 { get; set; } + public List? List1 { get; set; } +}