diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8eeb5aa0e..9bc5189476 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Build run: dotnet build - uses: actions/upload-artifact@v3 @@ -49,6 +53,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v2 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --logger "trx" -- -parallel none -noshadow env: @@ -85,6 +93,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v2 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --logger "trx" -- -parallel none -noshadow env: @@ -117,6 +129,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --logger "trx" -- -parallel none -noshadow env: @@ -153,6 +169,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --logger "trx" -- -parallel none -noshadow env: @@ -187,6 +207,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&Category=${{ matrix.suite }}" --framework ${{ matrix.framework }} --blame-hang-timeout 10m --logger "trx" -- -parallel none -noshadow env: @@ -220,6 +244,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --logger "trx" -- -parallel none -noshadow env: @@ -253,6 +281,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --logger "trx" -- -parallel none -noshadow env: @@ -287,6 +319,10 @@ jobs: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 + with: + dotnet-version: | + 3.1.x + 7.0.x - name: Test run: dotnet test --filter "Category=${{ matrix.provider }}&(Category=BVT|Category=SlowBVT|Category=Functional)" --blame-hang-timeout 10m --logger "trx" -- -parallel none -noshadow env: diff --git a/Directory.Packages.props b/Directory.Packages.props index 3ff6b4b955..426dc8975d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,6 +18,7 @@ + @@ -27,6 +28,7 @@ + diff --git a/src/Orleans.CodeGenerator/LibraryTypes.cs b/src/Orleans.CodeGenerator/LibraryTypes.cs index 64832ab1d2..e0a2ff1501 100644 --- a/src/Orleans.CodeGenerator/LibraryTypes.cs +++ b/src/Orleans.CodeGenerator/LibraryTypes.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -66,17 +67,17 @@ public static LibraryTypes FromCompilation(Compilation compilation, CodeGenerato Task_1 = Type("System.Threading.Tasks.Task`1"), Type = Type("System.Type"), Uri = Type("System.Uri"), - Int128 = Type("System.Int128"), - UInt128 = Type("System.UInt128"), - Half = Type("System.Half"), - DateOnly = Type("System.DateOnly"), + Int128 = TypeOrDefault("System.Int128"), + UInt128 = TypeOrDefault("System.UInt128"), + Half = TypeOrDefault("System.Half"), + DateOnly = TypeOrDefault("System.DateOnly"), DateTimeOffset = Type("System.DateTimeOffset"), BitVector32 = Type("System.Collections.Specialized.BitVector32"), Guid = Type("System.Guid"), CompareInfo = Type("System.Globalization.CompareInfo"), CultureInfo = Type("System.Globalization.CultureInfo"), Version = Type("System.Version"), - TimeOnly = Type("System.TimeOnly"), + TimeOnly = TypeOrDefault("System.TimeOnly"), ICodecProvider = Type("Orleans.Serialization.Serializers.ICodecProvider"), ValueSerializer = Type("Orleans.Serialization.Serializers.IValueSerializer`1"), ValueTask = Type("System.Threading.Tasks.ValueTask"), @@ -86,7 +87,7 @@ public static LibraryTypes FromCompilation(Compilation compilation, CodeGenerato Writer = Type("Orleans.Serialization.Buffers.Writer`1"), FSharpSourceConstructFlagsOrDefault = TypeOrDefault("Microsoft.FSharp.Core.SourceConstructFlags"), FSharpCompilationMappingAttributeOrDefault = TypeOrDefault("Microsoft.FSharp.Core.CompilationMappingAttribute"), - StaticCodecs = new WellKnownCodecDescription[] + StaticCodecs = new List { new(compilation.GetSpecialType(SpecialType.System_Object), Type("Orleans.Serialization.Codecs.ObjectCodec")), new(compilation.GetSpecialType(SpecialType.System_Boolean), Type("Orleans.Serialization.Codecs.BoolCodec")), @@ -107,19 +108,19 @@ public static LibraryTypes FromCompilation(Compilation compilation, CodeGenerato new(compilation.GetSpecialType(SpecialType.System_DateTime), Type("Orleans.Serialization.Codecs.DateTimeCodec")), new(Type("System.TimeSpan"), Type("Orleans.Serialization.Codecs.TimeSpanCodec")), new(Type("System.DateTimeOffset"), Type("Orleans.Serialization.Codecs.DateTimeOffsetCodec")), - new(Type("System.DateOnly"), Type("Orleans.Serialization.Codecs.DateOnlyCodec")), - new(Type("System.TimeOnly"), Type("Orleans.Serialization.Codecs.TimeOnlyCodec")), + new(TypeOrDefault("System.DateOnly"), TypeOrDefault("Orleans.Serialization.Codecs.DateOnlyCodec")), + new(TypeOrDefault("System.TimeOnly"), TypeOrDefault("Orleans.Serialization.Codecs.TimeOnlyCodec")), new(Type("System.Guid"), Type("Orleans.Serialization.Codecs.GuidCodec")), new(Type("System.Type"), Type("Orleans.Serialization.Codecs.TypeSerializerCodec")), new(Type("System.ReadOnlyMemory`1").Construct(compilation.GetSpecialType(SpecialType.System_Byte)), Type("Orleans.Serialization.Codecs.ReadOnlyMemoryOfByteCodec")), new(Type("System.Memory`1").Construct(compilation.GetSpecialType(SpecialType.System_Byte)), Type("Orleans.Serialization.Codecs.MemoryOfByteCodec")), new(Type("System.Net.IPAddress"), Type("Orleans.Serialization.Codecs.IPAddressCodec")), new(Type("System.Net.IPEndPoint"), Type("Orleans.Serialization.Codecs.IPEndPointCodec")), - new(Type("System.UInt128"), Type("Orleans.Serialization.Codecs.UInt128Codec")), - new(Type("System.Int128"), Type("Orleans.Serialization.Codecs.Int128Codec")), - new(Type("System.Half"), Type("Orleans.Serialization.Codecs.HalfCodec")), + new(TypeOrDefault("System.UInt128"), TypeOrDefault("Orleans.Serialization.Codecs.UInt128Codec")), + new(TypeOrDefault("System.Int128"), TypeOrDefault("Orleans.Serialization.Codecs.Int128Codec")), + new(TypeOrDefault("System.Half"), TypeOrDefault("Orleans.Serialization.Codecs.HalfCodec")), new(Type("System.Uri"), Type("Orleans.Serialization.Codecs.UriCodec")), - }, + }.Where(desc => desc.UnderlyingType is {} && desc.CodecType is {}).ToArray(), WellKnownCodecs = new WellKnownCodecDescription[] { new(Type("System.Exception"), Type("Orleans.Serialization.ExceptionCodec")), @@ -266,7 +267,7 @@ INamedTypeSymbol TypeOrDefault(string metadataName) private INamedTypeSymbol UInt128; private INamedTypeSymbol Half; private INamedTypeSymbol[] _regularShallowCopyableTypes; - private INamedTypeSymbol[] RegularShallowCopyableType => _regularShallowCopyableTypes ??= new[] + private INamedTypeSymbol[] RegularShallowCopyableType => _regularShallowCopyableTypes ??= new List { TimeSpan, DateOnly, @@ -285,7 +286,7 @@ INamedTypeSymbol TypeOrDefault(string metadataName) UInt128, Int128, Half - }; + }.Where(t => t is {}).ToArray(); public INamedTypeSymbol[] ImmutableAttributes { get; private set; } public INamedTypeSymbol Exception { get; private set; } diff --git a/src/Orleans.Serialization.FSharp/Orleans.Serialization.FSharp.csproj b/src/Orleans.Serialization.FSharp/Orleans.Serialization.FSharp.csproj index 08df809373..c8c398ca05 100644 --- a/src/Orleans.Serialization.FSharp/Orleans.Serialization.FSharp.csproj +++ b/src/Orleans.Serialization.FSharp/Orleans.Serialization.FSharp.csproj @@ -2,7 +2,7 @@ Microsoft.Orleans.Serialization.FSharp - $(DefaultTargetFrameworks) + $(DefaultTargetFrameworks);netstandard2.1 F# core type support for Orleans.Serialization true false diff --git a/src/Orleans.Serialization.NewtonsoftJson/Orleans.Serialization.NewtonsoftJson.csproj b/src/Orleans.Serialization.NewtonsoftJson/Orleans.Serialization.NewtonsoftJson.csproj index b2f9b55bf5..bc344b7d10 100644 --- a/src/Orleans.Serialization.NewtonsoftJson/Orleans.Serialization.NewtonsoftJson.csproj +++ b/src/Orleans.Serialization.NewtonsoftJson/Orleans.Serialization.NewtonsoftJson.csproj @@ -2,7 +2,7 @@ Microsoft.Orleans.Serialization.NewtonsoftJson - $(DefaultTargetFrameworks) + $(DefaultTargetFrameworks);netstandard2.1 Newtonsoft.Json integration for Orleans.Serialization true false diff --git a/src/Orleans.Serialization.SystemTextJson/Orleans.Serialization.SystemTextJson.csproj b/src/Orleans.Serialization.SystemTextJson/Orleans.Serialization.SystemTextJson.csproj index 5f064e0cc6..5b53249820 100644 --- a/src/Orleans.Serialization.SystemTextJson/Orleans.Serialization.SystemTextJson.csproj +++ b/src/Orleans.Serialization.SystemTextJson/Orleans.Serialization.SystemTextJson.csproj @@ -2,7 +2,7 @@ Microsoft.Orleans.Serialization.SystemTextJson - $(DefaultTargetFrameworks) + $(DefaultTargetFrameworks);netstandard2.1 System.Text.Json integration for Orleans.Serialization true false diff --git a/src/Orleans.Serialization.TestKit/CopierTester.cs b/src/Orleans.Serialization.TestKit/CopierTester.cs index 04333b44db..aed279cc80 100644 --- a/src/Orleans.Serialization.TestKit/CopierTester.cs +++ b/src/Orleans.Serialization.TestKit/CopierTester.cs @@ -18,7 +18,11 @@ public abstract class CopierTester where TCopier : class, IDeep protected CopierTester(ITestOutputHelper output) { +#if NET6_0_OR_GREATER var seed = Random.Shared.Next(); +#else + var seed = new Random().Next(); +#endif output.WriteLine($"Random seed: {seed}"); Random = new(seed); var services = new ServiceCollection(); @@ -51,7 +55,7 @@ protected virtual void Configure(ISerializerBuilder builder) protected virtual bool Equals(TValue left, TValue right) => EqualityComparer.Default.Equals(left, right); protected virtual Action> ValueProvider { get; } - + [Fact] public void CopiedValuesAreEqual() { diff --git a/src/Orleans.Serialization.TestKit/FieldCodecTester.cs b/src/Orleans.Serialization.TestKit/FieldCodecTester.cs index 8f57cc8b73..911280e01d 100644 --- a/src/Orleans.Serialization.TestKit/FieldCodecTester.cs +++ b/src/Orleans.Serialization.TestKit/FieldCodecTester.cs @@ -26,7 +26,11 @@ public abstract class FieldCodecTester : IDisposable where TCode protected FieldCodecTester(ITestOutputHelper output) { +#if NET6_0_OR_GREATER var seed = Random.Shared.Next(); +#else + var seed = new Random().Next(); +#endif output.WriteLine($"Random seed: {seed}"); Random = new(seed); var services = new ServiceCollection(); @@ -121,7 +125,7 @@ public void CorrectlyAdvancesReferenceCounterStream() public void CorrectlyAdvancesReferenceCounter() { var pipe = new Pipe(); - using var writerSession = _sessionPool.GetSession(); + using var writerSession = _sessionPool.GetSession(); var writer = Writer.Create(pipe.Writer, writerSession); var writerCodec = CreateCodec(); var beforeReference = writer.Session.ReferencedObjects.CurrentReferenceId; @@ -139,7 +143,7 @@ public void CorrectlyAdvancesReferenceCounter() pipe.Writer.Complete(); _ = pipe.Reader.TryRead(out var readResult); - using var readerSession = _sessionPool.GetSession(); + using var readerSession = _sessionPool.GetSession(); var reader = Reader.Create(readResult.Buffer, readerSession); var previousPos = reader.Position; diff --git a/src/Orleans.Serialization.TestKit/Orleans.Serialization.TestKit.csproj b/src/Orleans.Serialization.TestKit/Orleans.Serialization.TestKit.csproj index a480fc32a7..8b1c44e111 100644 --- a/src/Orleans.Serialization.TestKit/Orleans.Serialization.TestKit.csproj +++ b/src/Orleans.Serialization.TestKit/Orleans.Serialization.TestKit.csproj @@ -2,7 +2,7 @@ Microsoft.Orleans.Serialization.TestKit - $(DefaultTargetFrameworks) + $(DefaultTargetFrameworks);netstandard2.1 Test kit for projects using Orleans.Serialization false diff --git a/src/Orleans.Serialization/Activators/DefaultActivator.cs b/src/Orleans.Serialization/Activators/DefaultActivator.cs index 0f1a3754cf..2177eedab4 100644 --- a/src/Orleans.Serialization/Activators/DefaultActivator.cs +++ b/src/Orleans.Serialization/Activators/DefaultActivator.cs @@ -21,7 +21,7 @@ private static Func Init() var il = method.GetILGenerator(); il.Emit(OpCodes.Newobj, ctor); il.Emit(OpCodes.Ret); - return method.CreateDelegate>(); + return (Func)method.CreateDelegate(typeof(Func)); } public T Create() => _constructor is { } ctor ? ctor() : Unsafe.As(FormatterServices.GetUninitializedObject(_type)); diff --git a/src/Orleans.Serialization/Buffers/Adaptors/PooledArrayBufferWriter.cs b/src/Orleans.Serialization/Buffers/Adaptors/PooledArrayBufferWriter.cs index e7416cdc2d..5f01560a76 100644 --- a/src/Orleans.Serialization/Buffers/Adaptors/PooledArrayBufferWriter.cs +++ b/src/Orleans.Serialization/Buffers/Adaptors/PooledArrayBufferWriter.cs @@ -201,7 +201,12 @@ private SequenceSegment(int length) { if (length <= MinimumBlockSize) { +#if NET6_0_OR_GREATER var pinnedArray = GC.AllocateUninitializedArray(MinimumBlockSize, pinned: true); +#else + // Note: Not actually pinned in this case since it just a potential fragmentation optimization + var pinnedArray = new byte[MinimumBlockSize]; +#endif Array = pinnedArray; } else @@ -218,20 +223,24 @@ private SequenceSegment(int length) public Memory AsMemory(int offset) { +#if NET6_0_OR_GREATER if (IsStandardSize) { return MemoryMarshal.CreateFromPinnedArray(Array, offset, Array.Length); } +#endif return Array.AsMemory(offset); } public Memory AsMemory(int offset, int length) { +#if NET6_0_OR_GREATER if (IsStandardSize) { return MemoryMarshal.CreateFromPinnedArray(Array, offset, length); } +#endif return Array.AsMemory(offset, length); } diff --git a/src/Orleans.Serialization/Buffers/BufferWriterExtensions.cs b/src/Orleans.Serialization/Buffers/BufferWriterExtensions.cs index 540b33dc72..b6ea3fbe3d 100644 --- a/src/Orleans.Serialization/Buffers/BufferWriterExtensions.cs +++ b/src/Orleans.Serialization/Buffers/BufferWriterExtensions.cs @@ -20,7 +20,11 @@ public static class BufferWriterExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Writer CreateWriter(this TBufferWriter buffer, SerializerSession session) where TBufferWriter : IBufferWriter { +#if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(session); +#else + if (session is null) throw new ArgumentNullException(nameof(session)); +#endif return Writer.Create(buffer, session); } } diff --git a/src/Orleans.Serialization/Buffers/Reader.cs b/src/Orleans.Serialization/Buffers/Reader.cs index b2dcba3c54..7a2ac724c1 100644 --- a/src/Orleans.Serialization/Buffers/Reader.cs +++ b/src/Orleans.Serialization/Buffers/Reader.cs @@ -115,33 +115,11 @@ public override byte ReadByte() [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void ReadBytes(Span destination) { -#if NETCOREAPP3_1_OR_GREATER var count = _stream.Read(destination); if (count < destination.Length) { ThrowInsufficientData(); } -#else - byte[] array = default; - try - { - array = _memoryPool.Rent(destination.Length); - var count = _stream.Read(array, 0, destination.Length); - if (count < destination.Length) - { - ThrowInsufficientData(); - } - - array.CopyTo(destination); - } - finally - { - if (array is object) - { - _memoryPool.Return(array); - } - } -#endif } public override void ReadBytes(byte[] destination, int offset, int length) @@ -158,15 +136,9 @@ public override void ReadBytes(byte[] destination, int offset, int length) #endif public override uint ReadUInt32() { -#if NETCOREAPP3_1_OR_GREATER Span buffer = stackalloc byte[sizeof(uint)]; ReadBytes(buffer); return BinaryPrimitives.ReadUInt32LittleEndian(buffer); -#else - var buffer = GetScratchBuffer(); - ReadBytes(buffer, 0, sizeof(uint)); - return BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(0, sizeof(uint))); -#endif } #if NET5_0_OR_GREATER @@ -174,15 +146,9 @@ public override uint ReadUInt32() #endif public override ulong ReadUInt64() { -#if NETCOREAPP3_1_OR_GREATER Span buffer = stackalloc byte[sizeof(ulong)]; ReadBytes(buffer); return BinaryPrimitives.ReadUInt64LittleEndian(buffer); -#else - var buffer = GetScratchBuffer(); - ReadBytes(buffer, 0, sizeof(ulong)); - return BinaryPrimitives.ReadUInt64LittleEndian(buffer.AsSpan(0, sizeof(ulong))); -#endif } public override void Skip(long count) => _ = _stream.Seek(count, SeekOrigin.Current); diff --git a/src/Orleans.Serialization/Cloning/IDeepCopier.cs b/src/Orleans.Serialization/Cloning/IDeepCopier.cs index d212ddb7d2..73c3655f62 100644 --- a/src/Orleans.Serialization/Cloning/IDeepCopier.cs +++ b/src/Orleans.Serialization/Cloning/IDeepCopier.cs @@ -274,8 +274,10 @@ internal static class ShallowCopyableTypes { [typeof(decimal)] = true, [typeof(DateTime)] = true, +#if NET6_0_OR_GREATER [typeof(DateOnly)] = true, [typeof(TimeOnly)] = true, +#endif [typeof(DateTimeOffset)] = true, [typeof(TimeSpan)] = true, [typeof(IPAddress)] = true, @@ -288,9 +290,13 @@ internal static class ShallowCopyableTypes [typeof(CultureInfo)] = true, [typeof(Version)] = true, [typeof(Uri)] = true, +#if NET7_0_OR_GREATER [typeof(UInt128)] = true, [typeof(Int128)] = true, +#endif +#if NET5_0_OR_GREATER [typeof(Half)] = true, +#endif }; public static bool Contains(Type type) @@ -365,7 +371,7 @@ internal sealed class UntypedCopierWrapper : IDeepCopier /// /// Object pool for instances. /// - public sealed class CopyContextPool + public sealed class CopyContextPool { private readonly ConcurrentObjectPool _pool; diff --git a/src/Orleans.Serialization/Codecs/ConcurrentDictionaryCodec.cs b/src/Orleans.Serialization/Codecs/ConcurrentDictionaryCodec.cs index 81b0f64fb9..545d30e45d 100644 --- a/src/Orleans.Serialization/Codecs/ConcurrentDictionaryCodec.cs +++ b/src/Orleans.Serialization/Codecs/ConcurrentDictionaryCodec.cs @@ -27,7 +27,11 @@ public override ConcurrentDictionary ConvertFromSurrogate(ref Conc /// public override void ConvertToSurrogate(ConcurrentDictionary value, ref ConcurrentDictionarySurrogate surrogate) +#if NET6_0_OR_GREATER => surrogate.Values = new(value, value.Comparer); +#else + => surrogate.Values = new(value); +#endif } /// @@ -81,7 +85,11 @@ public ConcurrentDictionary DeepCopy(ConcurrentDictionary(ref Reader reader, Field field) return DateOnly.FromDayNumber(reader.ReadInt32()); } } +#endif \ No newline at end of file diff --git a/src/Orleans.Serialization/Codecs/FloatCodec.cs b/src/Orleans.Serialization/Codecs/FloatCodec.cs index b4e85dead5..65bf07f30a 100644 --- a/src/Orleans.Serialization/Codecs/FloatCodec.cs +++ b/src/Orleans.Serialization/Codecs/FloatCodec.cs @@ -19,7 +19,11 @@ void IFieldCodec.WriteField(ref Writer writ { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(float), WireType.Fixed32); +#if NET6_0_OR_GREATER writer.WriteUInt32(BitConverter.SingleToUInt32Bits(value)); +#else + writer.WriteUInt32((uint)BitConverter.SingleToInt32Bits(value)); +#endif } /// @@ -34,7 +38,11 @@ public static void WriteField(ref Writer writer, u { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.Fixed32); +#if NET6_0_OR_GREATER writer.WriteUInt32(BitConverter.SingleToUInt32Bits(value)); +#else + writer.WriteUInt32((uint)BitConverter.SingleToInt32Bits(value)); +#endif } /// @@ -67,9 +75,10 @@ public static float ReadValue(ref Reader reader, Field field) case WireType.LengthPrefixed: return (float)DecimalCodec.ReadDecimalRaw(ref reader); - +#if NET6_0_OR_GREATER case WireType.VarInt: return (float)HalfCodec.ReadHalfRaw(ref reader); +#endif default: ThrowWireTypeOutOfRange(field.WireType); return 0; @@ -83,7 +92,11 @@ public static float ReadValue(ref Reader reader, Field field) /// The reader. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET6_0_OR_GREATER public static float ReadFloatRaw(ref Reader reader) => BitConverter.UInt32BitsToSingle(reader.ReadUInt32()); +#else + public static float ReadFloatRaw(ref Reader reader) => BitConverter.Int32BitsToSingle((int)reader.ReadUInt32()); +#endif private static void ThrowWireTypeOutOfRange(WireType wireType) => throw new UnsupportedWireTypeException( $"WireType {wireType} is not supported by this codec."); @@ -102,7 +115,11 @@ void IFieldCodec.WriteField(ref Writer wri { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeader(fieldIdDelta, expectedType, typeof(double), WireType.Fixed64); +#if NET6_0_OR_GREATER writer.WriteUInt64(BitConverter.DoubleToUInt64Bits(value)); +#else + writer.WriteUInt64((ulong)BitConverter.DoubleToInt64Bits(value)); +#endif } /// @@ -117,7 +134,11 @@ public static void WriteField(ref Writer writer, u { ReferenceCodec.MarkValueField(writer.Session); writer.WriteFieldHeaderExpected(fieldIdDelta, WireType.Fixed64); +#if NET6_0_OR_GREATER writer.WriteUInt64(BitConverter.DoubleToUInt64Bits(value)); +#else + writer.WriteUInt64((ulong)BitConverter.DoubleToInt64Bits(value)); +#endif } /// @@ -141,8 +162,10 @@ public static double ReadValue(ref Reader reader, Field field) return ReadDoubleRaw(ref reader); case WireType.LengthPrefixed: return (double)DecimalCodec.ReadDecimalRaw(ref reader); +#if NET6_0_OR_GREATER case WireType.VarInt: return (double)HalfCodec.ReadHalfRaw(ref reader); +#endif default: ThrowWireTypeOutOfRange(field.WireType); return 0; @@ -156,7 +179,11 @@ public static double ReadValue(ref Reader reader, Field field) /// The reader. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET6_0_OR_GREATER public static double ReadDoubleRaw(ref Reader reader) => BitConverter.UInt64BitsToDouble(reader.ReadUInt64()); +#else + public static double ReadDoubleRaw(ref Reader reader) => BitConverter.Int64BitsToDouble((long)reader.ReadUInt64()); +#endif private static void ThrowWireTypeOutOfRange(WireType wireType) => throw new UnsupportedWireTypeException( $"WireType {wireType} is not supported by this codec."); @@ -197,11 +224,13 @@ private static void WriteRaw(ref Writer writer, re { writer.WriteVarUInt7(Width); +#if NET6_0_OR_GREATER if (BitConverter.IsLittleEndian) { writer.Write(MemoryMarshal.AsBytes(new Span(ref value))); return; } +#endif ref var holder = ref Unsafe.As(ref value); writer.WriteUInt32(holder.Flags); @@ -246,8 +275,10 @@ public static decimal ReadValue(ref Reader reader, Field field) } case WireType.LengthPrefixed: return ReadDecimalRaw(ref reader); +#if NET6_0_OR_GREATER case WireType.VarInt: return (decimal)HalfCodec.ReadHalfRaw(ref reader); +#endif default: ThrowWireTypeOutOfRange(field.WireType); return 0; @@ -268,12 +299,14 @@ public static decimal ReadDecimalRaw(ref Reader reader) throw new UnexpectedLengthPrefixValueException("decimal", Width, length); } +#if NET6_0_OR_GREATER if (BitConverter.IsLittleEndian) { Unsafe.SkipInit(out decimal res); reader.ReadBytes(MemoryMarshal.AsBytes(new Span(ref res))); return res; } +#endif DecimalConverter holder; holder.Flags = reader.ReadUInt32(); @@ -296,6 +329,7 @@ private struct DecimalConverter $"The {typeof(T)} value has a magnitude too high {value} to be converted to {typeof(decimal)}."); } +#if NET6_0_OR_GREATER /// /// Serializer for . /// @@ -392,4 +426,5 @@ public static Half ReadValue(ref Reader reader, Field field) private static void ThrowValueOutOfRange(T value) => throw new OverflowException( $"The {typeof(T)} value has a magnitude too high {value} to be converted to {typeof(Half)}."); } +#endif } \ No newline at end of file diff --git a/src/Orleans.Serialization/Codecs/GuidCodec.cs b/src/Orleans.Serialization/Codecs/GuidCodec.cs index 270301e31f..52a20e5152 100644 --- a/src/Orleans.Serialization/Codecs/GuidCodec.cs +++ b/src/Orleans.Serialization/Codecs/GuidCodec.cs @@ -41,7 +41,7 @@ public static void WriteField(ref Writer writer, u /// Guid IFieldCodec.ReadValue(ref Reader reader, Field field) => ReadValue(ref reader, field); - + /// /// Reads a value. /// @@ -72,7 +72,17 @@ public static void WriteRaw(ref Writer writer, Gui { if (BitConverter.IsLittleEndian) { +#if NET7_0_OR_GREATER writer.Write(MemoryMarshal.AsBytes(new Span(ref value))); +#else + writer.EnsureContiguous(Width); + if (value.TryWriteBytes(writer.WritableSpan)) + { + writer.AdvanceSpan(Width); + return; + } + writer.Write(value.ToByteArray()); +#endif } else { @@ -89,6 +99,7 @@ public static void WriteRaw(ref Writer writer, Gui [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Guid ReadRaw(ref Reader reader) { +#if NET7_0_OR_GREATER Unsafe.SkipInit(out Guid res); var bytes = MemoryMarshal.AsBytes(new Span(ref res)); reader.ReadBytes(bytes); @@ -97,6 +108,17 @@ public static Guid ReadRaw(ref Reader reader) return res; return new Guid(bytes); +#else + if (reader.TryReadBytes(Width, out var readOnly)) + { + return new Guid(readOnly); + } + + Span bytes = stackalloc byte[Width]; + reader.ReadBytes(bytes); + + return new Guid(bytes); +#endif } } } \ No newline at end of file diff --git a/src/Orleans.Serialization/Codecs/IntegerCodec.cs b/src/Orleans.Serialization/Codecs/IntegerCodec.cs index b73772888b..628206ca12 100644 --- a/src/Orleans.Serialization/Codecs/IntegerCodec.cs +++ b/src/Orleans.Serialization/Codecs/IntegerCodec.cs @@ -664,6 +664,7 @@ public static ulong ReadValue(ref Reader reader, Field field) } } +#if NET7_0_OR_GREATER /// /// Serializer for . /// @@ -851,4 +852,5 @@ internal static UInt128 ReadRaw(ref Reader reader) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryReadLittleEndian(ReadOnlySpan source, out T value) where T : IBinaryInteger => T.TryReadLittleEndian(source, isUnsigned: true, out value); } +#endif } diff --git a/src/Orleans.Serialization/Codecs/TimeOnlyCodec.cs b/src/Orleans.Serialization/Codecs/TimeOnlyCodec.cs index a3f8b5c199..cc76cec2b8 100644 --- a/src/Orleans.Serialization/Codecs/TimeOnlyCodec.cs +++ b/src/Orleans.Serialization/Codecs/TimeOnlyCodec.cs @@ -1,3 +1,4 @@ +#if NET6_0_OR_GREATER using System; using System.Buffers; using System.Runtime.CompilerServices; @@ -40,4 +41,5 @@ public static TimeOnly ReadValue(ref Reader reader, Field field) field.EnsureWireType(WireType.Fixed64); return new TimeOnly(reader.ReadInt64()); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/Orleans.Serialization/Codecs/WellKnownStringComparerCodec.cs b/src/Orleans.Serialization/Codecs/WellKnownStringComparerCodec.cs index 8362491318..9e3ea44f96 100644 --- a/src/Orleans.Serialization/Codecs/WellKnownStringComparerCodec.cs +++ b/src/Orleans.Serialization/Codecs/WellKnownStringComparerCodec.cs @@ -9,6 +9,10 @@ using Orleans.Serialization.Serializers; using Orleans.Serialization.WireProtocol; +#if !NET6_0_OR_GREATER +using System.Runtime.Serialization; +#endif + namespace Orleans.Serialization.Codecs { /// diff --git a/src/Orleans.Serialization/Configuration/DefaultTypeManifestProvider.cs b/src/Orleans.Serialization/Configuration/DefaultTypeManifestProvider.cs index c957eb9be8..5731b1ea95 100644 --- a/src/Orleans.Serialization/Configuration/DefaultTypeManifestProvider.cs +++ b/src/Orleans.Serialization/Configuration/DefaultTypeManifestProvider.cs @@ -37,8 +37,10 @@ public void Configure(TypeManifestOptions typeManifest) wellKnownTypes[25] = typeof(int[]); wellKnownTypes[26] = typeof(string[]); wellKnownTypes[27] = typeof(Type); +#if NET6_0_OR_GREATER wellKnownTypes[28] = typeof(DateOnly); wellKnownTypes[29] = typeof(TimeOnly); +#endif wellKnownTypes[30] = typeof(DayOfWeek); wellKnownTypes[31] = typeof(Uri); wellKnownTypes[32] = typeof(Version); @@ -46,9 +48,13 @@ public void Configure(TypeManifestOptions typeManifest) wellKnownTypes[34] = typeof(IPEndPoint); wellKnownTypes[35] = typeof(ExceptionResponse); wellKnownTypes[36] = typeof(CompletedResponse); +#if NET7_0_OR_GREATER wellKnownTypes[37] = typeof(Int128); wellKnownTypes[38] = typeof(UInt128); +#endif +#if NET5_0_OR_GREATER wellKnownTypes[39] = typeof(Half); +#endif var allowedTypes = typeManifest.AllowedTypes; allowedTypes.Add("System.Globalization.CompareOptions"); diff --git a/src/Orleans.Serialization/Hosting/ReferencedAssemblyHelper.cs b/src/Orleans.Serialization/Hosting/ReferencedAssemblyHelper.cs index 6d09e302ee..46c786b5d3 100644 --- a/src/Orleans.Serialization/Hosting/ReferencedAssemblyHelper.cs +++ b/src/Orleans.Serialization/Hosting/ReferencedAssemblyHelper.cs @@ -83,7 +83,7 @@ public static void AddFromAssemblyLoadContext(HashSet parts, Assembly if (!asm.IsDefined(typeof(ApplicationPartAttribute)) || !assemblies.Add(asm)) { continue; - } + } AddAssembly(parts, asm); } diff --git a/src/Orleans.Serialization/ISerializableSerializer/SerializationCallbacksFactory.cs b/src/Orleans.Serialization/ISerializableSerializer/SerializationCallbacksFactory.cs index 7a491dde5e..65bfc88f13 100644 --- a/src/Orleans.Serialization/ISerializableSerializer/SerializationCallbacksFactory.cs +++ b/src/Orleans.Serialization/ISerializableSerializer/SerializationCallbacksFactory.cs @@ -60,22 +60,22 @@ private static SerializationCallbacks CreateTypedCallbacks if (method.IsDefined(typeof(OnDeserializingAttribute), false)) { - onDeserializing = GetSerializationMethod(type, method, owner).CreateDelegate(); + onDeserializing = (TDelegate)GetSerializationMethod(type, method, owner).CreateDelegate(typeof(TDelegate)); } if (method.IsDefined(typeof(OnDeserializedAttribute), false)) { - onDeserialized = GetSerializationMethod(type, method, owner).CreateDelegate(); + onDeserialized = (TDelegate)GetSerializationMethod(type, method, owner).CreateDelegate(typeof(TDelegate)); } if (method.IsDefined(typeof(OnSerializingAttribute), false)) { - onSerializing = GetSerializationMethod(type, method, owner).CreateDelegate(); + onSerializing = (TDelegate)GetSerializationMethod(type, method, owner).CreateDelegate(typeof(TDelegate)); } if (method.IsDefined(typeof(OnSerializedAttribute), false)) { - onSerialized = GetSerializationMethod(type, method, owner).CreateDelegate(); + onSerialized = (TDelegate)GetSerializationMethod(type, method, owner).CreateDelegate(typeof(TDelegate)); } } @@ -154,7 +154,7 @@ public SerializationCallbacks( public readonly TDelegate OnSerializing; /// - /// Gets the callback invoked once a value has been serialized. + /// Gets the callback invoked once a value has been serialized. /// public readonly TDelegate OnSerialized; } diff --git a/src/Orleans.Serialization/Orleans.Serialization.csproj b/src/Orleans.Serialization/Orleans.Serialization.csproj index 1455a5063b..e87c5f4b5b 100644 --- a/src/Orleans.Serialization/Orleans.Serialization.csproj +++ b/src/Orleans.Serialization/Orleans.Serialization.csproj @@ -3,7 +3,7 @@ Microsoft.Orleans.Serialization Fast, flexible, and version-tolerant serializer for .NET - $(DefaultTargetFrameworks) + $(DefaultTargetFrameworks);netstandard2.1 true true false @@ -17,6 +17,11 @@ + + + + + diff --git a/src/Orleans.Serialization/Session/ReferencedObjectCollection.cs b/src/Orleans.Serialization/Session/ReferencedObjectCollection.cs index b3ba1880a5..7066806f21 100644 --- a/src/Orleans.Serialization/Session/ReferencedObjectCollection.cs +++ b/src/Orleans.Serialization/Session/ReferencedObjectCollection.cs @@ -4,7 +4,9 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +#if NET6_0_OR_GREATER using System.Reflection.Metadata; +#endif using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -100,6 +102,7 @@ public bool GetOrAddReference(object value, out uint reference) if (_objectToReferenceOverflow is { } overflow) { +#if NET6_0_OR_GREATER ref var refValue = ref CollectionsMarshal.GetValueRefOrAddDefault(overflow, value, out var exists); if (exists) { @@ -110,6 +113,19 @@ public bool GetOrAddReference(object value, out uint reference) refValue = nextReference; Unsafe.SkipInit(out reference); return false; +#else + if (overflow.TryGetValue(value, out var existing)) + { + reference = existing; + return true; + } + else + { + overflow[value] = nextReference; + Unsafe.SkipInit(out reference); + return false; + } +#endif } // Add the reference. @@ -177,6 +193,7 @@ private void AddToReferences(object value, uint reference) { if (_referenceToObjectOverflow is { } overflow) { +#if NET6_0_OR_GREATER ref var refValue = ref CollectionsMarshal.GetValueRefOrAddDefault(overflow, reference, out var exists); if (exists && value is not UnknownFieldMarker && refValue is not UnknownFieldMarker) { @@ -185,6 +202,15 @@ private void AddToReferences(object value, uint reference) } refValue = value; +#else + if (overflow.TryGetValue(reference, out var existing) && value is not UnknownFieldMarker && existing is not UnknownFieldMarker) + { + // Unknown field markers can be replaced once the type is known. + ThrowReferenceExistsException(reference); + } + + overflow[reference] = value; +#endif } else { @@ -228,6 +254,7 @@ void CreateReferenceToObjectOverflow() } } + [DoesNotReturn] private static void ThrowReferenceExistsException(uint reference) => throw new InvalidOperationException($"Reference {reference} already exists"); /// diff --git a/src/Orleans.Serialization/Session/ReferencedTypeCollection.cs b/src/Orleans.Serialization/Session/ReferencedTypeCollection.cs index b096429fd0..8ef872d74d 100644 --- a/src/Orleans.Serialization/Session/ReferencedTypeCollection.cs +++ b/src/Orleans.Serialization/Session/ReferencedTypeCollection.cs @@ -56,11 +56,22 @@ public Type GetReferencedType(uint reference) [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint GetOrAddTypeReference(Type type) { +#if NET6_0_OR_GREATER ref var refValue = ref CollectionsMarshal.GetValueRefOrAddDefault(_referencedTypeToIdMap, type, out var exists); if (exists) return refValue; refValue = ++_currentReferenceId; +#else + if (_referencedTypeToIdMap.TryGetValue(type, out var existing)) + { + return existing; + } + else + { + _referencedTypeToIdMap[type] = ++_currentReferenceId; + } +#endif return 0; } diff --git a/src/Orleans.Serialization/TypeSystem/CompoundTypeAliasTree.cs b/src/Orleans.Serialization/TypeSystem/CompoundTypeAliasTree.cs index 8575c4cf70..82eea8de9d 100644 --- a/src/Orleans.Serialization/TypeSystem/CompoundTypeAliasTree.cs +++ b/src/Orleans.Serialization/TypeSystem/CompoundTypeAliasTree.cs @@ -82,7 +82,11 @@ internal bool TryGetChild(object key, out CompoundTypeAliasTree? result) private CompoundTypeAliasTree AddInternal(object key) => AddInternal(key, default); private CompoundTypeAliasTree AddInternal(object key, Type? value) { +#if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(key, nameof(key)); +#else + if (key is null) throw new ArgumentNullException(nameof(key)); +#endif _children ??= new(); if (_children.TryGetValue(key, out var existing)) diff --git a/src/Orleans.Serialization/Utilities/BitOperations.cs b/src/Orleans.Serialization/Utilities/BitOperations.cs index 6c0e61a175..0c7a5dc70d 100644 --- a/src/Orleans.Serialization/Utilities/BitOperations.cs +++ b/src/Orleans.Serialization/Utilities/BitOperations.cs @@ -1,3 +1,7 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + namespace Orleans.Serialization.Utilities { #if NETCOREAPP3_1_OR_GREATER diff --git a/src/Orleans.Serialization/Utilities/VarIntReaderExtensions.cs b/src/Orleans.Serialization/Utilities/VarIntReaderExtensions.cs index 3dd74611e2..423cad2236 100644 --- a/src/Orleans.Serialization/Utilities/VarIntReaderExtensions.cs +++ b/src/Orleans.Serialization/Utilities/VarIntReaderExtensions.cs @@ -77,7 +77,9 @@ public static class VarIntReaderExtensions WireType.VarInt => reader.ReadVarUInt8(), WireType.Fixed32 => checked((byte)reader.ReadUInt32()), WireType.Fixed64 => checked((byte)reader.ReadUInt64()), +#if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((byte)UInt128Codec.ReadRaw(ref reader)), +#endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; @@ -94,7 +96,9 @@ public static class VarIntReaderExtensions WireType.VarInt => reader.ReadVarUInt16(), WireType.Fixed32 => checked((ushort)reader.ReadUInt32()), WireType.Fixed64 => checked((ushort)reader.ReadUInt64()), +#if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((ushort)UInt128Codec.ReadRaw(ref reader)), +#endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; @@ -111,7 +115,9 @@ public static class VarIntReaderExtensions WireType.VarInt => reader.ReadVarUInt32(), WireType.Fixed32 => reader.ReadUInt32(), WireType.Fixed64 => checked((uint)reader.ReadUInt64()), +#if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((uint)UInt128Codec.ReadRaw(ref reader)), +#endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; @@ -128,7 +134,9 @@ public static class VarIntReaderExtensions WireType.VarInt => reader.ReadVarUInt64(), WireType.Fixed32 => reader.ReadUInt32(), WireType.Fixed64 => reader.ReadUInt64(), +#if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((ulong)UInt128Codec.ReadRaw(ref reader)), +#endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; @@ -145,7 +153,9 @@ public static class VarIntReaderExtensions WireType.VarInt => reader.ReadVarInt8(), WireType.Fixed32 => checked((sbyte)reader.ReadInt32()), WireType.Fixed64 => checked((sbyte)reader.ReadInt64()), +#if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((sbyte)Int128Codec.ReadRaw(ref reader)), +#endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; @@ -162,7 +172,9 @@ public static class VarIntReaderExtensions WireType.VarInt => reader.ReadVarInt16(), WireType.Fixed32 => checked((short)reader.ReadInt32()), WireType.Fixed64 => checked((short)reader.ReadInt64()), +#if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((short)Int128Codec.ReadRaw(ref reader)), +#endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; @@ -178,7 +190,7 @@ public static int ReadInt32(this ref Reader reader, WireType wir { if (wireType == WireType.VarInt) { - return reader.ReadVarInt32(); + return reader.ReadVarInt32(); } return ReadInt32Slower(ref reader, wireType); @@ -189,7 +201,9 @@ public static int ReadInt32(this ref Reader reader, WireType wir { WireType.Fixed32 => reader.ReadInt32(), WireType.Fixed64 => checked((int)reader.ReadInt64()), +#if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((int)Int128Codec.ReadRaw(ref reader)), +#endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; @@ -206,7 +220,9 @@ public static int ReadInt32(this ref Reader reader, WireType wir WireType.VarInt => reader.ReadVarInt64(), WireType.Fixed32 => reader.ReadInt32(), WireType.Fixed64 => reader.ReadInt64(), +#if NET7_0_OR_GREATER WireType.LengthPrefixed => checked((long)Int128Codec.ReadRaw(ref reader)), +#endif _ => ExceptionHelper.ThrowArgumentOutOfRange(nameof(wireType)), }; diff --git a/src/Orleans.Serialization/WireProtocol/Field.cs b/src/Orleans.Serialization/WireProtocol/Field.cs index fb5ae498fa..1cbb95d997 100644 --- a/src/Orleans.Serialization/WireProtocol/Field.cs +++ b/src/Orleans.Serialization/WireProtocol/Field.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using System.Text; namespace Orleans.Serialization.WireProtocol { @@ -247,6 +248,7 @@ public void EnsureWireType(WireType expectedType) /// public override string ToString() { +#if NET6_0_OR_GREATER var builder = new DefaultInterpolatedStringHandler(0, 0); builder.AppendLiteral("["); builder.AppendFormatted(WireType); @@ -277,6 +279,38 @@ public override string ToString() builder.AppendLiteral("]"); return builder.ToStringAndClear(); +#else + var builder = new StringBuilder(); + builder.Append("["); + builder.Append(WireType); + + if (HasFieldId) + { + builder.Append(", IdDelta:"); + builder.Append(FieldIdDelta); + } + + if (IsSchemaTypeValid) + { + builder.Append(", SchemaType:"); + builder.Append(SchemaType); + } + + if (HasExtendedSchemaType) + { + builder.Append(", RuntimeType:"); + builder.Append(FieldType); + } + + if (WireType == WireType.Extended) + { + builder.Append(": "); + builder.Append(ExtendedWireType); + } + + builder.Append("]"); + return builder.ToString(); +#endif } } } \ No newline at end of file diff --git a/test/Orleans.Serialization.UnitTests/BuiltInCodecTests.cs b/test/Orleans.Serialization.UnitTests/BuiltInCodecTests.cs index edbefabe73..7878634443 100644 --- a/test/Orleans.Serialization.UnitTests/BuiltInCodecTests.cs +++ b/test/Orleans.Serialization.UnitTests/BuiltInCodecTests.cs @@ -138,6 +138,7 @@ public DateTimeCopierTests(ITestOutputHelper output) : base(output) protected override Action> ValueProvider => Gen.DateTime.ToValueProvider(); } +#if NET6_0_OR_GREATER public class DateOnlyTests : FieldCodecTester { public DateOnlyTests(ITestOutputHelper output) : base(output) @@ -181,6 +182,7 @@ public TimeOnlyCopierTests(ITestOutputHelper output) : base(output) protected override TimeOnly[] TestValues => new[] { TimeOnly.MinValue, TimeOnly.MaxValue, TimeOnly.FromTimeSpan(TimeSpan.Zero), CreateValue() }; protected override Action> ValueProvider => assert => Gen.Date.Sample(dt => assert(TimeOnly.FromDateTime(dt))); } +#endif public class TimeSpanTests : FieldCodecTester { @@ -1173,6 +1175,7 @@ public ArrayCopierTests(ITestOutputHelper output) : base(output) protected override int[][] TestValues => new[] { null, Array.Empty(), CreateValue(), CreateValue(), CreateValue() }; } +#if NET7_0_OR_GREATER public class UInt128CodecTests : FieldCodecTester { public UInt128CodecTests(ITestOutputHelper output) : base(output) @@ -1226,6 +1229,7 @@ public UInt128CopierTests(ITestOutputHelper output) : base(output) protected override Action> ValueProvider => assert => Gen.ULong.Select(Gen.ULong).Sample(value => assert(new (value.V0, value.V1))); } +#endif public class UInt64CodecTests : FieldCodecTester { @@ -1410,6 +1414,7 @@ public ByteCopierTests(ITestOutputHelper output) : base(output) protected override Action> ValueProvider => Gen.Byte.ToValueProvider(); } +#if NET7_0_OR_GREATER public class Int128CodecTests : FieldCodecTester { public Int128CodecTests(ITestOutputHelper output) : base(output) @@ -1463,6 +1468,7 @@ public Int128CopierTests(ITestOutputHelper output) : base(output) protected override Action> ValueProvider => assert => Gen.ULong.Select(Gen.ULong).Sample(value => assert(new (value.V0, value.V1))); } +#endif public class Int64CodecTests : FieldCodecTester { @@ -1866,6 +1872,7 @@ public FloatCopierTests(ITestOutputHelper output) : base(output) protected override Action> ValueProvider => Gen.Float.ToValueProvider(); } +#if NET5_0_OR_GREATER public class HalfCodecTests : FieldCodecTester { public HalfCodecTests(ITestOutputHelper output) : base(output) @@ -1889,6 +1896,7 @@ public HalfCopierTests(ITestOutputHelper output) : base(output) protected override Action> ValueProvider => assert => Gen.UShort.Sample(value => assert(BitConverter.UInt16BitsToHalf(value))); } +#endif public class DoubleCodecTests : FieldCodecTester { @@ -2245,7 +2253,7 @@ protected override bool Equals(ConcurrentDictionary left, Concurren return false; } } - + return true; } } @@ -2288,7 +2296,7 @@ protected override bool Equals(ConcurrentDictionary left, Concurren return false; } } - + return true; } } @@ -2400,7 +2408,7 @@ protected override IPAddress CreateValue() } Random.NextBytes(bytes); return new IPAddress(bytes); - } + } } public class IPAddressCopierTests : CopierTester> @@ -2595,7 +2603,7 @@ public FSharpOptionCopierTests(ITestOutputHelper output) : base(output) protected override bool Equals(FSharpOption left, FSharpOption right) => object.ReferenceEquals(left, right) || left.GetType() == right.GetType() && left.Value.Equals(right.Value); } - + public class FSharpOptionTests2 : FieldCodecTester, FSharpOptionCodec> { public FSharpOptionTests2(ITestOutputHelper output) : base(output) diff --git a/test/Orleans.Serialization.UnitTests/NumericsWideningAndNarrowingTests.cs b/test/Orleans.Serialization.UnitTests/NumericsWideningAndNarrowingTests.cs index 3f17127fcc..bb47f1a6bb 100644 --- a/test/Orleans.Serialization.UnitTests/NumericsWideningAndNarrowingTests.cs +++ b/test/Orleans.Serialization.UnitTests/NumericsWideningAndNarrowingTests.cs @@ -1,3 +1,4 @@ +#if NET7_0_OR_GREATER using System; using System.Collections.Generic; using System.Linq; @@ -243,4 +244,443 @@ public sealed class ValueHolder public T Value; } } +#else +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Orleans.Serialization.UnitTests; + +/// +/// Ensures that numeric fields (integers, floats, etc) can be widened and narrowed in a version tolerant manner. +/// +public class NumericsWideningAndNarrowingTests +{ + private static readonly ByteNumber ByteType = new(); + private static readonly ShortNumber ShortType = new(); + private static readonly IntNumber IntType = new(); + private static readonly LongNumber LongType = new(); + private static readonly SByteNumber SByteType = new(); + private static readonly UShortNumber UShortType = new(); + private static readonly UIntNumber UIntType = new(); + private static readonly ULongNumber ULongType = new(); + private static readonly FloatNumber FloatType = new(); + private static readonly DoubleNumber DoubleType = new(); + private static readonly DecimalNumber DecimalType = new(); +#if NET5_0_OR_GREATER + private static readonly HalfNumber HalfType = new(); +#endif + + private readonly IServiceProvider _serviceProvider; + private readonly Serializer _serializer; + + public NumericsWideningAndNarrowingTests() + { + var services = new ServiceCollection(); + _ = services.AddSerializer(); + _serviceProvider = services.BuildServiceProvider(); + _serializer = _serviceProvider.GetRequiredService(); + } + + /// + /// Tests that unsigned integers can be widened and narrowed and maintain compatibility. + /// + [Fact] + public void UnsignedIntegerWideningAndNarrowingVersionToleranceTests() + { + ConversionRoundTrip(ByteType, UShortType); + ConversionRoundTrip(ByteType, UIntType); + ConversionRoundTrip(ByteType, ULongType); + + ConversionRoundTrip(UShortType, UIntType); + ConversionRoundTrip(UShortType, ULongType); + + ConversionRoundTrip(UIntType, ULongType); + } + + /// + /// Tests that signed integers can be widened and narrowed and maintain compatibility. + /// + [Fact] + public void SignedIntegerWideningAndNarrowingVersionToleranceTests() + { + ConversionRoundTrip(SByteType, ShortType); + ConversionRoundTrip(SByteType, IntType); + ConversionRoundTrip(SByteType, LongType); + + ConversionRoundTrip(ShortType, IntType); + ConversionRoundTrip(ShortType, LongType); + + ConversionRoundTrip(IntType, LongType); + } + + /// + /// Tests that floating point numbers can be widened and narrowed and maintain compatibility. + /// + [Fact] + public void FloatWideningAndNarrowingVersionToleranceTests() + { +#if NET5_0_OR_GREATER + FloatConversionRoundTrip(HalfType, DecimalType); + FloatConversionRoundTrip(HalfType, FloatType); + FloatConversionRoundTrip(HalfType, DoubleType); +#endif + + FloatConversionRoundTrip(DecimalType, FloatType); + FloatConversionRoundTrip(DecimalType, DoubleType); + + FloatConversionRoundTrip(FloatType, DoubleType); + } + + private void ConversionRoundTrip(INumber n, INumber w) + { + WideningRoundTrip(n, w); + NarrowingRoundTrip(n, w); + NarrowingOverflow(n, w); + } + + private void FloatConversionRoundTrip(INumber n, INumber w) + { + FloatWideningRoundTrip(n, w); + FloatNarrowingRoundTrip(n, w); + NarrowingOverflow(n, w); + } + + private void WideningRoundTrip(INumber n, INumber w) + { + var two = n.Add(n.MultiplicativeIdentity, n.MultiplicativeIdentity); + var values = new List + { + n.MinValue, + default(N), + n.Divide(n.MaxValue, two), + n.MaxValue, + }; + + foreach (var value in values) + { + RoundTripValue(value, n, w); + } + } + + private void NarrowingRoundTrip(INumber n, INumber w) + { + var two = n.Add(n.MultiplicativeIdentity, n.MultiplicativeIdentity); + var values = (new N[] + { + n.MinValue, + default(N), + n.Divide(n.MaxValue, two), + n.MaxValue, + }).Select(w.CreateTruncating).ToList(); + + foreach (var value in values) + { + RoundTripValue(value, w, n); + } + } + + private void NarrowingOverflow(INumber n, INumber w) + { + var values = w.Sign(w.MinValue) switch + { + -1 => new[] { w.MinValue, w.MaxValue }, + _ => new[] { w.MaxValue } + }; + + foreach (var value in values) + { + Assert.Throws(() => RoundTripValueDirectly(value, w, n)); + Assert.Throws(() => RoundTripValueIndirectly(value, w, n)); + } + } + + private void FloatWideningRoundTrip(INumber n, INumber w) + { + var two = n.Add(n.MultiplicativeIdentity, n.MultiplicativeIdentity); + var buffer = n.Divide(n.Divide(n.Divide(n.MaxValue, two), two), two); + var values = new List + { + n.Add(n.MinValue, buffer), + n.Divide(n.MinValue, two), + default(N), + n.Divide(n.MaxValue, two), + n.Subtract(n.MaxValue, buffer), + }; + + foreach (var value in values) + { + RoundTripValue(value, n, w); + } + } + + private void FloatNarrowingRoundTrip(INumber n, INumber w) + { + var two = n.Add(n.MultiplicativeIdentity, n.MultiplicativeIdentity); + var buffer = n.Divide(n.Divide(n.Divide(n.MaxValue, two), two), two); + var values = new List + { + n.Add(n.MinValue, buffer), + n.Divide(n.MinValue, two), + default(N), + n.Divide(n.MaxValue, two), + n.Subtract(n.MaxValue, buffer), + }.Select(w.CreateTruncating).ToList(); + + foreach (var value in values) + { + RoundTripValue(value, w, n); + } + } + + private void RoundTripValue(TLeft leftValue, INumber left, INumber right) + { + RoundTripValueDirectly(leftValue, left, right); + RoundTripValueIndirectly(leftValue, left, right); + } + + private void RoundTripValueDirectly(TLeft leftValue, INumber left, INumber right) + { + // Round-trip the value, converting it along the way. + var payload = _serializer.SerializeToArray(leftValue); + var result = _serializer.Deserialize(payload); + + var asRight = right.CreateTruncating(leftValue); + Assert.Equal(asRight, result); + + var asLeft = left.CreateTruncating(result); + var expected = left.CreateTruncating(right.CreateTruncating(leftValue)); + Assert.Equal(expected, asLeft); + } + + private void RoundTripValueIndirectly(TLeft leftValue, INumber left, INumber right) + { + // Wrap the value and round-trip the wrapped value, converting it along the way. + var payload = _serializer.SerializeToArray(new ValueHolder { Value = leftValue }); + var result = _serializer.Deserialize>(payload).Value; + + var asRight = right.CreateTruncating(leftValue); + Assert.Equal(asRight, result); + + var asLeft = left.CreateTruncating(result); + var expected = left.CreateTruncating(right.CreateTruncating(leftValue)); + Assert.Equal(expected, asLeft); + } + + [GenerateSerializer] + public sealed class ValueHolder + { + [Id(0)] + public T Value; + } + + public interface INumber + { + public T MinValue { get; } + public T MaxValue { get; } + public T MultiplicativeIdentity { get; } + public int Sign(T value); + public T Add(T x, T y); + public T Subtract(T x, T y); + public T Divide(T x, T y); + public T CreateTruncating(TFrom from); + } + + public sealed class ByteNumber : INumber + { + public byte MinValue => byte.MinValue; + public byte MaxValue => byte.MaxValue; + public byte MultiplicativeIdentity => 1; + public int Sign(byte value) => Math.Sign(value); + + public byte Add(byte x, byte y) => (byte)(x + y); + + public byte Subtract(byte x, byte y) => (byte)(x - y); + + public byte Divide(byte x, byte y) => (byte)(x / y); + + public byte CreateTruncating(TFrom from) => (byte)Convert.ChangeType(from, typeof(byte)); + } + + public sealed class UShortNumber : INumber + { + public ushort MinValue => ushort.MinValue; + public ushort MaxValue => ushort.MaxValue; + public ushort MultiplicativeIdentity => 1; + public int Sign(ushort value) => 1; + + public ushort Add(ushort x, ushort y) => (ushort)(x + y); + + public ushort Subtract(ushort x, ushort y) => (ushort)(x - y); + + public ushort Divide(ushort x, ushort y) => (ushort)(x / y); + + public ushort CreateTruncating(TFrom from) => (ushort)Convert.ChangeType(from, typeof(ushort)); + } + + public sealed class UIntNumber : INumber + { + public uint MinValue => uint.MinValue; + public uint MaxValue => uint.MaxValue; + public uint MultiplicativeIdentity => 1; + public int Sign(uint value) => 1; + + public uint Add(uint x, uint y) => x + y; + + public uint Subtract(uint x, uint y) => x - y; + + public uint Divide(uint x, uint y) => x / y; + + public uint CreateTruncating(TFrom from) => (uint)Convert.ChangeType(from, typeof(uint)); + } + + public sealed class ULongNumber : INumber + { + public ulong MinValue => ulong.MinValue; + public ulong MaxValue => ulong.MaxValue; + public ulong MultiplicativeIdentity => 1; + public int Sign(ulong value) => 1; + + public ulong Add(ulong x, ulong y) => x + y; + + public ulong Subtract(ulong x, ulong y) => x - y; + + public ulong Divide(ulong x, ulong y) => x / y; + + public ulong CreateTruncating(TFrom from) => (ulong)Convert.ChangeType(from, typeof(ulong)); + } + + public sealed class SByteNumber : INumber + { + public sbyte MinValue => sbyte.MinValue; + public sbyte MaxValue => sbyte.MaxValue; + public sbyte MultiplicativeIdentity => 1; + public int Sign(sbyte value) => Math.Sign(value); + + public sbyte Add(sbyte x, sbyte y) => (sbyte)(x + y); + + public sbyte Subtract(sbyte x, sbyte y) => (sbyte)(x - y); + + public sbyte Divide(sbyte x, sbyte y) => (sbyte)(x / y); + + public sbyte CreateTruncating(TFrom from) => (sbyte)Convert.ChangeType(from, typeof(sbyte)); + } + + public sealed class ShortNumber : INumber + { + public short MinValue => short.MinValue; + public short MaxValue => short.MaxValue; + public short MultiplicativeIdentity => 1; + public int Sign(short value) => Math.Sign(value); + + public short Add(short x, short y) => (short)(x + y); + + public short Subtract(short x, short y) => (short)(x - y); + + public short Divide(short x, short y) => (short)(x / y); + + public short CreateTruncating(TFrom from) => (short)Convert.ChangeType(from, typeof(short)); + } + + public sealed class IntNumber : INumber + { + public int MinValue => int.MinValue; + public int MaxValue => int.MaxValue; + public int MultiplicativeIdentity => 1; + public int Sign(int value) => Math.Sign(value); + + public int Add(int x, int y) => x + y; + + public int Subtract(int x, int y) => x - y; + + public int Divide(int x, int y) => x / y; + + public int CreateTruncating(TFrom from) => (int)Convert.ChangeType(from, typeof(int)); + } + + public sealed class LongNumber : INumber + { + public long MinValue => long.MinValue; + public long MaxValue => long.MaxValue; + public long MultiplicativeIdentity => 1; + public int Sign(long value) => Math.Sign(value); + + public long Add(long x, long y) => x + y; + + public long Subtract(long x, long y) => x - y; + + public long Divide(long x, long y) => x / y; + + public long CreateTruncating(TFrom from) => (long)Convert.ChangeType(from, typeof(long)); + } + + public sealed class FloatNumber : INumber + { + public float MinValue => float.MinValue; + public float MaxValue => float.MaxValue; + public float MultiplicativeIdentity => 1; + public int Sign(float value) => Math.Sign(value); + + public float Add(float x, float y) => x + y; + + public float Subtract(float x, float y) => x - y; + + public float Divide(float x, float y) => x / y; + + public float CreateTruncating(TFrom from) => (float)Convert.ChangeType(from, typeof(float)); + } + + public sealed class DoubleNumber : INumber + { + public double MinValue => double.MinValue; + public double MaxValue => double.MaxValue; + public double MultiplicativeIdentity => 1; + public int Sign(double value) => Math.Sign(value); + + public double Add(double x, double y) => x + y; + + public double Subtract(double x, double y) => x - y; + + public double Divide(double x, double y) => x / y; + + public double CreateTruncating(TFrom from) => (double)Convert.ChangeType(from, typeof(double)); + } + + public sealed class DecimalNumber : INumber + { + public decimal MinValue => decimal.MinValue; + public decimal MaxValue => decimal.MaxValue; + public decimal MultiplicativeIdentity => 1; + public int Sign(decimal value) => Math.Sign(value); + + public decimal Add(decimal x, decimal y) => x + y; + + public decimal Subtract(decimal x, decimal y) => x - y; + + public decimal Divide(decimal x, decimal y) => x / y; + + public decimal CreateTruncating(TFrom from) => (decimal)Convert.ChangeType(from, typeof(decimal)); + } + +#if NET5_0_OR_GREATER + public sealed class HalfNumber : INumber + { + public Half MinValue => Half.MinValue; + public Half MaxValue => Half.MaxValue; + public Half MultiplicativeIdentity => 1; + public int Sign(Half value) => Math.Sign(value); + + public Half Add(Half x, Half y) => x + y; + + public Half Subtract(Half x, Half y) => x - y; + + public Half Divide(Half x, Half y) => x / y; + + public Half CreateTruncating(TFrom from) => (Half)Convert.ChangeType(from, typeof(Half)); + } +#endif +} +#endif \ No newline at end of file diff --git a/test/Orleans.Serialization.UnitTests/Orleans.Serialization.UnitTests.csproj b/test/Orleans.Serialization.UnitTests/Orleans.Serialization.UnitTests.csproj index 337038b70e..7ef2cde233 100644 --- a/test/Orleans.Serialization.UnitTests/Orleans.Serialization.UnitTests.csproj +++ b/test/Orleans.Serialization.UnitTests/Orleans.Serialization.UnitTests.csproj @@ -2,7 +2,7 @@ true - $(TestTargetFrameworks) + $(TestTargetFrameworks);netcoreapp3.1 true true true