diff --git a/appveyor.yml b/appveyor.yml index 83a5019b4..7ea3e8f2f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ skip_commits: install: - choco install dotnet-sdk --version 6.0.406 - choco install dotnet-sdk --version 7.0.200 + - choco install dotnet-sdk --version 8.0.100 environment: Appveyor: true diff --git a/docs/contract_first.md b/docs/contract_first.md index 800206bc5..cc860e943 100644 --- a/docs/contract_first.md +++ b/docs/contract_first.md @@ -29,7 +29,7 @@ change the version/contents from the copy baked into the tool. Normally, standar ## Additional options -Additional configuration options can be specified at attributes against each `` node, to fine tune your options; this is very similar to the options available with the command-line tools: +Additional configuration options can be specified as attributes against each `` node, to fine tune your options; this is very similar to the options available with the command-line tools: - `ImportPaths` - specifies a comma delimited list of additional import locations; usually this isn't required, as schemas are resolved relative to each file; this is resolved relative to the current file, and can indicate "upwards" locations like `../..` - but the actual lookup happens *purely* within the virtual file system of `` nodes - it does not allow external file access diff --git a/docs/releasenotes.md b/docs/releasenotes.md index 61f8c6b66..d465af640 100644 --- a/docs/releasenotes.md +++ b/docs/releasenotes.md @@ -14,6 +14,10 @@ Packages are available on NuGet: [protobuf-net](https://www.nuget.org/packages/p ## unreleased +- support of deserializing `ISet` and `IReadOnlySet` (ladeak) + +## 3.2.30 + - support `DateOnly` and `TimeOnly` (#1100 by @mgravell, fixes #977) - support Roslyn [DefaultValue] analyzer (#1040 by @deaglegross) diff --git a/global.json b/global.json index a8ab760dd..200bcace8 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,7 @@ { "sdk": { + "version": "8.0.100", + "rollForward": "latestPatch", "allowPrerelease": false } } \ No newline at end of file diff --git a/protobuf-net.sln b/protobuf-net.sln index a8d6bf4d5..b09f8a142 100644 --- a/protobuf-net.sln +++ b/protobuf-net.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{0A6E81 src\Directory.Packages.props = src\Directory.Packages.props global.json = global.json NuGet.Config = NuGet.Config + protobuf-net.png = protobuf-net.png protobuf-net.svg = protobuf-net.svg src\Tools\protogen.proto = src\Tools\protogen.proto README.md = README.md @@ -95,7 +96,7 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "protobuf-net.FSharp.Test", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protobuf-net.FSharp", "src\protobuf-net.FSharp\protobuf-net.FSharp.csproj", "{763193DB-D730-4898-AB6B-986880AB18BF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildToolsSmokeTests", "src\BuildToolsSmokeTests\BuildToolsSmokeTests.csproj", "{5BF248A8-1B2A-474C-9B62-A5173AD1BD55}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildToolsSmokeTests", "src\BuildToolsSmokeTests\BuildToolsSmokeTests.csproj", "{5BF248A8-1B2A-474C-9B62-A5173AD1BD55}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Benchmark/DeserializeBenchmarks.cs b/src/Benchmark/DeserializeBenchmarks.cs index f72abe3c3..a1ab36976 100644 --- a/src/Benchmark/DeserializeBenchmarks.cs +++ b/src/Benchmark/DeserializeBenchmarks.cs @@ -79,7 +79,7 @@ private protogen.Database MemoryStream_Legacy(TypeModel model) private MemoryStream _exposable; private Stream ExposableData() { - if (_exposable == null) _exposable = new MemoryStream(_data, 0, _data.Length, false, true); + _exposable ??= new MemoryStream(_data, 0, _data.Length, false, true); _exposable.Position = 0; return _exposable; } diff --git a/src/Benchmark/DeserializeBenchmarks_NewApi.cs b/src/Benchmark/DeserializeBenchmarks_NewApi.cs index 97f646b29..6b33b6e5e 100644 --- a/src/Benchmark/DeserializeBenchmarks_NewApi.cs +++ b/src/Benchmark/DeserializeBenchmarks_NewApi.cs @@ -127,7 +127,7 @@ private static void Merge(ref ProtoReader.State state, ref protogen.Database obj { SubItemToken tok; int field; - if (obj == null) obj = new protogen.Database(); + obj ??= new protogen.Database(); while ((field = state.ReadFieldHeader()) != 0) { switch (field) @@ -152,7 +152,7 @@ private static void Merge(ref ProtoReader.State state, ref protogen.Order obj) { SubItemToken tok; int field; - if (obj == null) obj = new protogen.Order(); + obj ??= new protogen.Order(); while ((field = state.ReadFieldHeader()) != 0) { switch (field) @@ -219,7 +219,7 @@ private static void Merge(ref ProtoReader.State state, ref protogen.Order obj) private static void Merge(ref ProtoReader.State state, ref protogen.OrderLine obj) { int field; - if (obj == null) obj = new protogen.OrderLine(); + obj ??= new protogen.OrderLine(); while ((field = state.ReadFieldHeader()) != 0) { switch (field) diff --git a/src/BuildToolsUnitTests/AnalyzerTestBase.cs b/src/BuildToolsUnitTests/AnalyzerTestBase.cs index cf3cdfad8..05ec3a6bc 100644 --- a/src/BuildToolsUnitTests/AnalyzerTestBase.cs +++ b/src/BuildToolsUnitTests/AnalyzerTestBase.cs @@ -65,6 +65,18 @@ protected async Task> AnalyzeAsync(Func> AnalyzeMultiFileAsync(List sourceCode, [CallerMemberName] string? callerMemberName = null, bool ignoreCompatibilityLevelAdvice = true, bool ignorePreferAsyncAdvice = true) + { + return await AnalyzeAsync(project => + { + for (int i = 0; i < sourceCode.Count; i++) + { + project = project.AddDocument($"{callerMemberName}_{i}_.cs", sourceCode[i]).Project; + } + return project; + }, callerMemberName, ignoreCompatibilityLevelAdvice, ignorePreferAsyncAdvice); + } + protected async Task<(Project Project, Compilation Compilation)> ObtainProjectAndCompilationAsync(Func? projectModifier = null, [CallerMemberName] string? callerMemberName = null) { _ = callerMemberName; diff --git a/src/BuildToolsUnitTests/CodeFixes/Abstractions/CodeFixProviderTestsBase.cs b/src/BuildToolsUnitTests/CodeFixes/Abstractions/CodeFixProviderTestsBase.cs index 4633ac5f0..5083e306e 100644 --- a/src/BuildToolsUnitTests/CodeFixes/Abstractions/CodeFixProviderTestsBase.cs +++ b/src/BuildToolsUnitTests/CodeFixes/Abstractions/CodeFixProviderTestsBase.cs @@ -27,7 +27,7 @@ protected async Task RunCodeFixTestAsync( params DiagnosticResult[] standardExpectedDiagnostics) where TDiagnosticAnalyzer : DiagnosticAnalyzer, new() { - var codeFixTest = BuildCSharpCodeFixTest(sourceCode, expectedCode, targetFramework); + var codeFixTest = CodeFixProviderTestsBase.BuildCSharpCodeFixTest(sourceCode, expectedCode, targetFramework); if (diagnosticResult is not null) { @@ -42,7 +42,7 @@ protected async Task RunCodeFixTestAsync( await codeFixTest.RunAsync(); } - CSharpCodeFixTest BuildCSharpCodeFixTest( + static CSharpCodeFixTest BuildCSharpCodeFixTest( string sourceCode, string expectedCode, string? targetFramework = null) where TDiagnosticAnalyzer : DiagnosticAnalyzer, new() { @@ -55,11 +55,10 @@ CSharpCodeFixTest BuildCSh { ReferenceAssemblies = new ReferenceAssemblies(targetFramework), TestState = { Sources = { sourceCode }, OutputKind = OutputKind.DynamicallyLinkedLibrary }, - FixedState = { Sources = { expectedCode }, OutputKind = OutputKind.DynamicallyLinkedLibrary } + FixedState = { Sources = { expectedCode }, OutputKind = OutputKind.DynamicallyLinkedLibrary }, + CodeActionValidationMode = CodeActionValidationMode.SemanticStructure }; - codeFixTest.CodeActionValidationMode = CodeActionValidationMode.SemanticStructure; - AddAdditionalReferences(codeFixTest.TestState); AddAdditionalReferences(codeFixTest.FixedState); diff --git a/src/BuildToolsUnitTests/DataContractAnalyzerTests.cs b/src/BuildToolsUnitTests/DataContractAnalyzerTests.cs index 2aa0d71be..5a19aae51 100644 --- a/src/BuildToolsUnitTests/DataContractAnalyzerTests.cs +++ b/src/BuildToolsUnitTests/DataContractAnalyzerTests.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using ProtoBuf.BuildTools.Analyzers; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -65,6 +66,34 @@ public class Foo Assert.Empty(diagnostics); } + [Fact] + public async Task DoesntReportOnPartialClassDefinition() + { + var diagnostics = await AnalyzeMultiFileAsync( + new List { +@" +using ProtoBuf; +[ProtoContract(SkipConstructor = true)] +public partial class Foo +{ + [ProtoMember(2)] + [System.ComponentModel.DefaultValue(3)] + public int Bar {get;set;} =3; +} ", +//next file +@" +public partial class Foo +{ + public Foo(int bar){ + Bar = bar; + } + public Foo (){} +} "}); + Assert.Empty(diagnostics); + } + + + [Theory] [InlineData(0)] [InlineData(-42)] diff --git a/src/BuildToolsUnitTests/GeneratorTestBase.cs b/src/BuildToolsUnitTests/GeneratorTestBase.cs index 532c407dd..d7c1dbc28 100644 --- a/src/BuildToolsUnitTests/GeneratorTestBase.cs +++ b/src/BuildToolsUnitTests/GeneratorTestBase.cs @@ -49,7 +49,7 @@ protected virtual TGenerator Generator var parseOptions = new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse); - if (globalOptions is null) globalOptions = ImmutableDictionary.Empty; + globalOptions ??= ImmutableDictionary.Empty; if (debugLog) globalOptions = globalOptions.SetItem("pbn_debug_log", "true"); var optionsProvider = TestAnalyzeConfigOptionsProvider.Empty.WithGlobalOptions(new TestAnalyzerConfigOptions(globalOptions)); diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 80fb33298..24e52dda8 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -24,11 +24,13 @@ en-US false $(MSBuildProjectName.Contains('Test')) - 9.0 + 12 + strict $(MSBuildThisFileDirectory)Shared.ruleset true 4.5.0 + readme.md true @@ -56,9 +58,7 @@ - - True - - + + \ No newline at end of file diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 94bbaaae5..b6bc365c4 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,60 +1,61 @@ - + + + + + + + + + + - - - - + - - + + - + - - + + - - + + - - + + - - + + - - + + - - - - - + - - + @@ -63,8 +64,8 @@ - - + + \ No newline at end of file diff --git a/src/Examples/AutoTuple.cs b/src/Examples/AutoTuple.cs index 6a645388b..a28b6ae05 100644 --- a/src/Examples/AutoTuple.cs +++ b/src/Examples/AutoTuple.cs @@ -175,8 +175,8 @@ public BasicTuple(int foo, string bar) this.foo = foo; this.bar = bar; } - public int Foo { get { return foo; } } - public string Bar { get { return bar; } } + public readonly int Foo { get { return foo; } } + public readonly string Bar { get { return bar; } } } public class BasicTupleReversedOrder diff --git a/src/Examples/Callbacks.cs b/src/Examples/Callbacks.cs index 50a21a944..505eaceae 100644 --- a/src/Examples/Callbacks.cs +++ b/src/Examples/Callbacks.cs @@ -476,12 +476,12 @@ public static int AfterSerializeCount [OnDeserializing] public void OnDeserializing() { History += ";OnDeserializing"; } [OnSerialized] - public void OnSerialized() + public readonly void OnSerialized() { Interlocked.Increment(ref afterSer); } [OnSerializing] - public void OnSerializing() + public readonly void OnSerializing() { Interlocked.Increment(ref beforeSer); } diff --git a/src/Examples/DictionaryTests.cs b/src/Examples/DictionaryTests.cs index ab2888d32..854e5cd31 100644 --- a/src/Examples/DictionaryTests.cs +++ b/src/Examples/DictionaryTests.cs @@ -262,7 +262,7 @@ public void EmptyDictionaryShouldDeserializeAsNonNullViaInterface() var clone = Serializer.Deserialize>(ms); Assert.NotNull(clone); - Assert.Equal(0, clone.Count); + Assert.Empty(clone); } [Fact] @@ -276,7 +276,7 @@ public void NonEmptyDictionaryShouldDeserializeViaInterface() var clone = Serializer.Deserialize>(ms); Assert.NotNull(clone); - Assert.Equal(1, clone.Count); + Assert.Single(clone); Assert.Equal(123, clone["abc"]); } } @@ -375,7 +375,7 @@ static void CheckNested(IDictionary data, string message Assert.Equal("abc, jkl", allKeys); var inner = data["abc"]; - Assert.Equal(1, inner.Keys.Count); //, message); + Assert.Single(inner.Keys); //, message); Assert.Equal("ghi", inner["def"]); //, message); inner = data["jkl"]; Assert.Equal(2, inner.Keys.Count); //, message); diff --git a/src/Examples/EnumTests.cs b/src/Examples/EnumTests.cs index 033193421..665178427 100644 --- a/src/Examples/EnumTests.cs +++ b/src/Examples/EnumTests.cs @@ -69,8 +69,8 @@ message EnumFoo { } enum blah { B = 0; - A = -1; C = 1; + A = -1; } ", proto, ignoreLineEndingDifferences: true); } @@ -93,8 +93,8 @@ message NonNullValues { } enum blah { B = 0; - A = -1; C = 1; + A = -1; } ", proto, ignoreLineEndingDifferences: true); } @@ -127,8 +127,8 @@ enum OutOfRangeEnum { A = 1; B = 4; C = 2147483647; - // D = 2147483648; // note: enums should be valid 32-bit integers E = -2147483647; + // D = 2147483648; // note: enums should be valid 32-bit integers // F = -2147483649; // note: enums should be valid 32-bit integers } */ @@ -178,8 +178,8 @@ message NullValues { } enum blah { B = 0; - A = -1; C = 1; + A = -1; } ", proto, ignoreLineEndingDifferences: true); } diff --git a/src/Examples/InheritanceExtensible.cs b/src/Examples/InheritanceExtensible.cs index 18a9afaf3..09139d58a 100644 --- a/src/Examples/InheritanceExtensible.cs +++ b/src/Examples/InheritanceExtensible.cs @@ -208,7 +208,7 @@ public void DetectValueTypeInstance() [ProtoContract] struct Foo : ITypedExtensible { - IExtension ITypedExtensible.GetExtensionObject(Type type, bool createIfMissing) + readonly IExtension ITypedExtensible.GetExtensionObject(Type type, bool createIfMissing) => throw new NotImplementedException("shouldn't be called"); } diff --git a/src/Examples/Issues/Issue1078/file.cs b/src/Examples/Issues/Issue1078/file.cs new file mode 100644 index 000000000..e525444ce --- /dev/null +++ b/src/Examples/Issues/Issue1078/file.cs @@ -0,0 +1,30 @@ +// +// This file was generated by a tool; you should avoid making direct changes. +// Consider using 'partial classes' to extend these types +// Input: file.proto +// + +#region Designer generated code +#pragma warning disable CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +[global::ProtoBuf.ProtoContract()] +public partial class Message : global::ProtoBuf.IExtensible +{ + private global::ProtoBuf.IExtension __pbn__extensionData; + global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing) + => global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing); + + [global::ProtoBuf.ProtoMember(3, Name = @"last_sequence_id")] + [global::System.ComponentModel.DefaultValue(-1L)] + public long LastSequenceId + { + get => __pbn__LastSequenceId ?? -1L; + set => __pbn__LastSequenceId = value; + } + public bool ShouldSerializeLastSequenceId() => __pbn__LastSequenceId != null; + public void ResetLastSequenceId() => __pbn__LastSequenceId = null; + private long? __pbn__LastSequenceId; + +} + +#pragma warning restore CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#endregion diff --git a/src/Examples/Issues/Issue1078/file.proto b/src/Examples/Issues/Issue1078/file.proto new file mode 100644 index 000000000..d7cd56900 --- /dev/null +++ b/src/Examples/Issues/Issue1078/file.proto @@ -0,0 +1,5 @@ +syntax = "proto2"; + +message Message { +optional int64 last_sequence_id = 3 [default = -1]; +} \ No newline at end of file diff --git a/src/Examples/Issues/Issue27.cs b/src/Examples/Issues/Issue27.cs index 4fbf42c48..a0dda4ca3 100644 --- a/src/Examples/Issues/Issue27.cs +++ b/src/Examples/Issues/Issue27.cs @@ -82,7 +82,7 @@ public KeyPair(TKey1 k1, TKey2 k2) [DataMember(Order = 2)] public TKey2 Key2 { get; internal set; } - public override string ToString() { + public override readonly string ToString() { return Key1.ToString() + ", " + Key2.ToString(); } } diff --git a/src/Examples/Issues/Issue647.Generated.cs b/src/Examples/Issues/Issue647.Generated.cs index 7752fec96..75af68aa8 100644 --- a/src/Examples/Issues/Issue647.Generated.cs +++ b/src/Examples/Issues/Issue647.Generated.cs @@ -5,7 +5,7 @@ // #region Designer generated code -#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning disable CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 namespace ProtoBuf.Issues.Issue647Types.Bar { @@ -54,5 +54,5 @@ public partial class FooClass : global::ProtoBuf.IExtensible } -#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning restore CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 #endregion diff --git a/src/Examples/Issues/Issue855Proto2CSharp.Generated.cs b/src/Examples/Issues/Issue855Proto2CSharp.Generated.cs index e538c2b47..685bbf6f7 100644 --- a/src/Examples/Issues/Issue855Proto2CSharp.Generated.cs +++ b/src/Examples/Issues/Issue855Proto2CSharp.Generated.cs @@ -5,7 +5,7 @@ // #region Designer generated code -#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning disable CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 [global::ProtoBuf.ProtoContract()] public partial class BarClass : global::ProtoBuf.IExtensible { @@ -128,5 +128,5 @@ public partial class FooClass : global::ProtoBuf.IExtensible } -#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning restore CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 #endregion diff --git a/src/Examples/Issues/Issue855Proto3CSharp.Generated.cs b/src/Examples/Issues/Issue855Proto3CSharp.Generated.cs index cf2c7ad76..fe21c4fcc 100644 --- a/src/Examples/Issues/Issue855Proto3CSharp.Generated.cs +++ b/src/Examples/Issues/Issue855Proto3CSharp.Generated.cs @@ -5,7 +5,7 @@ // #region Designer generated code -#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning disable CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 [global::ProtoBuf.ProtoContract()] public partial class BarClass : global::ProtoBuf.IExtensible { @@ -58,5 +58,5 @@ public partial class FooClass : global::ProtoBuf.IExtensible } -#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning restore CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 #endregion diff --git a/src/Examples/Issues/SO7120856.cs b/src/Examples/Issues/SO7120856.cs index 0d650a237..2fb16081d 100644 --- a/src/Examples/Issues/SO7120856.cs +++ b/src/Examples/Issues/SO7120856.cs @@ -63,12 +63,12 @@ public MyValueTypeViaFields(SerializationInfo info, StreamingContext text) } #endif - public int X + public readonly int X { get { return _x; } } - public int Z + public readonly int Z { get { return _z; } } @@ -145,13 +145,13 @@ public MyValueTypeAsTuple(SerializationInfo info, StreamingContext text) } #endif [DataMember(Order = 1)] - public int X + public readonly int X { get { return _x; } } [DataMember(Order = 2)] - public int Z + public readonly int Z { get { return _z; } } diff --git a/src/Examples/Program.cs b/src/Examples/Program.cs index 0e7e3c723..4fff8e5d6 100644 --- a/src/Examples/Program.cs +++ b/src/Examples/Program.cs @@ -94,7 +94,7 @@ public static bool CheckBytes(ITestOutputHelper output, T item, TypeModel mod public static bool CheckBytes(ITestOutputHelper output, T item, TypeModel model, string hex, params byte[] expected) { - if (model == null) model = RuntimeTypeModel.Default; + model ??= RuntimeTypeModel.Default; using (MemoryStream ms = new MemoryStream()) { model.Serialize(ms, item); diff --git a/src/Examples/SetTests.cs b/src/Examples/SetTests.cs new file mode 100644 index 000000000..281d885c4 --- /dev/null +++ b/src/Examples/SetTests.cs @@ -0,0 +1,378 @@ +using System.Collections.Generic; +using System.Linq; +using ProtoBuf; +using Xunit; + +namespace Examples +{ + public class HashSetSerializerTests + { + [ProtoContract] + class HashSetData + { + [ProtoMember(1)] + public HashSet Data { get; set; } + } + + [ProtoContract] + class NotEmptyHashSetData + { + public NotEmptyHashSetData() => Data = new HashSet(new[] { 1 }); + + public NotEmptyHashSetData(HashSet data) => Data = data; + + [ProtoMember(1)] + public HashSet Data { get; } + } + + [Fact] + public void TestEmptyNestedSetWithStrings() + { + var set = new HashSet(); + var input = new HashSetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + Assert.Null(clone.Data); + } + + [Fact] + public void TestNullNestedSetWithStrings() + { + var input = new HashSetData() { Data = null }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + Assert.Equal(input.Data, clone.Data); + } + + [Fact] + public void TestNestedSetWithStrings() + { + var set = new HashSet(); + set.Add("hello"); + set.Add("world"); + var input = new HashSetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void TestNestedSetWithInt32() + { + var set = new HashSet(); + set.Add(1); + set.Add(2); + set.Add(3); + set.Add(3); + var input = new HashSetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void RoundtripHashSet() + { + HashSet lookup = new HashSet(new[] { 1, 2, 3 }); + + var clone = Serializer.DeepClone(lookup); + + AssertEqual(lookup, clone); + } + + [Fact] + public void TestNonEmptySetStrings() + { + var set = new HashSet(); + set.Add(1); + set.Add(2); + set.Add(2); + var input = new NotEmptyHashSetData(set); + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void TestDefaultCtorValueOverwritten() + { + var set = new HashSet(); + set.Add(3); + var input = new NotEmptyHashSetData(set); + + var clone = Serializer.DeepClone(input); + Assert.NotSame(input, clone); + Assert.Contains(new NotEmptyHashSetData().Data.Single(), clone.Data); + Assert.Contains(3, clone.Data); + } + + static void AssertEqual( + HashSet expected, + HashSet actual) + { + Assert.Equal(expected.Count, actual.Count); + foreach (var value in expected) + Assert.Contains(value, actual); + } + } + + public class SetSerializerTests + { + [ProtoContract] + class SetData + { + [ProtoMember(1)] + public ISet Data { get; set; } + } + + [ProtoContract] + class NotEmptySetData + { + public NotEmptySetData() => Data = new HashSet(new[] { 1 }); + + public NotEmptySetData(ISet data) => Data = data; + + [ProtoMember(1)] + public ISet Data { get; } + } + + [Fact] + public void TestEmptyNestedSetWithStrings() + { + var set = new HashSet(); + var input = new SetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + Assert.Null(clone.Data); + } + + [Fact] + public void TestNullNestedSetWithStrings() + { + var input = new SetData() { Data = null }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + Assert.Equal(input.Data, clone.Data); + } + + [Fact] + public void TestNestedSetWithStrings() + { + var set = new HashSet(); + set.Add("hello"); + set.Add("world"); + var input = new SetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void TestNestedSetWithInt32() + { + var set = new HashSet(); + set.Add(1); + set.Add(2); + set.Add(3); + set.Add(3); + var input = new SetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void RoundtripSet() + { + ISet lookup = new HashSet(new[] { 1, 2, 3 }); + + var clone = Serializer.DeepClone(lookup); + + AssertEqual(lookup, clone); + } + + [Fact] + public void TestNonEmptySetStrings() + { + var set = new HashSet(); + set.Add(1); + set.Add(2); + set.Add(2); + var input = new NotEmptySetData(set); + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void TestDefaultCtorValueOverwritten() + { + var set = new HashSet(); + set.Add(3); + var input = new NotEmptySetData(set); + + var clone = Serializer.DeepClone(input); + Assert.NotSame(input, clone); + Assert.True(clone.Data.Contains(new NotEmptySetData().Data.Single())); + Assert.True(clone.Data.Contains(3)); + } + + static void AssertEqual( + ISet expected, + ISet actual) + { + Assert.Equal(expected.Count, actual.Count); + foreach (var value in expected) + Assert.True(actual.Contains(value)); + } + } + +#if NET6_0_OR_GREATER + public class ReadOnlySetSerializerTests + { + [ProtoContract] + class ReadOnlySetData + { + [ProtoMember(1)] + public IReadOnlySet Data { get; init; } + } + + [ProtoContract] + class NotEmptyReadOnlySetData + { + public NotEmptyReadOnlySetData() + { + Data = new HashSet(new[] { 1 }); + } + + public NotEmptyReadOnlySetData(IReadOnlySet data) + { + Data = data; + } + + [ProtoMember(1)] + public IReadOnlySet Data { get; } + } + + [Fact] + public void TestEmptyNestedSetWithStrings() + { + var set = new HashSet(); + var input = new ReadOnlySetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + Assert.Null(clone.Data); + } + + [Fact] + public void TestNullNestedSetWithStrings() + { + var input = new ReadOnlySetData() { Data = null }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + Assert.Equal(input.Data, clone.Data); + } + + [Fact] + public void TestNestedSetWithStrings() + { + var set = new HashSet(); + set.Add("hello"); + set.Add("world"); + var input = new ReadOnlySetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void TestNestedSetWithInt32() + { + var set = new HashSet(); + set.Add(1); + set.Add(2); + set.Add(3); + set.Add(3); + var input = new ReadOnlySetData() { Data = set }; + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void RoundtripReadOnlySet() + { + IReadOnlySet lookup = new HashSet(new[] { 1, 2, 3 }); + + var clone = Serializer.DeepClone(lookup); + + AssertEqual(lookup, clone); + } + + [Fact] + public void TestNonEmptySetStrings() + { + var set = new HashSet(); + set.Add(1); + set.Add(2); + set.Add(2); + var input = new NotEmptyReadOnlySetData(set); + + var clone = Serializer.DeepClone(input); + + Assert.NotSame(input, clone); + AssertEqual(input.Data, clone.Data); + } + + [Fact] + public void TestDefaultCtorValueOverwritten() + { + var set = new HashSet(); + set.Add(3); + var input = new NotEmptyReadOnlySetData(set); + + var clone = Serializer.DeepClone(input); + Assert.NotSame(input, clone); + Assert.True(clone.Data.Contains(new NotEmptyReadOnlySetData().Data.Single())); + Assert.True(clone.Data.Contains(3)); + } + + static void AssertEqual( + IReadOnlySet expected, + IReadOnlySet actual) + { + Assert.Equal(expected.Count, actual.Count); + foreach (var value in expected) + Assert.True(actual.Contains(value)); + } + } +#endif +} \ No newline at end of file diff --git a/src/protobuf-net.AspNetCore/DependencyInjection/ProtoBufMvcBuilderExtensions.cs b/src/protobuf-net.AspNetCore/DependencyInjection/ProtoBufMvcBuilderExtensions.cs index 10e7611a4..aa2d97d9d 100644 --- a/src/protobuf-net.AspNetCore/DependencyInjection/ProtoBufMvcBuilderExtensions.cs +++ b/src/protobuf-net.AspNetCore/DependencyInjection/ProtoBufMvcBuilderExtensions.cs @@ -23,7 +23,7 @@ public static IMvcCoreBuilder AddProtoBufNet(this IMvcCoreBuilder builder, Actio } builder.Services.TryAddEnumerable(ServiceDescriptor.Transient, MvcProtoBufNetOptionsSetup>()); - if (setupAction is object) builder.Services.Configure(setupAction); + if (setupAction is not null) builder.Services.Configure(setupAction); return builder; } @@ -38,7 +38,7 @@ public static IMvcBuilder AddProtoBufNet(this IMvcBuilder builder, Action, MvcProtoBufNetOptionsSetup>()); - if (setupAction is object) builder.Services.Configure(setupAction); + if (setupAction is not null) builder.Services.Configure(setupAction); return builder; } } diff --git a/src/protobuf-net.BuildTools/CodeFixes/DefaultValue/Abstractions/DefaultValueCodeFixProviderBase.cs b/src/protobuf-net.BuildTools/CodeFixes/DefaultValue/Abstractions/DefaultValueCodeFixProviderBase.cs index c21a77461..aee40294a 100644 --- a/src/protobuf-net.BuildTools/CodeFixes/DefaultValue/Abstractions/DefaultValueCodeFixProviderBase.cs +++ b/src/protobuf-net.BuildTools/CodeFixes/DefaultValue/Abstractions/DefaultValueCodeFixProviderBase.cs @@ -103,7 +103,7 @@ protected internal struct DiagnosticArguments /// /// Returns default value string representation with a cast to type included /// - internal string GetCastedRepresentation() + internal readonly string GetCastedRepresentation() { if (MemberSpecialType == SpecialType.System_Enum) { diff --git a/src/protobuf-net.BuildTools/CodeFixes/DefaultValue/ShouldDeclareDefaultCodeFixProvider.cs b/src/protobuf-net.BuildTools/CodeFixes/DefaultValue/ShouldDeclareDefaultCodeFixProvider.cs index 50eb0ec2d..1f32f4074 100644 --- a/src/protobuf-net.BuildTools/CodeFixes/DefaultValue/ShouldDeclareDefaultCodeFixProvider.cs +++ b/src/protobuf-net.BuildTools/CodeFixes/DefaultValue/ShouldDeclareDefaultCodeFixProvider.cs @@ -43,7 +43,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var protoMemberAttributeSyntax = root?.FindNode(diagnosticTextSpan) as AttributeSyntax; if (protoMemberAttributeSyntax is null) return; - if (!TryBuildDiagnosticArguments(diagnostic, out var diagnosticArguments)) return; + if (!TryBuildDiagnosticArguments(diagnostic, out var diagnosticArguments)) return; context.RegisterCodeFix( CodeAction.Create( diff --git a/src/protobuf-net.BuildTools/Internal/DataContractContext.cs b/src/protobuf-net.BuildTools/Internal/DataContractContext.cs index bf156bfcf..ac3712062 100644 --- a/src/protobuf-net.BuildTools/Internal/DataContractContext.cs +++ b/src/protobuf-net.BuildTools/Internal/DataContractContext.cs @@ -372,7 +372,7 @@ private MemberInitValueKind CalculateMemberInitialValue( } // calculating the member initial value using semantic model - var semanticModel = context.SemanticModel; + var semanticModel = context.Compilation.GetSemanticModel(memberNode.SyntaxTree); var constantValue = semanticModel.GetConstantValue(initialValueSyntaxNode!); if (!constantValue.HasValue || constantValue.Value is null) { diff --git a/src/protobuf-net.BuildTools/Internal/Utils.cs b/src/protobuf-net.BuildTools/Internal/Utils.cs index 2d98495d6..9152eedb6 100644 --- a/src/protobuf-net.BuildTools/Internal/Utils.cs +++ b/src/protobuf-net.BuildTools/Internal/Utils.cs @@ -37,7 +37,7 @@ internal static class Utils return Convert.ChangeType(value, type, CultureInfo.InvariantCulture); // Looking for ad hoc created TypeDescriptor.ConvertFromInvariantString(Type, string) - bool TryConvertFromInvariantString( + static bool TryConvertFromInvariantString( Type typeToConvert, string? stringValue, out object? conversionResult) diff --git a/src/protobuf-net.Core/Helpers.cs b/src/protobuf-net.Core/Helpers.cs index 0c82bc6b3..2c8607f51 100644 --- a/src/protobuf-net.Core/Helpers.cs +++ b/src/protobuf-net.Core/Helpers.cs @@ -23,7 +23,7 @@ internal static MethodInfo GetStaticMethod(Type declaringType, string name) internal static MethodInfo GetInstanceMethod(Type declaringType, string name, Type[] types) { - if (types is null) types = Type.EmptyTypes; + types ??= Type.EmptyTypes; return declaringType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, types, null); } @@ -94,7 +94,7 @@ internal static MethodInfo GetSetMethod(PropertyInfo property, bool nonPublic, b var method = property.GetSetMethod(nonPublic); if (method is null && !nonPublic && allowInternal) { // could be "internal" or "protected internal"; look for a non-public, then back-check - method = property.GetGetMethod(true); + method = property.GetSetMethod(true); if (method is not null && !(method.IsAssembly || method.IsFamilyOrAssembly)) { method = null; diff --git a/src/protobuf-net.Core/Internal/DynamicallyAccessedMembersAttribute.cs b/src/protobuf-net.Core/Internal/DynamicallyAccessedMembersAttribute.cs index e8d4dead8..55183fb59 100644 --- a/src/protobuf-net.Core/Internal/DynamicallyAccessedMembersAttribute.cs +++ b/src/protobuf-net.Core/Internal/DynamicallyAccessedMembersAttribute.cs @@ -1,12 +1,4 @@ -#if PLAT_DYNAMIC_ACCESS_ATTR -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -// forwards, just to make it explicit that we mean the same -[assembly: TypeForwardedTo(typeof(DynamicallyAccessedMembersAttribute))] -[assembly: TypeForwardedTo(typeof(DynamicallyAccessedMemberTypes))] -#endif - -namespace ProtoBuf.Internal +namespace ProtoBuf.Internal { using System.Diagnostics.CodeAnalysis; internal sealed class DynamicAccess @@ -15,7 +7,8 @@ internal const DynamicallyAccessedMemberTypes ContractType = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor // construction | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties // annotated properties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields // annotated fields and get-only accessors - | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; // callbacks + | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods // callbacks + | DynamicallyAccessedMemberTypes.PublicNestedTypes | DynamicallyAccessedMemberTypes.NonPublicNestedTypes; // nested/child objects internal const DynamicallyAccessedMemberTypes Serializer = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor; diff --git a/src/protobuf-net.Core/Internal/ReadBufferT.cs b/src/protobuf-net.Core/Internal/ReadBufferT.cs index a7640f8f4..f7639b48b 100644 --- a/src/protobuf-net.Core/Internal/ReadBufferT.cs +++ b/src/protobuf-net.Core/Internal/ReadBufferT.cs @@ -14,11 +14,12 @@ namespace ProtoBuf.Internal internal struct ReadBuffer : IDisposable, ICollection, IReadOnlyCollection, ICollection { public void Clear() => _count = 0; - bool ICollection.IsReadOnly => false; - public void CopyTo(T[] array, int arrayIndex = 0) + + readonly bool ICollection.IsReadOnly => false; + public readonly void CopyTo(T[] array, int arrayIndex = 0) => Array.Copy(_arr, 0, array, arrayIndex, _count); - void ICollection.CopyTo(Array array, int index) + readonly void ICollection.CopyTo(Array array, int index) => Array.Copy(_arr, 0, array, index, _count); public T[] ToArray() @@ -43,10 +44,8 @@ public T[] ToArray(T[] prepend) Array.Copy(_arr, 0, arr, oldLen, _count); return arr; } - - - bool ICollection.Contains(T item) + readonly bool ICollection.Contains(T item) => Array.IndexOf(_arr, item, 0, _count) >= 0; bool ICollection.Remove(T item) { @@ -57,7 +56,7 @@ bool ICollection.Remove(T item) Array.Copy(_arr, index + 1, _arr, index, _count - index); return true; } - public IEnumerator GetEnumerator() => _arr.Take(_count).GetEnumerator(); + public readonly IEnumerator GetEnumerator() => _arr.Take(_count).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -65,8 +64,9 @@ bool ICollection.Remove(T item) private T[] _arr; private int _count; - bool ICollection.IsSynchronized => false; - object ICollection.SyncRoot => _arr; + readonly bool ICollection.IsSynchronized => false; + + readonly object ICollection.SyncRoot => _arr; private ReadBuffer(int minimumLength) { @@ -91,12 +91,12 @@ private static void Recyle(ref T[] array) } } - public bool IsEmpty => _count == 0; + public readonly bool IsEmpty => _count == 0; - public int Count => _count; + public readonly int Count => _count; - public ArraySegment Segment => new ArraySegment(_arr, 0, _count); - public Span Span => new Span(_arr, 0, _count); + public readonly ArraySegment Segment => new ArraySegment(_arr, 0, _count); + public readonly Span Span => new Span(_arr, 0, _count); public void Dispose() { diff --git a/src/protobuf-net.Core/MeasureState.cs b/src/protobuf-net.Core/MeasureState.cs index b0cc55b00..37f42d4a5 100644 --- a/src/protobuf-net.Core/MeasureState.cs +++ b/src/protobuf-net.Core/MeasureState.cs @@ -64,13 +64,11 @@ public long LengthOnly() return len; } - private void SerializeCore(ProtoWriter.State state) + private readonly void SerializeCore(ProtoWriter.State state) { try { - var writer = _writer; - if (writer is null) throw new ObjectDisposedException(nameof(MeasureState)); - + var writer = _writer ?? throw new ObjectDisposedException(nameof(MeasureState)); var targetWriter = state.GetWriter(); targetWriter.InitializeFrom(writer); long actual = TypeModel.SerializeImpl(ref state, _value); @@ -89,18 +87,18 @@ private void SerializeCore(ProtoWriter.State state) } } - internal int GetLengthHits(out int misses) => _writer.GetLengthHits(out misses); + internal readonly int GetLengthHits(out int misses) => _writer.GetLengthHits(out misses); /// /// Perform the calculated serialization operation against the provided target stream. If the object state changes between the /// measure and serialize operations, the behavior is undefined. /// - public void Serialize(Stream stream) => SerializeCore(ProtoWriter.State.Create(stream, _model, _userState)); + public readonly void Serialize(Stream stream) => SerializeCore(ProtoWriter.State.Create(stream, _model, _userState)); /// /// Perform the calculated serialization operation against the provided target writer. If the object state changes between the /// measure and serialize operations, the behavior is undefined. /// - public void Serialize(IBufferWriter writer) => SerializeCore(ProtoWriter.State.Create(writer, _model, _userState)); + public readonly void Serialize(IBufferWriter writer) => SerializeCore(ProtoWriter.State.Create(writer, _model, _userState)); } } diff --git a/src/protobuf-net.Core/Meta/SchemaGenerationOptions.cs b/src/protobuf-net.Core/Meta/SchemaGenerationOptions.cs index cc4875f3c..4165c1f3f 100644 --- a/src/protobuf-net.Core/Meta/SchemaGenerationOptions.cs +++ b/src/protobuf-net.Core/Meta/SchemaGenerationOptions.cs @@ -68,6 +68,11 @@ public enum SchemaGenerationFlags /// Record the sub-type relationship formally in schemas /// PreserveSubType = 1 << 1, + + /// + /// Provides support for adding Prefix to names of Enum members in schemas + /// + IncludeEnumNamePrefix = 1 << 2, } diff --git a/src/protobuf-net.Core/Meta/TypeModel.cs b/src/protobuf-net.Core/Meta/TypeModel.cs index d0528822c..37e0aeeb0 100644 --- a/src/protobuf-net.Core/Meta/TypeModel.cs +++ b/src/protobuf-net.Core/Meta/TypeModel.cs @@ -1409,7 +1409,7 @@ internal static T CreateInstance(ISerializationContext context, ISerializer factory) obj = factory.Create(context); // note we already know this is a ref-type - if (obj is null) obj = ActivatorCreate(); + obj ??= ActivatorCreate(); return obj; } else @@ -1905,7 +1905,7 @@ internal static Type ResolveKnownType(string name, Assembly assembly) int i = name.IndexOf(','); string fullName = (i > 0 ? name.Substring(0, i) : name).Trim(); - if (assembly is null) assembly = Assembly.GetCallingAssembly(); + assembly ??= Assembly.GetCallingAssembly(); Type type = assembly?.GetType(fullName); if (type is not null) return type; diff --git a/src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs b/src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs index 95abfb476..57be5e489 100644 --- a/src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs +++ b/src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs @@ -45,7 +45,7 @@ public short ReadInt16() /// in the underlying stream, if multiple readers are used on the same stream) /// [MethodImpl(HotPath)] - public long GetPosition() => _reader._longPosition; + public readonly long GetPosition() => _reader._longPosition; /// /// Reads an unsigned 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 @@ -701,7 +701,7 @@ public void SkipField() } } - internal Type DeserializeType(string typeName) => _reader.DeserializeType(typeName); + internal readonly Type DeserializeType(string typeName) => _reader.DeserializeType(typeName); [MethodImpl(MethodImplOptions.NoInlining)] private void SkipGroup() @@ -788,7 +788,7 @@ internal void CheckFullyConsumed() /// a Variant may be updated to SignedVariant. If the hinted wire-type is unrelated then no change is made. /// [MethodImpl(HotPath)] - public void Hint(WireType wireType) => _reader.Hint(wireType); + public readonly void Hint(WireType wireType) => _reader.Hint(wireType); [MethodImpl(MethodImplOptions.NoInlining)] internal void ThrowWireTypeException() @@ -926,7 +926,7 @@ private void AppendExtensionDataImpl(IExtension extn) /// /// Indicates the underlying proto serialization format on the wire. /// - public WireType WireType + public readonly WireType WireType { [MethodImpl(HotPath)] get => _reader.WireType; @@ -938,7 +938,7 @@ public WireType WireType /// than a second instance of the same string. Disabled by default. Note that this uses /// a custom interner - the system-wide string interner is not used. /// - public bool InternStrings + public readonly bool InternStrings { get => _reader.InternStrings; set => _reader.InternStrings = value; @@ -947,7 +947,7 @@ public bool InternStrings /// /// Gets the number of the field being processed. /// - public int FieldNumber + public readonly int FieldNumber { [MethodImpl(HotPath)] get => _reader._fieldNumber; @@ -1104,7 +1104,7 @@ private void AppendExtensionField(ref ProtoWriter.State writeState) return value; } - internal TypeModel Model + internal readonly TypeModel Model { [MethodImpl(HotPath)] get => _reader?.Model; @@ -1195,7 +1195,7 @@ static T ReadFieldOne(ref State state, SerializerFeatures features, T value, ISe /// /// Gets the serialization context associated with this instance; /// - public ISerializationContext Context + public readonly ISerializationContext Context { [MethodImpl(HotPath)] get => _reader; @@ -1207,7 +1207,7 @@ public ISerializationContext Context /// This is used when decoding packed data. /// [MethodImpl(HotPath)] - public bool HasSubValue(WireType wireType) => ProtoReader.HasSubValue(wireType, _reader); + public readonly bool HasSubValue(WireType wireType) => ProtoReader.HasSubValue(wireType, _reader); /// /// Create an instance of the provided type, respecting any custom factory rules diff --git a/src/protobuf-net.Core/ProtoReader.State.cs b/src/protobuf-net.Core/ProtoReader.State.cs index 6bf196ec2..3c03c1402 100644 --- a/src/protobuf-net.Core/ProtoReader.State.cs +++ b/src/protobuf-net.Core/ProtoReader.State.cs @@ -27,7 +27,7 @@ public void Dispose() this = default; reader?.Dispose(); } - internal SolidState Solidify() => new SolidState( + internal readonly SolidState Solidify() => new SolidState( _reader, _memory.Slice(OffsetInCurrent, RemainingInCurrent)); @@ -132,7 +132,7 @@ private int ParseVarintUInt32Tail(ReadOnlySpan span, ref uint value) } [MethodImpl(HotPath)] - internal void Advance(long count) => _reader.Advance(count); + internal readonly void Advance(long count) => _reader.Advance(count); internal static int TryParseUInt64Varint(ReadOnlySpan span, int offset, out ulong value) { @@ -194,7 +194,7 @@ internal static int TryParseUInt64Varint(ReadOnlySpan span, int offset, ou } [MethodImpl(HotPath)] - internal ProtoReader GetReader() => _reader; + internal readonly ProtoReader GetReader() => _reader; } [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/src/protobuf-net.Core/ProtoWriter.BufferWriter.cs b/src/protobuf-net.Core/ProtoWriter.BufferWriter.cs index 1f7df48fd..899b7ce66 100644 --- a/src/protobuf-net.Core/ProtoWriter.BufferWriter.cs +++ b/src/protobuf-net.Core/ProtoWriter.BufferWriter.cs @@ -339,7 +339,7 @@ protected internal override void WriteSubType(ref State state, T value, ISubT private void WriteWithLengthPrefix(ref State state, T value, ISerializer serializer, PrefixStyle style) { - if (serializer is null) serializer = TypeModel.GetSerializer(Model); + serializer ??= TypeModel.GetSerializer(Model); long calculatedLength = Measure(_nullWriter, value, serializer); switch (style) @@ -378,7 +378,7 @@ private void WriteWithLengthPrefix(ref State state, T value, ISerializer s private void WriteWithLengthPrefix(ref State state, T value, ISubTypeSerializer serializer) where T : class { - if (serializer is null) serializer = TypeModel.GetSubTypeSerializer(Model); + serializer ??= TypeModel.GetSubTypeSerializer(Model); long calculatedLength = Measure(_nullWriter, value, serializer); // we'll always use varint here diff --git a/src/protobuf-net.Core/ProtoWriter.Null.cs b/src/protobuf-net.Core/ProtoWriter.Null.cs index 0f3269a6d..d20b9e44c 100644 --- a/src/protobuf-net.Core/ProtoWriter.Null.cs +++ b/src/protobuf-net.Core/ProtoWriter.Null.cs @@ -139,7 +139,7 @@ private void AdvanceSubMessage(ref State state, long length, PrefixStyle style) } protected internal override void WriteSubType(ref State state, T value, ISubTypeSerializer serializer) { - if (serializer is null) serializer = TypeModel.GetSubTypeSerializer(Model); + serializer ??= TypeModel.GetSubTypeSerializer(Model); var len = Measure(this, value, serializer); AdvanceSubMessage(ref state, len, PrefixStyle.Base128); } diff --git a/src/protobuf-net.Core/ProtoWriter.State.WriteMethods.cs b/src/protobuf-net.Core/ProtoWriter.State.WriteMethods.cs index dc7a58f93..62b7e79e8 100644 --- a/src/protobuf-net.Core/ProtoWriter.State.WriteMethods.cs +++ b/src/protobuf-net.Core/ProtoWriter.State.WriteMethods.cs @@ -485,7 +485,7 @@ static WireType AssertWrappedAndGetWireType(ref SerializerFeatures features, public void WriteBaseType([DynamicallyAccessedMembers(DynamicAccess.ContractType)] T value, ISubTypeSerializer serializer = null) where T : class => (serializer ?? TypeModel.GetSubTypeSerializer(Model)).WriteSubType(ref this, value); - internal TypeModel Model => _writer?.Model; + internal readonly TypeModel Model => _writer?.Model; /// /// Gets the serializer associated with a specific type @@ -493,28 +493,28 @@ public void WriteBaseType([DynamicallyAccessedMembers(DynamicAccess.ContractT [MethodImpl(HotPath)] public ISerializer GetSerializer<[DynamicallyAccessedMembers(DynamicAccess.ContractType)] T>() => TypeModel.GetSerializer(Model); - internal WireType WireType + internal readonly WireType WireType { get => _writer.WireType; set => _writer.WireType = value; } - internal int Depth => _writer.Depth; + internal readonly int Depth => _writer.Depth; - internal int FieldNumber + internal readonly int FieldNumber { get => _writer.fieldNumber; private set => _writer.fieldNumber = value; } - internal long GetPosition() => _writer._position64; + internal readonly long GetPosition() => _writer._position64; - internal ProtoWriter GetWriter() => _writer; + internal readonly ProtoWriter GetWriter() => _writer; /// /// The serialization context associated with this instance /// - public ISerializationContext Context => _writer; + public readonly ISerializationContext Context => _writer; /// /// Writes a byte-array to the stream; supported wire-types: String @@ -696,7 +696,7 @@ public void WriteBytes(ReadOnlySpan data) /// Abandon any pending unflushed data /// [MethodImpl(HotPath)] - public void Abandon() => _writer?.Abandon(); + public readonly void Abandon() => _writer?.Abandon(); void CheckClear() => _writer?.CheckClear(ref this); @@ -770,7 +770,7 @@ internal void WriteObject(object value, [DynamicallyAccessedMembers(DynamicAcces { ThrowHelper.ThrowInvalidOperationException("Cannot serialize sub-objects unless a model is provided"); } - if (type is null) type = value.GetType(); + type ??= value.GetType(); if (WireType != WireType.None) ThrowInvalidSerializationOperation(); switch (style) @@ -951,7 +951,7 @@ internal void ThrowInvalidSerializationOperation() } [MethodImpl(MethodImplOptions.NoInlining)] - internal void ThrowTooDeep(int depth) => ThrowHelper.ThrowInvalidOperationException("Maximum model depth exceeded (see " + nameof(TypeModel) + "." + nameof(TypeModel.MaxDepth) + "): " + depth.ToString()); + internal readonly void ThrowTooDeep(int depth) => ThrowHelper.ThrowInvalidOperationException("Maximum model depth exceeded (see " + nameof(TypeModel) + "." + nameof(TypeModel.MaxDepth) + "): " + depth.ToString()); /// /// Used for packed encoding; indicates that the next field should be skipped rather than @@ -959,7 +959,7 @@ internal void ThrowInvalidSerializationOperation() /// when the attempt is made to write the (incorrect) field. The wire-type is taken from the /// subsequent call to WriteFieldHeader. Only primitive types can be packed. /// - public void SetPackedField(int fieldNumber) + public readonly void SetPackedField(int fieldNumber) { if (fieldNumber <= 0) ThrowHelper.ThrowArgumentOutOfRangeException(nameof(fieldNumber)); _writer.packedFieldNumber = fieldNumber; @@ -969,7 +969,7 @@ public void SetPackedField(int fieldNumber) /// Used for packed encoding; explicitly reset the packed field marker; this is not required /// if using StartSubItem/EndSubItem /// - public void ClearPackedField(int fieldNumber) + public readonly void ClearPackedField(int fieldNumber) { if (fieldNumber != _writer.packedFieldNumber) ThrowWrongPackedField(fieldNumber, _writer); diff --git a/src/protobuf-net.Core/ProtoWriter.State.cs b/src/protobuf-net.Core/ProtoWriter.State.cs index 2b0b5d49c..7777cf5bb 100644 --- a/src/protobuf-net.Core/ProtoWriter.State.cs +++ b/src/protobuf-net.Core/ProtoWriter.State.cs @@ -19,7 +19,7 @@ public partial class ProtoWriter [StructLayout(LayoutKind.Auto)] public ref partial struct State { - internal bool IsActive => !_span.IsEmpty; + internal readonly bool IsActive => !_span.IsEmpty; private Span _span; private Memory _memory; @@ -33,7 +33,7 @@ internal State(ProtoWriter writer) private ProtoWriter _writer; - internal Span Remaining + internal readonly Span Remaining { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _span.Slice(OffsetInCurrent); @@ -78,7 +78,7 @@ internal void LocalWriteFixed32(uint value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void ReverseLast32() => _span.Slice(OffsetInCurrent - 4, 4).Reverse(); + internal readonly void ReverseLast32() => _span.Slice(OffsetInCurrent - 4, 4).Reverse(); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void LocalAdvance(int bytes) diff --git a/src/protobuf-net.Core/Serializers/IProtoSerializerT.cs b/src/protobuf-net.Core/Serializers/IProtoSerializerT.cs index fbb41bcd8..d22df1e5a 100644 --- a/src/protobuf-net.Core/Serializers/IProtoSerializerT.cs +++ b/src/protobuf-net.Core/Serializers/IProtoSerializerT.cs @@ -369,12 +369,12 @@ public T Value [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CreateIfNeeded() => _ = Value; - internal object RawValue => _value; + internal readonly object RawValue => _value; /// /// Indicates whether an instance currently exists /// - public bool HasValue => _value is not null; + public readonly bool HasValue => _value is not null; [MethodImpl(MethodImplOptions.NoInlining)] private T Cast() diff --git a/src/protobuf-net.Core/Serializers/RepeatedSerializer.Immutable.cs b/src/protobuf-net.Core/Serializers/RepeatedSerializer.Immutable.cs index e4efd7b48..3cef0eac0 100644 --- a/src/protobuf-net.Core/Serializers/RepeatedSerializer.Immutable.cs +++ b/src/protobuf-net.Core/Serializers/RepeatedSerializer.Immutable.cs @@ -103,13 +103,13 @@ internal override void Write(ref ProtoWriter.State state, int fieldNumber, Seria [StructLayout(LayoutKind.Auto)] struct Enumerator : IEnumerator { - public void Reset() => ThrowHelper.ThrowNotSupportedException(); + public readonly void Reset() => ThrowHelper.ThrowNotSupportedException(); public Enumerator(ImmutableArray array) => _iter = array.GetEnumerator(); private ImmutableArray.Enumerator _iter; public T Current => _iter.Current; object IEnumerator.Current => _iter.Current; public bool MoveNext() => _iter.MoveNext(); - public void Dispose() { } + public readonly void Dispose() { } } } @@ -330,11 +330,11 @@ struct Enumerator : IEnumerator T IEnumerator.Current => _iter.Current; object IEnumerator.Current => _iter.Current; - void IDisposable.Dispose() { } + readonly void IDisposable.Dispose() { } bool IEnumerator.MoveNext() => _iter.MoveNext(); - void IEnumerator.Reset() => ThrowHelper.ThrowNotImplementedException(); + readonly void IEnumerator.Reset() => ThrowHelper.ThrowNotImplementedException(); } } sealed class ImmutableIStackSerializer : RepeatedSerializer, T> @@ -443,11 +443,11 @@ struct Enumerator : IEnumerator T IEnumerator.Current => _iter.Current; object IEnumerator.Current => _iter.Current; - void IDisposable.Dispose() { } + readonly void IDisposable.Dispose() { } bool IEnumerator.MoveNext() => _iter.MoveNext(); - void IEnumerator.Reset() => ThrowHelper.ThrowNotImplementedException(); + readonly void IEnumerator.Reset() => ThrowHelper.ThrowNotImplementedException(); } } sealed class ImmutableIQueueSerializer : RepeatedSerializer, T> diff --git a/src/protobuf-net.Core/Serializers/RepeatedSerializer.cs b/src/protobuf-net.Core/Serializers/RepeatedSerializer.cs index 6c0f8e39b..25ff5127c 100644 --- a/src/protobuf-net.Core/Serializers/RepeatedSerializer.cs +++ b/src/protobuf-net.Core/Serializers/RepeatedSerializer.cs @@ -5,8 +5,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using static System.Collections.Specialized.BitVector32; namespace ProtoBuf.Serializers { @@ -74,7 +76,18 @@ public static partial class RepeatedSerializer where TCollection : Stack => SerializerCache>.InstanceField; + /// Create a serializer that operates on sets + [MethodImpl(ProtoReader.HotPath)] + public static RepeatedSerializer CreateSet() + where TCollection : ISet + => SerializerCache>.InstanceField; +#if NET6_0_OR_GREATER + /// Create a serializer that operates on sets + [MethodImpl(ProtoReader.HotPath)] + public static RepeatedSerializer, T> CreateReadOnySet<[DynamicallyAccessedMembers(DynamicAccess.ContractType)] T>() + => SerializerCache>.InstanceField; +#endif /// Reverses a range of values [MethodImpl(ProtoReader.HotPath)] // note: not "in" because ArraySegment isn't "readonly" on all TFMs @@ -400,7 +413,7 @@ internal override void Write(ref ProtoWriter.State state, int fieldNumber, Seria Write(ref state, fieldNumber, category, wireType, ref iter, serializer, features); } } - + sealed class ListSerializer : ListSerializer, T> { protected override List Initialize(List values, ISerializationContext context) @@ -569,7 +582,7 @@ internal override void Write(ref ProtoWriter.State state, int fieldNumber, Seria [StructLayout(LayoutKind.Auto)] struct Enumerator : IEnumerator { - public void Reset() => ThrowHelper.ThrowNotSupportedException(); + public readonly void Reset() => ThrowHelper.ThrowNotSupportedException(); private readonly T[] _array; private int _index; public Enumerator(T[] array) @@ -577,10 +590,11 @@ public Enumerator(T[] array) _array = array; _index = -1; } - public T Current => _array[_index]; - object IEnumerator.Current => _array[_index]; + public readonly T Current => _array[_index]; + + readonly object IEnumerator.Current => _array[_index]; public bool MoveNext() => ++_index < _array.Length; - public void Dispose() { } + public readonly void Dispose() { } } } @@ -620,4 +634,93 @@ internal override void Write(ref ProtoWriter.State state, int fieldNumber, Seria Write(ref state, fieldNumber, category, wireType, ref iter, serializer, features); } } + + sealed class SetSerializer : RepeatedSerializer + where TCollection : ISet + { + protected override TCollection Initialize(TCollection values, ISerializationContext context) => + values ?? (typeof(TCollection).IsInterface ? (TCollection)(object)new HashSet() : TypeModel.ActivatorCreate()); + + protected override TCollection Clear(TCollection values, ISerializationContext context) + { + values.Clear(); + return values; + } + + protected override int TryGetCount(TCollection values) => values is null ? 0 : values.Count; + + protected override TCollection AddRange(TCollection values, ref ArraySegment newValues, ISerializationContext context) + { + values.UnionWith(newValues); + return values; + } + + internal override long Measure(TCollection values, IMeasuringSerializer serializer, ISerializationContext context, WireType wireType) + { + var iter = values.GetEnumerator(); + return Measure(ref iter, serializer, context, wireType); + } + + internal override void WritePacked(ref ProtoWriter.State state, TCollection values, IMeasuringSerializer serializer, WireType wireType) + { + var iter = values.GetEnumerator(); + WritePacked(ref state, ref iter, serializer, wireType); + } + + internal override void Write(ref ProtoWriter.State state, int fieldNumber, SerializerFeatures category, WireType wireType, TCollection values, ISerializer serializer, SerializerFeatures features) + { + var iter = values.GetEnumerator(); + Write(ref state, fieldNumber, category, wireType, ref iter, serializer, features); + } + } + +#if NET6_0_OR_GREATER + sealed class ReadOnlySetSerializer : RepeatedSerializer, T> + { + protected override IReadOnlySet Initialize(IReadOnlySet values, ISerializationContext context) => values ?? new HashSet(); + + protected override IReadOnlySet Clear(IReadOnlySet values, ISerializationContext context) + { + if (values is ISet target && !target.IsReadOnly) + { + target.Clear(); + return values; + } + return new HashSet(); + } + + protected override int TryGetCount(IReadOnlySet values) => values is null ? 0 : values.Count; + + protected override IReadOnlySet AddRange(IReadOnlySet values, ref ArraySegment newValues, ISerializationContext context) + { + if (values is ISet target && !target.IsReadOnly) + { + target.UnionWith(newValues); + return values; + } + + var resultSet = new HashSet(values.Count + newValues.Count); + resultSet.UnionWith(values); + resultSet.UnionWith(newValues); + return resultSet; + } + internal override long Measure(IReadOnlySet values, IMeasuringSerializer serializer, ISerializationContext context, WireType wireType) + { + var iter = values.GetEnumerator(); + return Measure(ref iter, serializer, context, wireType); + } + + internal override void WritePacked(ref ProtoWriter.State state, IReadOnlySet values, IMeasuringSerializer serializer, WireType wireType) + { + var iter = values.GetEnumerator(); + WritePacked(ref state, ref iter, serializer, wireType); + } + + internal override void Write(ref ProtoWriter.State state, int fieldNumber, SerializerFeatures category, WireType wireType, IReadOnlySet values, ISerializer serializer, SerializerFeatures features) + { + var iter = values.GetEnumerator(); + Write(ref state, fieldNumber, category, wireType, ref iter, serializer, features); + } + } +#endif } diff --git a/src/protobuf-net.Core/WellKnownTypes/Timestamp.cs b/src/protobuf-net.Core/WellKnownTypes/Timestamp.cs index 5c2559d3d..7f3c8f955 100644 --- a/src/protobuf-net.Core/WellKnownTypes/Timestamp.cs +++ b/src/protobuf-net.Core/WellKnownTypes/Timestamp.cs @@ -47,7 +47,7 @@ namespace ProtoBuf.WellKnownTypes /// [ProtoContract(Name = ".google.protobuf.Timestamp", Serializer = typeof(PrimaryTypeProvider), Origin = "google/protobuf/timestamp.proto")] [StructLayout(LayoutKind.Auto)] - public readonly struct Timestamp + public readonly struct Timestamp : IComparable, IEquatable { /// /// Represents seconds of UTC time since Unix epoch @@ -89,6 +89,18 @@ public Timestamp Normalize() /// Converts a Timestamp to a DateTime public DateTime AsDateTime() => TimestampEpoch.AddTicks(PrimaryTypeProvider.ToTicks(Seconds, Nanoseconds)); + public int CompareTo(Timestamp other) + { + if (Seconds != other.Seconds) + { + return Seconds.CompareTo(other.Seconds); + } + + return Nanoseconds.CompareTo(other.Nanoseconds); + } + + public bool Equals(Timestamp other) => Seconds == other.Seconds && Nanoseconds == other.Nanoseconds; + /// Converts a Timestamp to a DateTime public static implicit operator DateTime(Timestamp value) => value.AsDateTime(); diff --git a/src/protobuf-net.Core/WireType.cs b/src/protobuf-net.Core/WireType.cs index 1c5ac6534..d68027cf6 100644 --- a/src/protobuf-net.Core/WireType.cs +++ b/src/protobuf-net.Core/WireType.cs @@ -23,7 +23,9 @@ public enum WireType /// /// Base-128 variable-length encoding /// +#pragma warning disable CA1069 // duplicate enum value Varint = 0, +#pragma warning restore CA1069 // duplicate enum value /// /// Fixed-length 8-byte encoding @@ -65,6 +67,8 @@ public enum WireType /// zig-zag semantics (so -ve numbers aren't a significant overhead) /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] +#pragma warning disable CA1069 // duplicate enum value SignedVarint = Varint | (1 << 3), +#pragma warning restore CA1069 // duplicate enum value } } diff --git a/src/protobuf-net.Core/protobuf-net.Core.csproj b/src/protobuf-net.Core/protobuf-net.Core.csproj index 1c4edc120..150fc6289 100644 --- a/src/protobuf-net.Core/protobuf-net.Core.csproj +++ b/src/protobuf-net.Core/protobuf-net.Core.csproj @@ -6,7 +6,6 @@ true True protobuf-net.Core - 10 protobuf-net.Core Provides simple access to fast and efficient "Protocol Buffers" serialization from .NET applications diff --git a/src/protobuf-net.FSharp/protobuf-net.FSharp.csproj b/src/protobuf-net.FSharp/protobuf-net.FSharp.csproj index 7a24719de..fc46b25b1 100644 --- a/src/protobuf-net.FSharp/protobuf-net.FSharp.csproj +++ b/src/protobuf-net.FSharp/protobuf-net.FSharp.csproj @@ -6,8 +6,7 @@ - - + diff --git a/src/protobuf-net.NodaTime/NodaTimeExtensions.cs b/src/protobuf-net.NodaTime/NodaTimeExtensions.cs index 070562a88..c79f89b90 100644 --- a/src/protobuf-net.NodaTime/NodaTimeExtensions.cs +++ b/src/protobuf-net.NodaTime/NodaTimeExtensions.cs @@ -33,9 +33,9 @@ public static RuntimeTypeModel AddNodaTime(this RuntimeTypeModel model) static void Add(RuntimeTypeModel model, Type type, string name, string origin, Type serializerType) { var mt = model.Add(type, true); - if (name is object) mt.Name = name; - if (origin is object) mt.Origin = origin; - if (serializerType is object) mt.SerializerType = serializerType; + if (name is not null) mt.Name = name; + if (origin is not null) mt.Origin = origin; + if (serializerType is not null) mt.SerializerType = serializerType; } } diff --git a/src/protobuf-net.Protogen/protobuf-net.Protogen.csproj b/src/protobuf-net.Protogen/protobuf-net.Protogen.csproj index 922d5d819..388cbea39 100644 --- a/src/protobuf-net.Protogen/protobuf-net.Protogen.csproj +++ b/src/protobuf-net.Protogen/protobuf-net.Protogen.csproj @@ -1,7 +1,7 @@  Exe - net6.0 + net6.0;net8.0 Debug;Release;VS true True diff --git a/src/protobuf-net.Reflection.Test/Schemas/SchemaTests.cs b/src/protobuf-net.Reflection.Test/Schemas/SchemaTests.cs index 70f44609e..303480a2a 100644 --- a/src/protobuf-net.Reflection.Test/Schemas/SchemaTests.cs +++ b/src/protobuf-net.Reflection.Test/Schemas/SchemaTests.cs @@ -14,6 +14,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -170,7 +171,7 @@ public void EverythingProtoLangver3() _output.WriteLine(sourceFiles[0]); using var csharp = new CSharpCodeProvider(new Dictionary { - { "CompilerVersion", "v3.5"} + // { "CompilerVersion", "v3.5"} }); var p = new CompilerParameters { @@ -220,7 +221,7 @@ public void DescriptorProtoVB() _output.WriteLine(sourceFiles[0]); using var vb = new VBCodeProvider(new Dictionary { - { "CompilerVersion", "v3.5"} + // { "CompilerVersion", "v3.5"} }); var p = new CompilerParameters { @@ -257,7 +258,7 @@ public void LargeDefaultValueIsCorrect() using var csharp = new CSharpCodeProvider(new Dictionary { - { "CompilerVersion", "v3.5"} + // { "CompilerVersion", "v3.5"} }); var p = new CompilerParameters { @@ -296,7 +297,7 @@ public void LargeDefaultValueIsCorrect() [Theory] [MemberData(nameof(GetSchemas))] - public void CompareProtoToParser(string path, bool includeImports) + public async Task CompareProtoToParser(string path, bool includeImports) { if (path == "google/protobuf/map_unittest_proto3.proto") return; // TODO known oddity @@ -333,8 +334,8 @@ public void CompareProtoToParser(string path, bool includeImports) } exitCode = proc.ExitCode; string err = "", @out = ""; - if (stdout.Wait(1000)) @out = stdout.Result; - if (stderr.Wait(1000)) err = stderr.Result; + if (await stdout.WaitForAsync(1000)) @out = await stdout; + if (await stdout.WaitForAsync(1000)) err = await stderr; if (!string.IsNullOrWhiteSpace(@out)) { @@ -381,8 +382,8 @@ public void CompareProtoToParser(string path, bool includeImports) } exitCode = proc.ExitCode; string err = "", @out = ""; - if (stdout.Wait(1000)) @out = stdout.Result; - if (stderr.Wait(1000)) err = stderr.Result; + if (await stdout.WaitForAsync(1000)) @out = await stdout; + if (await stderr.WaitForAsync(1000)) err = await stderr; if (!string.IsNullOrWhiteSpace(@out)) { @@ -523,4 +524,27 @@ public static string GetPrettyHex(byte[] bytes) return sb.ToString(); } } + + static class TaskExtensions + { +#if NETFRAMEWORK + public static Task WaitForAsync(this Task task, int timeout) + => task.Wait(timeout) ? SharedTrue : SharedFalse; + + private static readonly Task SharedTrue = Task.FromResult(true), SharedFalse = Task.FromResult(false); +#else + public async static Task WaitForAsync(this Task task, int timeout) + { + try + { + await task.WaitAsync(TimeSpan.FromMilliseconds(timeout)); + return true; + } + catch (TimeoutException) + { + return false; + } + } +#endif + } } diff --git a/src/protobuf-net.Reflection/CSharpCodeGenerator.cs b/src/protobuf-net.Reflection/CSharpCodeGenerator.cs index 3073b4684..569a5f808 100644 --- a/src/protobuf-net.Reflection/CSharpCodeGenerator.cs +++ b/src/protobuf-net.Reflection/CSharpCodeGenerator.cs @@ -126,7 +126,7 @@ protected override string Escape(string identifier) protected override string GetLanguageVersion(FileDescriptorProto obj) => obj?.Options?.GetOptions()?.CSharpLanguageVersion; - private const string AdditionalSuppressionCodes = ", IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192"; + private const string AdditionalSuppressionCodes = ", CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192"; /// /// Start a file @@ -406,7 +406,7 @@ private string GetDefaultValue(GeneratorContext ctx, FieldDescriptorProto obj, s case FieldDescriptorProto.Type.TypeSfixed64: case FieldDescriptorProto.Type.TypeSint64: case FieldDescriptorProto.Type.TypeInt64: - suffix = "l"; + suffix = "L"; break; case FieldDescriptorProto.Type.TypeFixed64: case FieldDescriptorProto.Type.TypeUint64: @@ -578,7 +578,7 @@ void WriteCompatibilityLevelAttribute() ctx.WriteLine($"{GetAccess(GetAccess(field))} global::System.Collections.Generic.List<{typeName}> {Escape(name)} {{ get; {(allowSet ? "" : "private ")}set; }}"); } } - else if (oneOf is object) + else if (oneOf is not null) { var defValue = string.IsNullOrWhiteSpace(defaultValue) ? (ctx.Supports(CSharp7_1) ? "default" : $"default({typeName})") : (defaultValue + suffix); var fieldName = GetOneOfFieldName(oneOf.OneOf); diff --git a/src/protobuf-net.Reflection/CodeGenerator.cs b/src/protobuf-net.Reflection/CodeGenerator.cs index e42ae13ac..3458b602b 100644 --- a/src/protobuf-net.Reflection/CodeGenerator.cs +++ b/src/protobuf-net.Reflection/CodeGenerator.cs @@ -386,7 +386,7 @@ protected bool TrackFieldPresence(GeneratorContext ctx, FieldDescriptorProto fie // get the oneof, ignoring 'synthetic' oneofs from proto3-optional // (the CountTotal check would *also* work for this, but: let's be explicit and intentional) oneOf = (field.ShouldSerializeOneofIndex() && !field.Proto3Optional) ? oneOfs[field.OneofIndex] : null; - if (oneOf is object && !ctx.OneOfEnums && oneOf.CountTotal == 1) + if (oneOf is not null && !ctx.OneOfEnums && oneOf.CountTotal == 1) { oneOf = null; // not really a one-of, then! } @@ -578,10 +578,7 @@ internal GeneratorContext(CommonCodeGenerator generator, FileDescriptorProto fil if (options != null) options.TryGetValue("langver", out langver); // explicit option first if (string.IsNullOrWhiteSpace(langver)) langver = generator?.GetLanguageVersion(file); // then from file - if (nameNormalizer == null) - { - nameNormalizer = NameNormalizer.Default; - } + nameNormalizer ??= NameNormalizer.Default; nameNormalizer.IsCaseSensitive = generator.IsCaseSensitive; File = file; diff --git a/src/protobuf-net.Reflection/Descriptor.cs b/src/protobuf-net.Reflection/Descriptor.cs index b9d795b4a..1646c4236 100644 --- a/src/protobuf-net.Reflection/Descriptor.cs +++ b/src/protobuf-net.Reflection/Descriptor.cs @@ -5,7 +5,7 @@ // #region Designer generated code -#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning disable CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 namespace Google.Protobuf.Reflection { @@ -1322,5 +1322,5 @@ public int End } -#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning restore CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 #endregion diff --git a/src/protobuf-net.Reflection/Parsers.cs b/src/protobuf-net.Reflection/Parsers.cs index 2d1818298..e43418953 100644 --- a/src/protobuf-net.Reflection/Parsers.cs +++ b/src/protobuf-net.Reflection/Parsers.cs @@ -657,7 +657,7 @@ private void ParseMap(ParserContext ctx) } } }; - if (msgType.Options == null) msgType.Options = new MessageOptions(); + msgType.Options ??= new MessageOptions(); msgType.Options.MapEntry = true; NestedTypes.Add(msgType); @@ -3047,7 +3047,7 @@ public void Fill(T obj) where T : class, ISchemaObject if (tokens.ConsumeIf(TokenType.Symbol, "{")) { - if (obj == null) obj = new T(); + obj ??= new T(); bool any = false; while (!tokens.ConsumeIf(TokenType.Symbol, "}")) { @@ -3092,7 +3092,7 @@ public void Fill(T obj) where T : class, ISchemaObject } else { - if (obj == null) obj = new T(); + obj ??= new T(); if (singleKey == "deprecated") { obj.Deprecated = tokens.ConsumeBoolean(); diff --git a/src/protobuf-net.Reflection/TokenExtensions.cs b/src/protobuf-net.Reflection/TokenExtensions.cs index 476a245c2..29ae0a1fc 100644 --- a/src/protobuf-net.Reflection/TokenExtensions.cs +++ b/src/protobuf-net.Reflection/TokenExtensions.cs @@ -345,7 +345,7 @@ static void AppendEscaped(MemoryStream target, char c) var value = token.Value; if (string.IsNullOrEmpty(value)) return; - if (ms == null) ms = new MemoryStream(value.Length); + ms ??= new MemoryStream(value.Length); uint escapedCodePoint = 0; int escapeLength = 0; for (int i = 0; i < value.Length; i++) diff --git a/src/protobuf-net.Reflection/VBCodeGenerator.cs b/src/protobuf-net.Reflection/VBCodeGenerator.cs index 07b2e9031..55d089a85 100644 --- a/src/protobuf-net.Reflection/VBCodeGenerator.cs +++ b/src/protobuf-net.Reflection/VBCodeGenerator.cs @@ -582,7 +582,7 @@ protected override void WriteField(GeneratorContext ctx, FieldDescriptorProto fi } } } - else if (oneOf is object) + else if (oneOf is not null) { var defValue = string.IsNullOrWhiteSpace(defaultValue) ? $"CType(Nothing, {typeName})" : defaultValue; var fieldName = GetOneOfFieldName(oneOf.OneOf); diff --git a/src/protobuf-net.Reflection/protobuf-net.Reflection.csproj b/src/protobuf-net.Reflection/protobuf-net.Reflection.csproj index 58d59a9fd..30ad70efc 100644 --- a/src/protobuf-net.Reflection/protobuf-net.Reflection.csproj +++ b/src/protobuf-net.Reflection/protobuf-net.Reflection.csproj @@ -4,7 +4,6 @@ protobuf-net.Reflection ProtoBuf DSL (proto2 / proto3) and descriptor tools for protobuf-net net462;netstandard2.0 - 10 True true Debug;Release;VS diff --git a/src/protobuf-net.ServiceModel/ServiceModel/ProtoEndpointBehavior.cs b/src/protobuf-net.ServiceModel/ServiceModel/ProtoEndpointBehavior.cs index 4f91754f7..eb6973690 100644 --- a/src/protobuf-net.ServiceModel/ServiceModel/ProtoEndpointBehavior.cs +++ b/src/protobuf-net.ServiceModel/ServiceModel/ProtoEndpointBehavior.cs @@ -65,7 +65,7 @@ private static void ReplaceDataContractSerializerOperationBehavior(ServiceEndpoi private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description) { DataContractSerializerOperationBehavior dcsOperationBehavior = description.Behaviors.Find(); - if (dcsOperationBehavior is object) + if (dcsOperationBehavior is not null) { description.Behaviors.Remove(dcsOperationBehavior); diff --git a/src/protobuf-net.ServiceModel/ServiceModel/XmlProtoSerializer.cs b/src/protobuf-net.ServiceModel/ServiceModel/XmlProtoSerializer.cs index 31f90148e..c6006a62c 100644 --- a/src/protobuf-net.ServiceModel/ServiceModel/XmlProtoSerializer.cs +++ b/src/protobuf-net.ServiceModel/ServiceModel/XmlProtoSerializer.cs @@ -60,7 +60,7 @@ public XmlProtoSerializer(TypeModel model, [DynamicallyAccessedMembers(DynamicAc private static bool IsKnownType(TypeModel model, Type type, out bool isList) { - if (model is object && type is object) + if (model is not null && type is not null) { if (model.CanSerialize(type, true, true, true, out var category)) { diff --git a/src/protobuf-net.Test/App.config b/src/protobuf-net.Test/App.config new file mode 100644 index 000000000..90a7affa2 --- /dev/null +++ b/src/protobuf-net.Test/App.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/protobuf-net.Test/Attribs/ComplexMultiTypes.cs b/src/protobuf-net.Test/Attribs/ComplexMultiTypes.cs index 00ecca84b..6ea96f64c 100644 --- a/src/protobuf-net.Test/Attribs/ComplexMultiTypes.cs +++ b/src/protobuf-net.Test/Attribs/ComplexMultiTypes.cs @@ -120,7 +120,7 @@ private static void CheckEmptyEntityDto(TypeModel model, string message) { Assert.Equal(typeof(EntityDTO), result.GetType()); //, message + ":type"); Assert.Equal(1, result.Id); //, message + ":Id"); - Assert.Equal(0, result.Components.Count); //, message + ":Count"); + Assert.Empty(result.Components); //, message + ":Count"); } [Fact] diff --git a/src/protobuf-net.Test/Attribs/PointStruct.cs b/src/protobuf-net.Test/Attribs/PointStruct.cs index 18c0a7035..6b0bf8929 100644 --- a/src/protobuf-net.Test/Attribs/PointStruct.cs +++ b/src/protobuf-net.Test/Attribs/PointStruct.cs @@ -48,8 +48,8 @@ public struct Point { [ProtoMember(1)] private readonly int x; [ProtoMember(2)] private readonly int y; - public int X { get { return x; } } - public int Y { get { return y; } } + public readonly int X { get { return x; } } + public readonly int Y { get { return y; } } public Point(int x, int y) { this.x = x; diff --git a/src/protobuf-net.Test/BufferWriterTests.cs b/src/protobuf-net.Test/BufferWriterTests.cs index 6a0798c92..1f2cda484 100644 --- a/src/protobuf-net.Test/BufferWriterTests.cs +++ b/src/protobuf-net.Test/BufferWriterTests.cs @@ -135,7 +135,7 @@ class ASerializer : ISerializer public A Read(ref ProtoReader.State state, A value) { int fieldHeader; - if (value == null) value = new A(); + value ??= new A(); while ((fieldHeader = state.ReadFieldHeader()) > 0) { switch (fieldHeader) diff --git a/src/protobuf-net.Test/FX11/Class1.cs b/src/protobuf-net.Test/FX11/Class1.cs index d5a9e43a0..b855dc1cd 100644 --- a/src/protobuf-net.Test/FX11/Class1.cs +++ b/src/protobuf-net.Test/FX11/Class1.cs @@ -30,14 +30,14 @@ public override string ToString() public struct CustomerStruct { private int id; - public int Id { get { return id; } set { id = value; } } + public int Id { readonly get { return id; } set { id = value; } } public string Name; #if !FX11 private double? howMuch; - public double? HowMuch { get { return howMuch; } set { howMuch = value; } } + public double? HowMuch { readonly get { return howMuch; } set { howMuch = value; } } public bool? HasValue; #endif - public override string ToString() + public override readonly string ToString() { string s = id + ": " + Name; #if !FX11 diff --git a/src/protobuf-net.Test/FX11/Program.cs b/src/protobuf-net.Test/FX11/Program.cs index a1ef48f68..3db617f8a 100644 --- a/src/protobuf-net.Test/FX11/Program.cs +++ b/src/protobuf-net.Test/FX11/Program.cs @@ -274,24 +274,15 @@ private static Product Read(ref ProtoReader.State state, Product product1) switch (num) { case 1: - if (product1 == null) - { - product1 = new Product(); - } + product1 ??= new Product(); product1.ProductID = state.ReadInt32(); continue; case 2: - if (product1 == null) - { - product1 = new Product(); - } + product1 ??= new Product(); product1.ProductName = state.ReadString(); continue; case 3: - if (product1 == null) - { - product1 = new Product(); - } + product1 ??= new Product(); product1.SupplierID = new int?(state.ReadInt32()); continue; } diff --git a/src/protobuf-net.Test/Issues/Issue1084.cs b/src/protobuf-net.Test/Issues/Issue1084.cs new file mode 100644 index 000000000..dd75a8e0d --- /dev/null +++ b/src/protobuf-net.Test/Issues/Issue1084.cs @@ -0,0 +1,53 @@ +using ProtoBuf.Meta; +using ProtoBuf.unittest; +using System; +using System.IO; +using Xunit; +using Xunit.Sdk; + +namespace ProtoBuf.Test.Issues +{ + + public class Issue1084 // https://github.com/protobuf-net/protobuf-net/issues/1084 + { + [Fact] + public void Execute() + { + var model = RuntimeTypeModel.Create(); + model.AutoCompile = false; + model.Add(typeof(WithInternalSetter)); + Verify(model, "Runtime"); + model.CompileInPlace(); + Verify(model, "CompileInPlace"); + // Verify(model.Compile(), "Compile"); + // Verify(PEVerify.CompileAndVerify(model), "Full Compile"); + + static void Verify(TypeModel model, string label) + { + try + { + using var ms = new MemoryStream(); + model.Serialize(ms, new WithInternalSetter { Id = 42 }); + if (!ms.TryGetBuffer(out var segment)) segment = new(ms.ToArray()); + Assert.Equal("08-2A", BitConverter.ToString(segment.Array, segment.Offset, segment.Count)); + ms.Position = 0; + Assert.Equal(42, model.Deserialize(ms).Id); + + } + catch (Exception ex) when (ex is not XunitException) + { + Assert.Fail(label + ":" + ex.Message); + } + } + } + + + + [ProtoContract] + public class WithInternalSetter + { + [ProtoMember(1)] + public int Id { get; internal set; } + } + } +} \ No newline at end of file diff --git a/src/protobuf-net.Test/Issues/Issue1102.cs b/src/protobuf-net.Test/Issues/Issue1102.cs new file mode 100644 index 000000000..670a4f1b6 --- /dev/null +++ b/src/protobuf-net.Test/Issues/Issue1102.cs @@ -0,0 +1,185 @@ +using ProtoBuf.Meta; +using System.Reflection; +using Xunit; + +namespace ProtoBuf.Test.Issues +{ + public class Issue1102 + { + + [Fact] + public void TestEnumOrderWithNegativesAndOutOfRangeValues() + { + var model = RuntimeTypeModel.Create(); + model.UseImplicitZeroDefaults = false; + + string proto = model.GetSchema(typeof(HazOutOfRangeAndNegatives), ProtoSyntax.Proto3); + + Assert.Equal(@"syntax = ""proto3""; +package ProtoBuf.Test.Issues; + +message HazOutOfRangeAndNegatives { + int64 OutOfRange = 1; // declared as invalid enum: OutOfRangeEnum + InRangeEnum InRange = 2; +} +enum InRangeEnum { + ZERO = 0; // proto3 requires a zero value as the first item (it can be named anything) + A = 1; + C = 2147483647; + E = -2147483647; + B = -4; +} +/* for context only +enum OutOfRangeEnum { + ZERO = 0; // proto3 requires a zero value as the first item (it can be named anything) + A = 1; + C = 2147483647; + E = -2147483647; + B = -4; + // D = 2147483648; // note: enums should be valid 32-bit integers + // F = -2147483649; // note: enums should be valid 32-bit integers +} +*/ +", proto, ignoreLineEndingDifferences: true); + } + + public enum InRangeEnum : long + { + A = 1, + B = -4, + C = int.MaxValue, + E = -int.MaxValue, + } + + public enum OutOfRangeEnum : long + { + A = 1, + B = -4, + C = int.MaxValue, + D = ((long)int.MaxValue) + 1, + E = -int.MaxValue, + F = ((long)int.MinValue) - 1, + } + + [ProtoContract] + public class HazOutOfRangeAndNegatives + { + [ProtoMember(1)] + public OutOfRangeEnum OutOfRange { get; set; } + + [ProtoMember(2)] + public InRangeEnum InRange { get; set; } + } + + + [Fact] + public void TestEnumOrderInProtoMatchesDefinitionOrder() + { + var letters = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; + + MemberInfo[] foundList = typeof(FooEnum).GetMembers(BindingFlags.Public | BindingFlags.Static); + + Assert.Equal(letters.Length, foundList.Length); + + for (var i = 0; i < foundList.Length; i++) + { + var field = (FieldInfo)foundList[i]; + Assert.Equal(letters[i], field.Name); + } + + var proto = Serializer.GetProto(); + + Assert.Equal(ExpectedOutput, proto, ignoreLineEndingDifferences: true); + } + + private static string ExpectedOutput = @"syntax = ""proto3""; +package ProtoBuf.Test.Issues; + +enum FooEnum { + A = 0; + B = 1; + C = 2; + D = 3; + E = 4; + F = 5; + G = 6; + H = 7; + I = 8; + J = 9; + K = 10; + L = 11; + M = 12; + N = 13; + O = 14; + P = 15; + Q = 16; + R = 17; + S = 18; + T = 19; + U = 20; + V = 21; + W = 22; + X = 23; + Y = 24; + Z = 25; +} +"; + + [ProtoContract] + public enum FooEnum + { + [ProtoEnum] + A, + [ProtoEnum] + B, + [ProtoEnum] + C, + [ProtoEnum] + D, + [ProtoEnum] + E, + [ProtoEnum] + F, + [ProtoEnum] + G, + [ProtoEnum] + H, + [ProtoEnum] + I, + [ProtoEnum] + J, + [ProtoEnum] + K, + [ProtoEnum] + L, + [ProtoEnum] + M, + [ProtoEnum] + N, + [ProtoEnum] + O, + [ProtoEnum] + P, + [ProtoEnum] + Q, + [ProtoEnum] + R, + [ProtoEnum] + S, + [ProtoEnum] + T, + [ProtoEnum] + U, + [ProtoEnum] + V, + [ProtoEnum] + W, + [ProtoEnum] + X, + [ProtoEnum] + Y, + [ProtoEnum] + Z + } + } +} diff --git a/src/protobuf-net.Test/Issues/Issue323.cs b/src/protobuf-net.Test/Issues/Issue323.cs new file mode 100644 index 000000000..b96e5bed8 --- /dev/null +++ b/src/protobuf-net.Test/Issues/Issue323.cs @@ -0,0 +1,57 @@ +using Xunit; +using ProtoBuf.Meta; + +namespace ProtoBuf.Test.Issues +{ + public class Issue323 + { + const string ExpectedProtoWithPrefixedEnumMembers = @"syntax = ""proto3""; +package ProtoBuf.Test.Issues; + +enum TokenType { + TokenType_Temporary = 0; + TokenType_Persistent = 1; +} +"; + + const string ExpectedProtoWithoutPrefixedEnumMembers = @"syntax = ""proto3""; +package ProtoBuf.Test.Issues; + +enum TokenType { + Temporary = 0; + Persistent = 1; +} +"; + + [Fact] + public void GeneratesProtoWithPrefixedEnumMembers() + { + string proto = Serializer.GetProto(new SchemaGenerationOptions { Types = { typeof(TokenType) }, + Syntax = ProtoSyntax.Proto3, Flags = SchemaGenerationFlags.IncludeEnumNamePrefix }); + + Assert.Equal(ExpectedProtoWithPrefixedEnumMembers, proto, ignoreLineEndingDifferences: true); + } + + [Fact] + public void GeneratesProtoWithoutPrefixedEnumMembers() + { + string proto = Serializer.GetProto(new SchemaGenerationOptions + { + Types = { typeof(TokenType) }, + Syntax = ProtoSyntax.Proto3, + Flags = SchemaGenerationFlags.None, + }); + + Assert.Equal(ExpectedProtoWithoutPrefixedEnumMembers, proto, ignoreLineEndingDifferences: true); + } + + [ProtoContract] + public enum TokenType + { + [ProtoEnum] + Temporary, + [ProtoEnum] + Persistent, + } + } +} diff --git a/src/protobuf-net.Test/ManualSerializer.cs b/src/protobuf-net.Test/ManualSerializer.cs index ee6f4c77d..35d6e9193 100644 --- a/src/protobuf-net.Test/ManualSerializer.cs +++ b/src/protobuf-net.Test/ManualSerializer.cs @@ -424,7 +424,7 @@ void ISerializer.Write(ref ProtoWriter.State state, D value) D ISerializer.Read(ref ProtoReader.State state, D value) { - if (value == null) value = state.CreateInstance(this); + value ??= state.CreateInstance(this); int field; while ((field = state.ReadFieldHeader()) != 0) { diff --git a/src/protobuf-net.Test/NanoPBOptions.cs b/src/protobuf-net.Test/NanoPBOptions.cs index 356db3714..6e7d31309 100644 --- a/src/protobuf-net.Test/NanoPBOptions.cs +++ b/src/protobuf-net.Test/NanoPBOptions.cs @@ -8,7 +8,7 @@ // #region Designer generated code -#pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning disable CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 namespace NanoPB { [global::ProtoBuf.ProtoContract()] @@ -396,5 +396,5 @@ public static void SetNanopb(this global::Google.Protobuf.Reflection.FieldOption } } -#pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 +#pragma warning restore CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 #endregion diff --git a/src/protobuf-net.Test/NullCollections.cs b/src/protobuf-net.Test/NullCollections.cs index 6669e1880..a5db8214e 100644 --- a/src/protobuf-net.Test/NullCollections.cs +++ b/src/protobuf-net.Test/NullCollections.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; +using System.Threading; using Xunit; using Xunit.Abstractions; using static ProtoBuf.Test.BufferWriteCountTests; @@ -193,6 +194,8 @@ public void SchemaGenerationSucceeds(Type type, string expected) [InlineData(10, "23-0A-04-0A-02-08-00-0A-04-0A-02-08-01-0A-00-0A-04-0A-02-08-03-0A-04-0A-02-08-04-0A-00-0A-04-0A-02-08-06-0A-04-0A-02-08-07-0A-00-0A-04-0A-02-08-09-24")] public void TestManualWrappedGroupEquivalent_WrappedValues(int count, string? hex = null) => Test(count, true, hex, true); + static int _next; + private static int Next() => Interlocked.Increment(ref _next); private void Test(int count, bool preserveEmpty, string? expectedHex, bool usesWrappedValues = false, [CallerMemberName] string name = "") where T : class, ITestScenario, new() { @@ -202,7 +205,7 @@ public void SchemaGenerationSucceeds(Type type, string expected) Test(model, count, preserveEmpty, expectedHex, true, usesWrappedValues); model.CompileInPlace(); Test(model, count, preserveEmpty, expectedHex, false, usesWrappedValues); - Test(count == 0 ? PEVerify.CompileAndVerify(model, name) : model.Compile(), count, preserveEmpty, expectedHex, false, usesWrappedValues); + Test(count == 0 ? PEVerify.CompileAndVerify(model, name + Next()) : model.Compile(), count, preserveEmpty, expectedHex, false, usesWrappedValues); } private void Test(TypeModel model, int count, bool preserveEmpty, string? expectedHex, bool logHex, bool usesWrappedValues) where T : class, ITestScenario, new() diff --git a/src/protobuf-net.Test/NullWrappedValueTests.cs b/src/protobuf-net.Test/NullWrappedValueTests.cs index 9545c2da1..3a57ae06f 100644 --- a/src/protobuf-net.Test/NullWrappedValueTests.cs +++ b/src/protobuf-net.Test/NullWrappedValueTests.cs @@ -34,7 +34,7 @@ public void CanApplySimpleConfiguration(bool configured) // configure all Nullable properties as wrapped foreach (var field in e.MetaType.GetFields()) { - if (Nullable.GetUnderlyingType(field.MemberType) is object) + if (Nullable.GetUnderlyingType(field.MemberType) is not null) { field.NullWrappedValue = true; } diff --git a/src/protobuf-net.Test/Nullables/WrappersProto/WrappersProto.ProtoToCode.cs b/src/protobuf-net.Test/Nullables/WrappersProto/WrappersProto.ProtoToCode.cs index 3582d4ea1..83ad08638 100644 --- a/src/protobuf-net.Test/Nullables/WrappersProto/WrappersProto.ProtoToCode.cs +++ b/src/protobuf-net.Test/Nullables/WrappersProto/WrappersProto.ProtoToCode.cs @@ -76,7 +76,7 @@ string GetSimpleCSharpGeneratedCodeContent( // #region Designer generated code - #pragma warning disable CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 + #pragma warning disable CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 [global::ProtoBuf.ProtoContract()] public partial class WrappedTest : global::ProtoBuf.IExtensible {{ @@ -90,7 +90,7 @@ public partial class WrappedTest : global::ProtoBuf.IExtensible }} - #pragma warning restore CS0612, CS0618, CS1591, CS3021, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 + #pragma warning restore CS0612, CS0618, CS1591, CS3021, CS8981, IDE0079, IDE1006, RCS1036, RCS1057, RCS1085, RCS1192 #endregion "; } diff --git a/src/protobuf-net.Test/Serializers/Collections.cs b/src/protobuf-net.Test/Serializers/Collections.cs index 42dd784b0..710022442 100644 --- a/src/protobuf-net.Test/Serializers/Collections.cs +++ b/src/protobuf-net.Test/Serializers/Collections.cs @@ -24,7 +24,7 @@ public class Collections [InlineData(typeof(IEnumerable), typeof(EnumerableSerializer, IEnumerable, int>))] [InlineData(typeof(IList), typeof(EnumerableSerializer, IList, int>))] [InlineData(typeof(Dictionary), typeof(DictionarySerializer))] - [InlineData(typeof(IDictionary), typeof(DictionarySerializer,int, string>))] + [InlineData(typeof(IDictionary), typeof(DictionarySerializer, int, string>))] [InlineData(typeof(IReadOnlyDictionary), typeof(DictionaryOfIReadOnlyDictionarySerializer))] [InlineData(typeof(ImmutableArray), typeof(ImmutableArraySerializer))] [InlineData(typeof(ImmutableDictionary), typeof(ImmutableDictionarySerializer))] @@ -32,8 +32,8 @@ public class Collections [InlineData(typeof(IImmutableDictionary), typeof(ImmutableIDictionarySerializer))] [InlineData(typeof(Queue), typeof(QueueSerializer, int>))] [InlineData(typeof(Stack), typeof(StackSerializer, int>))] - [InlineData(typeof(CustomGenericCollection), typeof(EnumerableSerializer, CustomGenericCollection,int>))] - [InlineData(typeof(CustomNonGenericCollection), typeof(EnumerableSerializer))] + [InlineData(typeof(CustomGenericCollection), typeof(EnumerableSerializer, CustomGenericCollection, int>))] + [InlineData(typeof(CustomNonGenericCollection), typeof(EnumerableSerializer))] [InlineData(typeof(IReadOnlyCollection), typeof(EnumerableSerializer, IReadOnlyCollection, string>))] [InlineData(typeof(CustomNonGenericReadOnlyCollection), typeof(EnumerableSerializer))] [InlineData(typeof(CustomGenericReadOnlyCollection), typeof(EnumerableSerializer, CustomGenericReadOnlyCollection, string>))] @@ -58,6 +58,11 @@ public class Collections [InlineData(typeof(Dictionary), typeof(DictionarySerializer))] [InlineData(typeof(Dictionary), typeof(DictionarySerializer))] + [InlineData(typeof(HashSet), typeof(SetSerializer, int>))] + [InlineData(typeof(ISet), typeof(SetSerializer, int>))] +#if NET6_0_OR_GREATER + [InlineData(typeof(IReadOnlySet), typeof(ReadOnlySetSerializer))] +#endif public void TestWhatProviderWeGet(Type type, Type expected) { var provider = RepeatedSerializers.TryGetRepeatedProvider(type); @@ -155,7 +160,8 @@ public class CustomGenericReadOnlyCollection : IReadOnlyCollection public class CustomGenericCollection : IList { #region nope - T IList.this[int index] { + T IList.this[int index] + { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } diff --git a/src/protobuf-net.Test/Serializers/KeyValuePairTests.cs b/src/protobuf-net.Test/Serializers/KeyValuePairTests.cs index bbd1c1387..41cfe7b1a 100644 --- a/src/protobuf-net.Test/Serializers/KeyValuePairTests.cs +++ b/src/protobuf-net.Test/Serializers/KeyValuePairTests.cs @@ -175,7 +175,7 @@ public void TypeWithIDictionaryTest() var orig = new TypeWithIDictionary { Data = new Dictionary { { "abc", 123.45M } } }; var model = RuntimeTypeModel.Create(); var clone = (TypeWithIDictionary)model.DeepClone(orig); - Assert.Equal(1, clone.Data.Count); + Assert.Single(clone.Data); Assert.Equal(123.45M, clone.Data["abc"]); //, "Runtime"); model.Compile("TypeWithIDictionary", "TypeWithIDictionary.dll"); @@ -183,11 +183,11 @@ public void TypeWithIDictionaryTest() model.CompileInPlace(); clone = (TypeWithIDictionary)model.DeepClone(orig); - Assert.Equal(1, clone.Data.Count); + Assert.Single(clone.Data); Assert.Equal(123.45M, clone.Data["abc"]); //, "Runtime"); clone = (TypeWithIDictionary)model.Compile().DeepClone(orig); - Assert.Equal(1, clone.Data.Count); + Assert.Single(clone.Data); Assert.Equal(123.45M, clone.Data["abc"]); //, "Runtime"); } diff --git a/src/protobuf-net.Test/WellKnownTypes.cs b/src/protobuf-net.Test/WellKnownTypes.cs index 89d13aef6..4d867cda4 100644 --- a/src/protobuf-net.Test/WellKnownTypes.cs +++ b/src/protobuf-net.Test/WellKnownTypes.cs @@ -183,8 +183,7 @@ private void AssertKnownValue(Timestamp value, DateTime expected) private void AssertKnownValue(TimeSpan expected, Duration valueToSerialize, Duration valueToDeserialize = null) { - if (valueToDeserialize == null) - valueToDeserialize = valueToSerialize; // assume they are te same + valueToDeserialize ??= valueToSerialize; // assume they are te same Log($"valueToSerialize: {valueToSerialize}, {valueToSerialize.Seconds} / {valueToSerialize.Nanos}"); Log($"expected: {expected}"); diff --git a/src/protobuf-net.Test/protobuf-net.Test.csproj b/src/protobuf-net.Test/protobuf-net.Test.csproj index 64ff6aad6..a6c69d9c8 100644 --- a/src/protobuf-net.Test/protobuf-net.Test.csproj +++ b/src/protobuf-net.Test/protobuf-net.Test.csproj @@ -31,6 +31,7 @@ + diff --git a/src/protobuf-net/Compiler/CompilerContext.cs b/src/protobuf-net/Compiler/CompilerContext.cs index 6f7fa601f..cabbef04d 100644 --- a/src/protobuf-net/Compiler/CompilerContext.cs +++ b/src/protobuf-net/Compiler/CompilerContext.cs @@ -580,7 +580,7 @@ internal void EmitStateBasedRead(Type ownerType, string methodName, Type expecte internal void EmitStateBasedWrite(string methodName, Local fromValue, Type type = null, Type argType = null) { if (string.IsNullOrEmpty(methodName)) throw new ArgumentNullException(nameof(methodName)); - if (type is null) type = typeof(ProtoWriter.State); + type ??= typeof(ProtoWriter.State); Type foundType; MethodInfo foundMethod; @@ -751,8 +751,7 @@ public void EmitCtor(Type type, params Type[] parameterTypes) } else { - ConstructorInfo ctor = Helpers.GetConstructor(type, parameterTypes, true); - if (ctor is null) throw new InvalidOperationException("No suitable constructor found for " + type.FullName); + ConstructorInfo ctor = Helpers.GetConstructor(type, parameterTypes, true) ?? throw new InvalidOperationException("No suitable constructor found for " + type.FullName); EmitCtor(ctor); } } diff --git a/src/protobuf-net/Compiler/CompilerContextScope.cs b/src/protobuf-net/Compiler/CompilerContextScope.cs index fd9ca53f6..313c35284 100644 --- a/src/protobuf-net/Compiler/CompilerContextScope.cs +++ b/src/protobuf-net/Compiler/CompilerContextScope.cs @@ -51,8 +51,7 @@ internal static readonly ModuleBuilder Shared internal static ILGenerator Implement(TypeBuilder type, Type interfaceType, string name, bool @explicit = true) { - var decl = interfaceType.GetMethod(name, BindingFlags.Public | BindingFlags.Instance); - if (decl is null) throw new ArgumentException($"Declaration not found for '{name}'", nameof(name)); + var decl = interfaceType.GetMethod(name, BindingFlags.Public | BindingFlags.Instance) ?? throw new ArgumentException($"Declaration not found for '{name}'", nameof(name)); var args = decl.GetParameters(); string implName = name; // name.StartsWith("get_") ? name.Substring(4) : name; var attribs = (decl.Attributes & ~MethodAttributes.Abstract) | MethodAttributes.Final; diff --git a/src/protobuf-net/Internal/BasicList.cs b/src/protobuf-net/Internal/BasicList.cs index e632b3479..e4841f0c2 100644 --- a/src/protobuf-net/Internal/BasicList.cs +++ b/src/protobuf-net/Internal/BasicList.cs @@ -53,7 +53,7 @@ internal NodeEnumerator(Node node) this.node = node; } void IEnumerator.Reset() { position = -1; } - public object Current { get { return node[position]; } } + public readonly object Current { get { return node[position]; } } public bool MoveNext() { int len = node.Length; diff --git a/src/protobuf-net/Internal/Serializers/TupleSerializer.cs b/src/protobuf-net/Internal/Serializers/TupleSerializer.cs index c12935a29..7f0bc5e17 100644 --- a/src/protobuf-net/Internal/Serializers/TupleSerializer.cs +++ b/src/protobuf-net/Internal/Serializers/TupleSerializer.cs @@ -138,11 +138,8 @@ public void Write(ref ProtoWriter.State state, object value) bool IProtoTypeSerializer.CanCreateInstance() { return false; } private Type GetMemberType(int index) - { - Type result = Helpers.GetMemberType(members[index]); - if (result is null) throw new InvalidOperationException(); - return result; - } + => Helpers.GetMemberType(members[index]) ?? throw new InvalidOperationException(); + public void EmitWrite(Compiler.CompilerContext ctx, Compiler.Local valueFrom) { using Compiler.Local loc = ctx.GetLocalWithValue(ctor.DeclaringType, valueFrom); diff --git a/src/protobuf-net/Internal/Serializers/TypeSerializer.cs b/src/protobuf-net/Internal/Serializers/TypeSerializer.cs index a6e6306c8..6363686dd 100644 --- a/src/protobuf-net/Internal/Serializers/TypeSerializer.cs +++ b/src/protobuf-net/Internal/Serializers/TypeSerializer.cs @@ -120,7 +120,7 @@ public virtual void Write(ref ProtoWriter.State state, T value) public virtual T Read(ref ProtoReader.State state, T value) { - if (value is null) value = (T)CreateInstance(state.Context); + value ??= (T)CreateInstance(state.Context); Callback(ref value, TypeModel.CallbackType.BeforeDeserialize, state.Context); DeserializeBody(ref state, ref value, (ref T o) => o, (ref T o, T v) => o = v); diff --git a/src/protobuf-net/Meta/EnumMember.cs b/src/protobuf-net/Meta/EnumMember.cs index 5d520963b..4cd0b7076 100644 --- a/src/protobuf-net/Meta/EnumMember.cs +++ b/src/protobuf-net/Meta/EnumMember.cs @@ -8,7 +8,7 @@ namespace ProtoBuf.Meta /// Describes a named constant integer, i.e. an enum value /// [StructLayout(LayoutKind.Auto)] - public readonly struct EnumMember : IEquatable + public readonly struct EnumMember : IEquatable, IComparable { /// /// Gets the declared name of this enum member @@ -119,5 +119,26 @@ internal void Validate() if (string.IsNullOrWhiteSpace(Name)) ThrowHelper.ThrowInvalidOperationException("All enum declarations must have valid names"); } + + public int CompareTo(EnumMember other) + { + var thisValue = TryGetInt32(); + var otherValue = other.TryGetInt32(); + + if (!thisValue.HasValue && !otherValue.HasValue) return 0; + if (!thisValue.HasValue) return 1; + if (!otherValue.HasValue) return -1; + + if (thisValue.Value < 0 && otherValue.Value >= 0) + { + return 1; + } + if (otherValue.Value < 0 && thisValue.Value >= 0) + { + return -1; + } + + return thisValue.Value.CompareTo(otherValue.Value); + } } } diff --git a/src/protobuf-net/Meta/MetaType.cs b/src/protobuf-net/Meta/MetaType.cs index 99068a3ea..527bc4478 100644 --- a/src/protobuf-net/Meta/MetaType.cs +++ b/src/protobuf-net/Meta/MetaType.cs @@ -196,6 +196,11 @@ private void SetBaseType(MetaType baseType) /// public bool HasSubtypes => _subTypes is not null && _subTypes.Count != 0; + /// + /// Returns the number of defined subtypes on the current type + /// + public int SubtypesCount => _subTypes?.Count ?? 0; + /// /// Returns the set of callbacks defined for this type /// @@ -564,8 +569,7 @@ private IProtoTypeSerializer BuildSerializer() if (IsAutoTuple) { if (involvedInInheritance) ThrowTupleTypeWithInheritance(Type); - ConstructorInfo ctor = ResolveTupleConstructor(Type, out MemberInfo[] mapping); - if (ctor is null) throw new InvalidOperationException(); + ConstructorInfo ctor = ResolveTupleConstructor(Type, out MemberInfo[] mapping) ?? throw new InvalidOperationException(); return (IProtoTypeSerializer)Activator.CreateInstance(typeof(TupleSerializer<>).MakeGenericType(Type), args: new object[] { model, ctor, mapping, GetFeatures(), CompatibilityLevel }); } @@ -937,7 +941,7 @@ internal static AttributeFamily GetContractFamily(RuntimeTypeModel model, Type t { AttributeFamily family = AttributeFamily.None; - if (attributes is null) attributes = AttributeMap.Create(type, false); + attributes ??= AttributeMap.Create(type, false); for (int i = 0; i < attributes.Length; i++) { @@ -1316,13 +1320,13 @@ private ValueMember ApplyDefaultBehaviour(bool isEnum, ProtoMemberAttribute norm } } - if ((attrib = GetAttribute(attribs, typeof(NullWrappedValueAttribute).FullName)) is object) + if ((attrib = GetAttribute(attribs, typeof(NullWrappedValueAttribute).FullName)) is not null) { vm.NullWrappedValue = true; if (attrib.TryGet(nameof(NullWrappedValueAttribute.AsGroup), out object tmp) && tmp is bool b) vm.NullWrappedValueGroup = b; } - if ((attrib = GetAttribute(attribs, typeof(NullWrappedCollectionAttribute).FullName)) is object) + if ((attrib = GetAttribute(attribs, typeof(NullWrappedCollectionAttribute).FullName)) is not null) { vm.NullWrappedCollection = true; if (attrib.TryGet(nameof(NullWrappedCollectionAttribute.AsGroup), out object tmp) && tmp is bool b) @@ -1720,7 +1724,10 @@ public ValueMember[] GetFields() public EnumMember[] GetEnumValues() { if (!HasEnums) return Array.Empty(); - return Enums.ToArray(); + + var arr = Enums.ToArray(); + Array.Sort(arr); + return arr; } /// @@ -1970,6 +1977,8 @@ internal void WriteSchema(HashSet callstack, StringBuilder builder, int in if (surrogateType is not null) return; // nothing to write bool multipleNamespaceSupport = (flags & SchemaGenerationFlags.MultipleNamespaceSupport) != 0; + bool isEnumNamePrefixSupported = (flags & SchemaGenerationFlags.IncludeEnumNamePrefix) != 0; + var repeated = model.TryGetRepeatedProvider(Type); if (repeated is not null) @@ -2026,7 +2035,7 @@ internal void WriteSchema(HashSet callstack, StringBuilder builder, int in else if (Type.IsEnum) { var enums = GetEnumValues(); - + string enumNamePrefix = isEnumNamePrefixSupported ? $"{GetSchemaTypeName(callstack)}_" : ""; bool allValid = IsValidEnum(enums); if (!allValid) NewLine(builder, indent).Append("/* for context only"); @@ -2062,14 +2071,15 @@ internal void WriteSchema(HashSet callstack, StringBuilder builder, int in var parsed = member.TryGetInt32(); if (parsed.HasValue && parsed.Value == 0) { - NewLine(builder, indent + 1).Append(member.Name).Append(" = 0;"); + NewLine(builder, indent + 1).Append(enumNamePrefix).Append(member.Name).Append(" = 0;"); haveWrittenZero = true; } } if (syntax == ProtoSyntax.Proto3 && !haveWrittenZero) { - NewLine(builder, indent + 1).Append("ZERO = 0; // proto3 requires a zero value as the first item (it can be named anything)"); + NewLine(builder, indent + 1).Append(enumNamePrefix).Append("ZERO").Append(" = 0;") + .Append(" // proto3 requires a zero value as the first item (it can be named anything)"); } // note array is already sorted, so zero would already be first @@ -2079,11 +2089,12 @@ internal void WriteSchema(HashSet callstack, StringBuilder builder, int in if (parsed.HasValue) { if (parsed.Value == 0) continue; - NewLine(builder, indent + 1).Append(member.Name).Append(" = ").Append(parsed.Value).Append(';'); + NewLine(builder, indent + 1).Append(enumNamePrefix).Append(member.Name).Append(" = ").Append(parsed.Value).Append(';'); } else { - NewLine(builder, indent + 1).Append("// ").Append(member.Name).Append(" = ").Append(member.Value).Append(';').Append(" // note: enums should be valid 32-bit integers"); + NewLine(builder, indent + 1).Append("// ").Append(enumNamePrefix).Append(member.Name) + .Append(" = ").Append(member.Value).Append(';').Append(" // note: enums should be valid 32-bit integers"); } } if (HasReservations) AppendReservations(); diff --git a/src/protobuf-net/Meta/RuntimeTypeModel.cs b/src/protobuf-net/Meta/RuntimeTypeModel.cs index e0af2dc4a..cf1eedc75 100644 --- a/src/protobuf-net/Meta/RuntimeTypeModel.cs +++ b/src/protobuf-net/Meta/RuntimeTypeModel.cs @@ -866,7 +866,7 @@ public MetaType Add(Type type, bool applyDefaultBehaviour = true, CompatibilityL // we should assume that type is fully configured, though; no need to re-run: applyDefaultBehaviour = false; } - if (newType is null) newType = Create(type); + newType ??= Create(type); newType.CompatibilityLevel = compatibilityLevel; // usually this will be setting it to "unspecified", which is fine newType.Pending = true; TakeLock(ref opaqueToken); @@ -908,7 +908,7 @@ private static void OnApplyDefaultBehaviour( { if (handler is not null) { - if (args is null) args = new TypeAddedEventArgs(metaType); + args ??= new TypeAddedEventArgs(metaType); handler(metaType.Model, args); } } @@ -2270,8 +2270,7 @@ public RuntimeTypeModel AddSerializer (Type collectionType, Type serializerType) lock (_serviceCache) { - if (_externalProviders == null) - _externalProviders = new Hashtable(); + _externalProviders ??= new Hashtable(); } if (!_externalProviders.ContainsKey(collection)) RepeatedSerializers.Add(collection, (root, current, targs) => RepeatedSerializers.Resolve(serializerType, "Create", targs),true,_externalProviders); diff --git a/src/protobuf-net/Meta/ValueMember.cs b/src/protobuf-net/Meta/ValueMember.cs index 6c22982b5..e9fce7bad 100644 --- a/src/protobuf-net/Meta/ValueMember.cs +++ b/src/protobuf-net/Meta/ValueMember.cs @@ -523,7 +523,7 @@ private IRuntimeProtoSerializerNode BuildSerializer() var valueFeatures = SerializerFeatures.OptionWrappedValue; if (NullWrappedValueGroup) valueFeatures |= SerializerFeatures.OptionWrappedValueGroup; if (MemberType.IsValueType && Nullable.GetUnderlyingType(MemberType) is null) ThrowHelper.ThrowNotSupportedException($"{nameof(NullWrappedValue)} cannot be used with non-nullable values"); - if (_defaultValue is object) ThrowHelper.ThrowNotSupportedException($"{nameof(NullWrappedValue)} cannot be used with default values"); + if (_defaultValue is not null) ThrowHelper.ThrowNotSupportedException($"{nameof(NullWrappedValue)} cannot be used with default values"); if (IsRequired) ThrowHelper.ThrowNotSupportedException($"{nameof(NullWrappedValue)} cannot be used with required values"); if (IsPacked) ThrowHelper.ThrowNotSupportedException($"{nameof(NullWrappedValue)} cannot be used with packed values"); if (DataFormat != DataFormat.Default) ThrowHelper.ThrowNotSupportedException($"{nameof(NullWrappedValue)} can only be used with {nameof(DataFormat)}.{nameof(DataFormat.Default)}"); diff --git a/src/protobuf-net/Serializers/RepeatedSerializers.cs b/src/protobuf-net/Serializers/RepeatedSerializers.cs index 1329db63e..e54a2ff3d 100644 --- a/src/protobuf-net/Serializers/RepeatedSerializers.cs +++ b/src/protobuf-net/Serializers/RepeatedSerializers.cs @@ -96,6 +96,11 @@ static RepeatedSerializers() Add(typeof(IReadOnlyDictionary<,>), (root, current, targs) => Resolve(typeof(MapSerializer), nameof(MapSerializer.CreateIReadOnlyDictionary), targs)); Add(typeof(Queue<>), (root, current, targs) => Resolve(typeof(RepeatedSerializer), nameof(RepeatedSerializer.CreateQueue), new[] { root, targs[0] }), false); Add(typeof(Stack<>), (root, current, targs) => Resolve(typeof(RepeatedSerializer), nameof(RepeatedSerializer.CreateStack), new[] { root, targs[0] }), false); + Add(typeof(HashSet<>), (root, current, targs) => Resolve(typeof(RepeatedSerializer), nameof(RepeatedSerializer.CreateSet), new[] { root, targs[0] })); + Add(typeof(ISet<>), (root, current, targs) => Resolve(typeof(RepeatedSerializer), nameof(RepeatedSerializer.CreateSet), new[] { root, targs[0] })); +#if NET6_0_OR_GREATER + Add(typeof(IReadOnlySet<>), (root, current, targs) => Resolve(typeof(RepeatedSerializer), nameof(RepeatedSerializer.CreateReadOnySet), new[] { targs[0] })); +#endif // fallbacks, these should be at the end Add(typeof(IEnumerable<>), (root, current, targs) => Resolve(typeof(RepeatedSerializer), nameof(RepeatedSerializer.CreateEnumerable), new[] { root, targs[0] }), false); diff --git a/src/protobuf-net/protobuf-net.csproj b/src/protobuf-net/protobuf-net.csproj index 2b27208d7..6d09fadd1 100644 --- a/src/protobuf-net/protobuf-net.csproj +++ b/src/protobuf-net/protobuf-net.csproj @@ -7,7 +7,6 @@ true True Debug;Release - 10 diff --git a/src/protogen/GrpcTools.cs b/src/protogen/GrpcTools.cs index ce644f7dd..74c4f5765 100644 --- a/src/protogen/GrpcTools.cs +++ b/src/protogen/GrpcTools.cs @@ -105,7 +105,7 @@ internal static async Task ExecuteAsync(string modeString, string uri, stri break; case GrpcMode.Get: Console.WriteLine($"gRPC descriptors fetched: {set?.Files?.Count ?? 0}"); - if (set is object) + if (set is not null) { set.Process(); foreach (var error in set.GetErrors()) diff --git a/src/protogen/Program.cs b/src/protogen/Program.cs index 47f8aa037..24125281d 100644 --- a/src/protogen/Program.cs +++ b/src/protogen/Program.cs @@ -49,7 +49,7 @@ private static async Task Main(string[] args) if (lhs.StartsWith("+")) { - if (options == null) options = new Dictionary(StringComparer.OrdinalIgnoreCase); + options ??= new Dictionary(StringComparer.OrdinalIgnoreCase); options[lhs.Substring(1)] = rhs; continue; } @@ -135,7 +135,7 @@ private static async Task Main(string[] args) if (tmp != ver) Console.WriteLine($"protobuf-net.Reflection {tmp}"); return 0; } - else if (grpcMode is object) + else if (grpcMode is not null) { #if GRPC_TOOLS return await GrpcTools.ExecuteAsync(grpcMode, grpcUrl, grpcService, codegen, outPath, options); @@ -231,10 +231,8 @@ private static async Task Main(string[] args) if (codegen == null) { - using (var fds = File.Create(outPath)) - { - Serializer.Serialize(fds, set); - } + using var fds = File.Create(outPath); + Serializer.Serialize(fds, set); return 0; } diff --git a/src/protogen/protogen.csproj b/src/protogen/protogen.csproj index c60fc5c10..4d134a51c 100644 --- a/src/protogen/protogen.csproj +++ b/src/protogen/protogen.csproj @@ -2,7 +2,7 @@ $(ProtoGenVersion) Exe - net462;net6.0 + net462;net6.0;net8.0 Debug;Release;VS protogen protobuf-net command-line "global tool" for .NET code-generation from .proto schema files