diff --git a/Directory.Packages.props b/Directory.Packages.props
index 130de510..b00ba10b 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -30,6 +30,14 @@
+
+
+
+
+
+
+
+
diff --git a/docfx/docs/custom-converters.md b/docfx/docs/custom-converters.md
index e2e0f4cb..a4c5d83b 100644
--- a/docfx/docs/custom-converters.md
+++ b/docfx/docs/custom-converters.md
@@ -41,20 +41,43 @@ Applications that have a legitimate need to exceed the default stack depth limit
The @Nerdbank.MessagePack.SerializationContext.GetConverter*?displayProperty=nameWithType method may be used to obtain a converter to use for members of the type your converter is serializing or deserializing.
-[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValues)]
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValuesNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/CustomConverters.cs#DelegateSubValuesNETFX)]
+
+---
The above assumes that `SomeOtherType` is a type that you declare and can have @PolyType.GenerateShapeAttribute`1 applied to it.
If this is not the case, you may provide your own type shape and reference that.
For convenience, you may want to apply it directly to your custom converter:
-[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatter)]
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatterNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/CustomConverters.cs#WitnessOnFormatterNETFX)]
+
+---
The @PolyType.GenerateShapeAttribute`1 is what enables `FooConverter` to be a "provider" for the shape of `SomeOtherType`.
Arrays of a type require a shape of their own.
So even if you define your type `MyType` with @PolyType.GenerateShapeAttribute`1, serializing `MyType[]` would require a witness type and attribute. For example:
-[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatter)]
+
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatterNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/CustomConverters.cs#ArrayWitnessOnFormatterNETFX)]
### Version compatibility
diff --git a/docfx/docs/getting-started.md b/docfx/docs/getting-started.md
index ac9af6a2..a9bd0723 100644
--- a/docfx/docs/getting-started.md
+++ b/docfx/docs/getting-started.md
@@ -15,7 +15,15 @@ Given a type annotated with [`GenerateShapeAttribute`](xref:PolyType.GenerateSha
You can serialize and deserialize it like this:
-[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtrip)]
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtripNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/GettingStarted.cs#SimpleRecordRoundtripNETFX)]
+
+---
Only the top-level types that you serialize need the attribute.
All types that they reference will automatically have their 'shape' source generated as well so the whole object graph can be serialized.
diff --git a/docfx/docs/security.md b/docfx/docs/security.md
index 9c92fce1..b74a5c43 100644
--- a/docfx/docs/security.md
+++ b/docfx/docs/security.md
@@ -43,7 +43,15 @@ Instead, you can provide your own defense by initializing your collections with
Here is an example of a defense against hash collisions:
-[!code-csharp[](../../samples/Security.cs#SecureEqualityComparers)]
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/Security.cs#SecureEqualityComparersNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/Security.cs#SecureEqualityComparersNETFX)]
+
+---
Note how the collection properties do *not* define a property setter.
This is crucial to the threat mitigation, since it activates the deserializer behavior of not recreating the collection using the default (insecure) equality comparer.
diff --git a/docfx/docs/type-shapes.md b/docfx/docs/type-shapes.md
index 7db0472e..dbd0285e 100644
--- a/docfx/docs/type-shapes.md
+++ b/docfx/docs/type-shapes.md
@@ -16,7 +16,15 @@ Doing so leads to default serialization rules being applied to the type (e.g. on
For this example, suppose you consume a `FamilyTree` type from a library that you don't control and did not annotate their type for serialization.
In your own project, you can define this witness type and use it to serialize an external type.
-[!code-csharp[](../../samples/TypeShapePatterns.cs#Witness)]
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/TypeShapePatterns.cs#WitnessNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/TypeShapePatterns.cs#WitnessNETFX)]
+
+---
Note the only special bit is providing the `Witness` class as a type argument to the `Serialize` method.
The _name_ of the witness class is completely inconsequential.
diff --git a/docfx/docs/unions.md b/docfx/docs/unions.md
index c54b2fe0..56149e39 100644
--- a/docfx/docs/unions.md
+++ b/docfx/docs/unions.md
@@ -11,7 +11,15 @@ For instance, suppose you have this type to serialize:
But there are many kinds of animals.
You can get them to serialize and deserialize correctly like this:
-[!code-csharp[](../../samples/Unions.cs#FarmAnimals)]
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/Unions.cs#FarmAnimalsNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/Unions.cs#FarmAnimalsNETFX)]
+
+---
This changes the schema of the serialized data to include a tag that indicates the type of the object.
@@ -50,7 +58,15 @@ This is because the `Horse` type is statically known as the generic type argumen
Now suppose you have different breeds of horses that each had their own subtype:
-[!code-csharp[](../../samples/Unions.cs#HorseBreeds)]
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/Unions.cs#HorseBreedsNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/Unions.cs#HorseBreedsNETFX)]
+
+---
At this point your `HorsePen` *would* serialize with the union schema around each horse:
```json
@@ -73,4 +89,12 @@ This witness type must be specified as a second type argument to @Nerdbank.Messa
For example:
-[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypes)]
+# [.NET](#tab/net)
+
+[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypesNET)]
+
+# [.NET Standard](#tab/netfx)
+
+[!code-csharp[](../../samples/Unions.cs#ClosedGenericSubTypesNETFX)]
+
+---
diff --git a/samples/.editorconfig b/samples/.editorconfig
index e5c3fac0..4afec869 100644
--- a/samples/.editorconfig
+++ b/samples/.editorconfig
@@ -1,5 +1,7 @@
[*.cs]
+indent_style = space
+
# SA1123: Do not place regions within elements
dotnet_diagnostic.SA1123.severity = none
diff --git a/samples/ApplyingSerializationContext.cs b/samples/ApplyingSerializationContext.cs
index 0fa385a9..67b206d8 100644
--- a/samples/ApplyingSerializationContext.cs
+++ b/samples/ApplyingSerializationContext.cs
@@ -3,20 +3,16 @@
partial class ApplyingSerializationContext
{
- void ApplyingContext()
- {
- #region ApplyingStartingContext
- MessagePackSerializer serializer = new()
- {
- StartingContext = new SerializationContext
- {
- MaxDepth = 128,
- },
- };
- serializer.Serialize(new SomeType());
- #endregion
- }
-
- [GenerateShape]
- internal partial record SomeType;
+ void ApplyingContext()
+ {
+ #region ApplyingStartingContext
+ MessagePackSerializer serializer = new()
+ {
+ StartingContext = new SerializationContext
+ {
+ MaxDepth = 128,
+ },
+ };
+ #endregion
+ }
}
diff --git a/samples/CustomConverters.cs b/samples/CustomConverters.cs
index c13b2682..252437d0 100644
--- a/samples/CustomConverters.cs
+++ b/samples/CustomConverters.cs
@@ -3,263 +3,329 @@
namespace CustomConverter
{
- #region YourOwnConverter
- using Nerdbank.MessagePack;
-
- public record Foo(int MyProperty1, string? MyProperty2);
-
- class FooConverter : MessagePackConverter
- {
- public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
- {
- if (reader.TryReadNil())
- {
- return null;
- }
-
- context.DepthStep();
- int property1 = 0;
- string? property2 = null;
-
- int count = reader.ReadMapHeader();
- for (int i = 0; i < count; i++)
- {
- string? key = reader.ReadString();
- switch (key)
- {
- case "MyProperty":
- property1 = reader.ReadInt32();
- break;
- case "MyProperty2":
- property2 = reader.ReadString();
- break;
- default:
- // Skip the value, as we don't know where to put it.
- reader.Skip(context);
- break;
- }
- }
-
- return new Foo(property1, property2);
- }
-
- public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
- {
- if (value is null)
- {
- writer.WriteNil();
- return;
- }
-
- context.DepthStep();
- writer.WriteMapHeader(2);
-
- writer.Write("MyProperty");
- writer.Write(value.MyProperty1);
-
- writer.Write("MyProperty2");
- writer.Write(value.MyProperty2);
- }
- }
- #endregion
-
- class VersionSafeConverter : MessagePackConverter
- {
- public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
- {
- if (reader.TryReadNil())
- {
- return null;
- }
-
- #region ReadWholeArray
- context.DepthStep();
- int property1 = 0;
- string? property2 = null;
- int count = reader.ReadArrayHeader();
- for (int i = 0; i < count; i++)
- {
- switch (i)
- {
- case 0:
- property1 = reader.ReadInt32();
- break;
- case 1:
- property2 = reader.ReadString();
- break;
- default:
- // Skip the value, as we don't know where to put it.
- reader.Skip(context);
- break;
- }
- }
-
- return new Foo(property1, property2);
- #endregion
- }
-
- public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
- {
- throw new NotImplementedException();
- }
- }
+ #region YourOwnConverter
+ using Nerdbank.MessagePack;
+
+ public record Foo(int MyProperty1, string? MyProperty2);
+
+ class FooConverter : MessagePackConverter
+ {
+ public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ if (reader.TryReadNil())
+ {
+ return null;
+ }
+
+ context.DepthStep();
+ int property1 = 0;
+ string? property2 = null;
+
+ int count = reader.ReadMapHeader();
+ for (int i = 0; i < count; i++)
+ {
+ string? key = reader.ReadString();
+ switch (key)
+ {
+ case "MyProperty":
+ property1 = reader.ReadInt32();
+ break;
+ case "MyProperty2":
+ property2 = reader.ReadString();
+ break;
+ default:
+ // Skip the value, as we don't know where to put it.
+ reader.Skip(context);
+ break;
+ }
+ }
+
+ return new Foo(property1, property2);
+ }
+
+ public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
+ {
+ if (value is null)
+ {
+ writer.WriteNil();
+ return;
+ }
+
+ context.DepthStep();
+ writer.WriteMapHeader(2);
+
+ writer.Write("MyProperty");
+ writer.Write(value.MyProperty1);
+
+ writer.Write("MyProperty2");
+ writer.Write(value.MyProperty2);
+ }
+ }
+ #endregion
+
+ class VersionSafeConverter : MessagePackConverter
+ {
+ public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ if (reader.TryReadNil())
+ {
+ return null;
+ }
+
+ #region ReadWholeArray
+ context.DepthStep();
+ int property1 = 0;
+ string? property2 = null;
+ int count = reader.ReadArrayHeader();
+ for (int i = 0; i < count; i++)
+ {
+ switch (i)
+ {
+ case 0:
+ property1 = reader.ReadInt32();
+ break;
+ case 1:
+ property2 = reader.ReadString();
+ break;
+ default:
+ // Skip the value, as we don't know where to put it.
+ reader.Skip(context);
+ break;
+ }
+ }
+
+ return new Foo(property1, property2);
+ #endregion
+ }
+
+ public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
namespace SubValues
{
- using Nerdbank.MessagePack;
-
- public record Foo(SomeOtherType? MyProperty1, string? MyProperty2);
-
- [GenerateShape]
- public partial record SomeOtherType;
-
- class FooConverter : MessagePackConverter
- {
- public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
- {
- if (reader.TryReadNil())
- {
- return null;
- }
-
- context.DepthStep();
- SomeOtherType? property1 = null;
- string? property2 = null;
-
- int count = reader.ReadMapHeader();
- for (int i = 0; i < count; i++)
- {
- string? key = reader.ReadString();
- switch (key)
- {
- case "MyProperty":
- property1 = context.GetConverter().Read(ref reader, context);
- break;
- case "MyProperty2":
- property2 = reader.ReadString();
- break;
- default:
- // Skip the value, as we don't know where to put it.
- reader.Skip(context);
- break;
- }
- }
-
- return new Foo(property1, property2);
- }
-
- #region DelegateSubValues
- public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
- {
- if (value is null)
- {
- writer.WriteNil();
- return;
- }
-
- context.DepthStep();
- writer.WriteMapHeader(2);
-
- writer.Write("MyProperty");
- SomeOtherType? propertyValue = value.MyProperty1;
- context.GetConverter().Write(ref writer, propertyValue, context);
-
- writer.Write("MyProperty2");
- writer.Write(value.MyProperty2);
- }
- #endregion
- }
+ using Nerdbank.MessagePack;
+
+ public record Foo(SomeOtherType? MyProperty1, string? MyProperty2);
+
+ [GenerateShape]
+ public partial record SomeOtherType;
+
+ class FooConverter : MessagePackConverter
+ {
+ public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ if (reader.TryReadNil())
+ {
+ return null;
+ }
+
+ context.DepthStep();
+ SomeOtherType? property1 = null;
+ string? property2 = null;
+
+ int count = reader.ReadMapHeader();
+ for (int i = 0; i < count; i++)
+ {
+ string? key = reader.ReadString();
+ switch (key)
+ {
+ case "MyProperty":
+#if NET
+ property1 = context.GetConverter().Read(ref reader, context);
+#else
+ property1 = context.GetConverter(context.TypeShapeProvider).Read(ref reader, context);
+#endif
+ break;
+ case "MyProperty2":
+ property2 = reader.ReadString();
+ break;
+ default:
+ // Skip the value, as we don't know where to put it.
+ reader.Skip(context);
+ break;
+ }
+ }
+
+ return new Foo(property1, property2);
+ }
+
+#if NET
+ #region DelegateSubValuesNET
+ public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
+ {
+ if (value is null)
+ {
+ writer.WriteNil();
+ return;
+ }
+
+ context.DepthStep();
+ writer.WriteMapHeader(2);
+
+ writer.Write("MyProperty");
+ SomeOtherType? propertyValue = value.MyProperty1;
+ context.GetConverter().Write(ref writer, propertyValue, context);
+ writer.Write("MyProperty2");
+ writer.Write(value.MyProperty2);
+ }
+
+ #endregion
+#else
+#region DelegateSubValuesNETFX
+ public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
+ {
+ if (value is null)
+ {
+ writer.WriteNil();
+ return;
+ }
+
+ context.DepthStep();
+ writer.WriteMapHeader(2);
+
+ writer.Write("MyProperty");
+ SomeOtherType? propertyValue = value.MyProperty1;
+ context.GetConverter(context.TypeShapeProvider).Write(ref writer, propertyValue, context);
+ writer.Write("MyProperty2");
+ writer.Write(value.MyProperty2);
+ }
+#endregion
+#endif
+ }
}
namespace SubValuesWithWitness
{
- using Nerdbank.MessagePack;
-
- public record Foo(SomeOtherType? MyProperty1, string? MyProperty2);
-
- #region WitnessOnFormatter
- // SomeOtherType is outside your assembly and not attributed.
- public partial record SomeOtherType;
-
- [GenerateShape] // allow FooConverter to provide the shape for SomeOtherType
- partial class FooConverter : MessagePackConverter
- {
- public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
- {
- // ...
- context.GetConverter().Read(ref reader, context);
- // ...
- #endregion
-
- throw new NotImplementedException();
- }
-
- public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
- {
- throw new NotImplementedException();
- }
- }
+ using Nerdbank.MessagePack;
+
+ public record Foo(SomeOtherType? MyProperty1, string? MyProperty2);
+
+#if NET
+ #region WitnessOnFormatterNET
+ // SomeOtherType is outside your assembly and not attributed.
+ public partial record SomeOtherType;
+
+ [GenerateShape] // allow FooConverter to provide the shape for SomeOtherType
+ partial class FooConverter : MessagePackConverter
+ {
+ public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ // ...
+ context.GetConverter().Read(ref reader, context);
+ // ...
+ #endregion
+
+ throw new NotImplementedException();
+ }
+
+ public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+#else
+#region WitnessOnFormatterNETFX
+ // SomeOtherType is outside your assembly and not attributed.
+ public partial record SomeOtherType;
+
+ [GenerateShape] // allow FooConverter to provide the shape for SomeOtherType
+ partial class FooConverter : MessagePackConverter
+ {
+ public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ // ...
+ context.GetConverter(ShapeProvider).Read(ref reader, context);
+ // ...
+#endregion
+
+ throw new NotImplementedException();
+ }
+
+ public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+#endif
}
namespace WitnessForArray
{
- using Nerdbank.MessagePack;
-
- public record Foo(SomeOtherType? MyProperty1, string? MyProperty2);
-
- #region ArrayWitnessOnFormatter
- // SomeOtherType is outside your assembly and not attributed.
- public partial record SomeOtherType;
-
- [GenerateShape]
- partial class FooConverter : MessagePackConverter
- {
- public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
- {
- // ...
- context.GetConverter().Read(ref reader, context);
- // ...
- #endregion
-
- throw new NotImplementedException();
- }
-
- public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
- {
- throw new NotImplementedException();
- }
- }
+ using Nerdbank.MessagePack;
+
+ public record Foo(SomeOtherType? MyProperty1, string? MyProperty2);
+
+#if NET
+ #region ArrayWitnessOnFormatterNET
+ // SomeOtherType is outside your assembly and not attributed.
+ public partial record SomeOtherType;
+
+ [GenerateShape]
+ partial class FooConverter : MessagePackConverter
+ {
+ public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ // ...
+ context.GetConverter().Read(ref reader, context);
+ // ...
+ #endregion
+#else
+#region ArrayWitnessOnFormatterNETFX
+ // SomeOtherType is outside your assembly and not attributed.
+ public partial record SomeOtherType;
+
+ [GenerateShape]
+ partial class FooConverter : MessagePackConverter
+ {
+ public override Foo? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ // ...
+ context.GetConverter(ShapeProvider).Read(ref reader, context);
+ // ...
+#endregion
+#endif
+ throw new NotImplementedException();
+ }
+
+ public override void Write(ref MessagePackWriter writer, in Foo? value, SerializationContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
}
namespace CustomConverterRegistration
{
- #region CustomConverterByAttribute
- [MessagePackConverter(typeof(MyCustomTypeConverter))]
- public class MyCustomType { }
- #endregion
-
- public class MyCustomTypeConverter : MessagePackConverter
- {
- public override MyCustomType? Read(ref MessagePackReader reader, SerializationContext context)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(ref MessagePackWriter writer, in MyCustomType? value, SerializationContext context)
- {
- throw new NotImplementedException();
- }
- }
-
- class CustomConverterByRegister
- {
- void Main()
- {
- #region CustomConverterByRegister
- MessagePackSerializer serializer = new();
- serializer.RegisterConverter(new MyCustomTypeConverter());
- #endregion
- }
- }
+ #region CustomConverterByAttribute
+ [MessagePackConverter(typeof(MyCustomTypeConverter))]
+ public class MyCustomType { }
+ #endregion
+
+ public class MyCustomTypeConverter : MessagePackConverter
+ {
+ public override MyCustomType? Read(ref MessagePackReader reader, SerializationContext context)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(ref MessagePackWriter writer, in MyCustomType? value, SerializationContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ class CustomConverterByRegister
+ {
+ void Main()
+ {
+ #region CustomConverterByRegister
+ MessagePackSerializer serializer = new();
+ serializer.RegisterConverter(new MyCustomTypeConverter());
+ #endregion
+ }
+ }
}
diff --git a/samples/CustomizingSerialization.cs b/samples/CustomizingSerialization.cs
index 02965f28..fdbe7a62 100644
--- a/samples/CustomizingSerialization.cs
+++ b/samples/CustomizingSerialization.cs
@@ -3,101 +3,101 @@
partial class IncludingExcludingMembers
{
- #region IncludingExcludingMembers
- class MyType
- {
- [PropertyShape(Ignore = true)] // exclude this property from serialization
- public string? MyName { get; set; }
+ #region IncludingExcludingMembers
+ class MyType
+ {
+ [PropertyShape(Ignore = true)] // exclude this property from serialization
+ public string? MyName { get; set; }
- [PropertyShape] // include this non-public property in serialization
- internal string? InternalMember { get; set; }
- }
- #endregion
+ [PropertyShape] // include this non-public property in serialization
+ internal string? InternalMember { get; set; }
+ }
+ #endregion
}
partial class ChangingPropertyNames
{
- #region ChangingPropertyNames
- class MyType
- {
- [PropertyShape(Name = "name")] // serialize this property as "name"
- public string? MyName { get; set; }
- }
- #endregion
+ #region ChangingPropertyNames
+ class MyType
+ {
+ [PropertyShape(Name = "name")] // serialize this property as "name"
+ public string? MyName { get; set; }
+ }
+ #endregion
}
partial class ApplyNamePolicy
{
- class MyType
- {
- void Example()
- {
- #region ApplyNamePolicy
- var serializer = new MessagePackSerializer
- {
- PropertyNamingPolicy = MessagePackNamingPolicy.CamelCase,
- };
- #endregion
- }
- }
+ class MyType
+ {
+ void Example()
+ {
+ #region ApplyNamePolicy
+ var serializer = new MessagePackSerializer
+ {
+ PropertyNamingPolicy = MessagePackNamingPolicy.CamelCase,
+ };
+ #endregion
+ }
+ }
}
namespace DeserializingConstructors
{
- #region DeserializingConstructors
- [GenerateShape]
- partial class ImmutablePerson
- {
- public ImmutablePerson(string? name)
- {
- this.Name = name;
- }
+ #region DeserializingConstructors
+ [GenerateShape]
+ partial class ImmutablePerson
+ {
+ public ImmutablePerson(string? name)
+ {
+ this.Name = name;
+ }
- public string? Name { get; }
- }
- #endregion
+ public string? Name { get; }
+ }
+ #endregion
}
namespace DeserializingConstructorsPropertyRenamed
{
- #region DeserializingConstructorsPropertyRenamed
- [GenerateShape]
- partial class ImmutablePerson
- {
- public ImmutablePerson(string? name)
- {
- this.Name = name;
- }
+ #region DeserializingConstructorsPropertyRenamed
+ [GenerateShape]
+ partial class ImmutablePerson
+ {
+ public ImmutablePerson(string? name)
+ {
+ this.Name = name;
+ }
- [PropertyShape(Name = "person_name")]
- public string? Name { get; }
- }
- #endregion
+ [PropertyShape(Name = "person_name")]
+ public string? Name { get; }
+ }
+ #endregion
}
namespace SerializeWithKey
{
- #region SerializeWithKey
- [GenerateShape]
- partial class MyType
- {
- [Key(0)]
- public string? OneProperty { get; set; }
+ #region SerializeWithKey
+ [GenerateShape]
+ partial class MyType
+ {
+ [Key(0)]
+ public string? OneProperty { get; set; }
- [Key(1)]
- public string? AnotherProperty { get; set; }
- }
- #endregion
+ [Key(1)]
+ public string? AnotherProperty { get; set; }
+ }
+ #endregion
- #region SerializeWithKeyAndGaps
- [GenerateShape]
- partial class MyTypeWithGaps
- {
- [Key(0)]
- public string? OneProperty { get; set; }
+ #region SerializeWithKeyAndGaps
+ [GenerateShape]
+ partial class MyTypeWithGaps
+ {
+ [Key(0)]
+ public string? OneProperty { get; set; }
- [Key(5)]
- public string? AnotherProperty { get; set; }
- }
- #endregion
+ [Key(5)]
+ public string? AnotherProperty { get; set; }
+ }
+ #endregion
}
diff --git a/samples/GettingStarted.cs b/samples/GettingStarted.cs
index 425b197a..750e0294 100644
--- a/samples/GettingStarted.cs
+++ b/samples/GettingStarted.cs
@@ -3,25 +3,47 @@
partial class SimpleUsage
{
- #region SimpleRecord
- [GenerateShape]
- public partial record ARecord(string AString, bool ABoolean);
- #endregion
-
- void Roundtrip()
- {
- #region SimpleRecordRoundtrip
- // Construct a value.
- var value = new ARecord("hello", true);
-
- // Create a serializer instance.
- MessagePackSerializer serializer = new();
-
- // Serialize the value to the buffer.
- byte[] msgpack = serializer.Serialize(value);
-
- // Deserialize it back.
- ARecord? deserialized = serializer.Deserialize(msgpack);
- #endregion
- }
+ #region SimpleRecord
+ [GenerateShape]
+ public partial record ARecord(string AString, bool ABoolean);
+ #endregion
+
+#if NET
+ #region SimpleRecordRoundtripNET
+ void Roundtrip()
+ {
+ // Construct a value.
+ var value = new ARecord("hello", true);
+
+ // Create a serializer instance.
+ MessagePackSerializer serializer = new();
+
+ // Serialize the value to the buffer.
+ byte[] msgpack = serializer.Serialize(value);
+
+ // Deserialize it back.
+ ARecord? deserialized = serializer.Deserialize(msgpack);
+ }
+ #endregion
+#else
+ #region SimpleRecordRoundtripNETFX
+ void Roundtrip()
+ {
+ // Construct a value.
+ var value = new ARecord("hello", true);
+
+ // Create a serializer instance.
+ MessagePackSerializer serializer = new();
+
+ // Serialize the value to the buffer.
+ byte[] msgpack = serializer.Serialize(value, Witness.ShapeProvider);
+
+ // Deserialize it back.
+ ARecord? deserialized = serializer.Deserialize(msgpack, Witness.ShapeProvider);
+ }
+
+ [GenerateShape]
+ internal partial class Witness;
+ #endregion
+#endif
}
diff --git a/samples/Samples.csproj b/samples/Samples.csproj
index 9b256b84..e75ef955 100644
--- a/samples/Samples.csproj
+++ b/samples/Samples.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net8.0;net472
false
diff --git a/samples/Security.cs b/samples/Security.cs
index d2eded61..ce817405 100644
--- a/samples/Security.cs
+++ b/samples/Security.cs
@@ -5,39 +5,68 @@
partial class Security
{
- void SetMaxDepth()
- {
- #region SetMaxDepth
- var serializer = new MessagePackSerializer
- {
- StartingContext = new SerializationContext
- {
- MaxDepth = 100,
- },
- };
- #endregion
- }
-
- #region SecureEqualityComparers
- [GenerateShape]
- public partial class HashCollisionResistance
- {
- public HashCollisionResistance()
- {
- this.Dictionary = new(ByValueEqualityComparer.HashResistant);
- this.HashSet = new(ByValueEqualityComparer.HashResistant);
- }
-
- public Dictionary Dictionary { get; }
-
- public HashSet HashSet { get; }
- }
-
- [GenerateShape]
- public partial class CustomType
- {
- // Whatever members you want. Make them public or attribute with [PropertyShape]
- // to include them in the hash and equality checks as part of the dictionary keys.
- }
- #endregion
+ void SetMaxDepth()
+ {
+ #region SetMaxDepth
+ var serializer = new MessagePackSerializer
+ {
+ StartingContext = new SerializationContext
+ {
+ MaxDepth = 100,
+ },
+ };
+ #endregion
+ }
+
+#if NET
+ #region SecureEqualityComparersNET
+ [GenerateShape]
+ public partial class HashCollisionResistance
+ {
+ public HashCollisionResistance()
+ {
+ this.Dictionary = new(ByValueEqualityComparer.GetHashResistant());
+ this.HashSet = new(ByValueEqualityComparer.GetHashResistant());
+ }
+
+ public Dictionary Dictionary { get; }
+
+ public HashSet HashSet { get; }
+ }
+
+ [GenerateShape]
+ public partial class CustomType
+ {
+ // Whatever members you want. Make them public or attribute with [PropertyShape]
+ // to include them in the hash and equality checks as part of the dictionary keys.
+ }
+
+ #endregion
+#else
+ #region SecureEqualityComparersNETFX
+ [GenerateShape]
+ public partial class HashCollisionResistance
+ {
+ public HashCollisionResistance()
+ {
+ this.Dictionary = new(ByValueEqualityComparer.GetHashResistant(Witness.ShapeProvider));
+ this.HashSet = new(ByValueEqualityComparer.GetHashResistant(Witness.ShapeProvider));
+ }
+
+ public Dictionary Dictionary { get; }
+
+ public HashSet HashSet { get; }
+ }
+
+ [GenerateShape]
+ public partial class CustomType
+ {
+ // Whatever members you want. Make them public or attribute with [PropertyShape]
+ // to include them in the hash and equality checks as part of the dictionary keys.
+ }
+
+ [GenerateShape]
+ internal partial class Witness;
+ #endregion
+#endif
}
diff --git a/samples/TypeShapePatterns.cs b/samples/TypeShapePatterns.cs
index 7b2d2704..6634226d 100644
--- a/samples/TypeShapePatterns.cs
+++ b/samples/TypeShapePatterns.cs
@@ -6,51 +6,73 @@
partial class SourceGenerated
{
- #region NaturallyAttributed
- [GenerateShape]
- public partial record Tree(string Name, Fruit[] Fruits);
+ #region NaturallyAttributed
+ [GenerateShape]
+ public partial record Tree(string Name, Fruit[] Fruits);
- public record Fruit(double Weight, string Color);
- #endregion
+ public record Fruit(double Weight, string Color);
+ #endregion
}
partial class WitnessGenerated
{
- #region Witness
- // This class declared in another assembly, unattributed and outside of your control.
- public class FamilyTree
- {
- }
-
- // Within your own assembly, define a 'witness' class with one or more shapes generated for external types.
- [GenerateShape]
- partial class Witness;
-
- void Serialize()
- {
- var familyTree = new FamilyTree();
- var serializer = new MessagePackSerializer();
-
- // Serialize the FamilyTree instance using the shape generated from your witness class.
- byte[] msgpack = serializer.Serialize(familyTree);
- }
- #endregion
+#if NET
+ #region WitnessNET
+ // This class declared in another assembly, unattributed and outside of your control.
+ public class FamilyTree
+ {
+ }
+
+ // Within your own assembly, define a 'witness' class with one or more shapes generated for external types.
+ [GenerateShape]
+ partial class Witness;
+
+ void Serialize()
+ {
+ var familyTree = new FamilyTree();
+ var serializer = new MessagePackSerializer();
+
+ // Serialize the FamilyTree instance using the shape generated from your witness class.
+ byte[] msgpack = serializer.Serialize(familyTree);
+ }
+ #endregion
+#else
+ #region WitnessNETFX
+ // This class declared in another assembly, unattributed and outside of your control.
+ public class FamilyTree
+ {
+ }
+
+ // Within your own assembly, define a 'witness' class with one or more shapes generated for external types.
+ [GenerateShape]
+ partial class Witness;
+
+ void Serialize()
+ {
+ var familyTree = new FamilyTree();
+ var serializer = new MessagePackSerializer();
+
+ // Serialize the FamilyTree instance using the shape generated from your witness class.
+ byte[] msgpack = serializer.Serialize(familyTree, Witness.ShapeProvider);
+ }
+ #endregion
+#endif
}
class ReflectionShapeProvider
{
- #region SerializeUnshapedType
- void SerializeUnshapedType()
- {
- Person person = new("Andrew", "Arnott");
+ #region SerializeUnshapedType
+ void SerializeUnshapedType()
+ {
+ Person person = new("Andrew", "Arnott");
- ITypeShape shape = ReflectionTypeShapeProvider.Default.GetShape();
- MessagePackSerializer serializer = new();
+ ITypeShape shape = ReflectionTypeShapeProvider.Default.GetShape();
+ MessagePackSerializer serializer = new();
- byte[] msgpack = serializer.Serialize(person, shape);
- Person? deserialized = serializer.Deserialize(msgpack, shape);
- }
+ byte[] msgpack = serializer.Serialize(person, shape);
+ Person? deserialized = serializer.Deserialize(msgpack, shape);
+ }
- record Person(string FirstName, string LastName);
- #endregion
+ record Person(string FirstName, string LastName);
+ #endregion
}
diff --git a/samples/Unions.cs b/samples/Unions.cs
index 14e8c903..7796df63 100644
--- a/samples/Unions.cs
+++ b/samples/Unions.cs
@@ -3,71 +3,128 @@
namespace Sample1
{
- #region Farm
- public class Farm
- {
- public List? Animals { get; set; }
- }
- #endregion
-
- #region FarmAnimals
- [KnownSubType(1)]
- [KnownSubType(2)]
- [KnownSubType(3)]
- public class Animal
- {
- public string? Name { get; set; }
- }
-
- [GenerateShape]
- public partial class Cow : Animal { }
- [GenerateShape]
- public partial class Horse : Animal { }
- [GenerateShape]
- public partial class Dog : Animal { }
- #endregion
-
- #region HorsePen
- public class HorsePen
- {
- public List? Horses { get; set; }
- }
- #endregion
-
- #region HorseBreeds
- [KnownSubType(1)]
- [KnownSubType(2)]
- public partial class Horse : Animal { }
-
- [GenerateShape]
- public partial class QuarterHorse : Horse { }
- [GenerateShape]
- public partial class Thoroughbred : Horse { }
- #endregion
+ #region Farm
+ public class Farm
+ {
+ public List? Animals { get; set; }
+ }
+ #endregion
+
+#if NET
+ #region FarmAnimalsNET
+ [KnownSubType(1)]
+ [KnownSubType(2)]
+ [KnownSubType(3)]
+ public class Animal
+ {
+ public string? Name { get; set; }
+ }
+
+ [GenerateShape]
+ public partial class Cow : Animal { }
+ [GenerateShape]
+ public partial class Horse : Animal { }
+ [GenerateShape]
+ public partial class Dog : Animal { }
+ #endregion
+#else
+ #region FarmAnimalsNETFX
+ [KnownSubType(1, typeof(Cow))]
+ [KnownSubType(2, typeof(Horse))]
+ [KnownSubType(3, typeof(Dog))]
+ public class Animal
+ {
+ public string? Name { get; set; }
+ }
+
+ [GenerateShape]
+ public partial class Cow : Animal { }
+ [GenerateShape]
+ public partial class Horse : Animal { }
+ [GenerateShape]
+ public partial class Dog : Animal { }
+ #endregion
+#endif
+
+ #region HorsePen
+ public class HorsePen
+ {
+ public List? Horses { get; set; }
+ }
+ #endregion
+
+#if NET
+ #region HorseBreedsNET
+ [KnownSubType(1)]
+ [KnownSubType(2)]
+ public partial class Horse : Animal { }
+
+ [GenerateShape]
+ public partial class QuarterHorse : Horse { }
+ [GenerateShape]
+ public partial class Thoroughbred : Horse { }
+ #endregion
+#else
+ #region HorseBreedsNETFX
+ [KnownSubType(1, typeof(QuarterHorse))]
+ [KnownSubType(2, typeof(Thoroughbred))]
+ public partial class Horse : Animal { }
+
+ [GenerateShape]
+ public partial class QuarterHorse : Horse { }
+ [GenerateShape]
+ public partial class Thoroughbred : Horse { }
+ #endregion
+#endif
}
namespace GenericSubTypes
{
- #region ClosedGenericSubTypes
- [KnownSubType(1)]
- [KnownSubType, Witness>(2)]
- [KnownSubType, Witness>(3)]
- class Animal
- {
- public string? Name { get; set; }
- }
+#if NET
+ #region ClosedGenericSubTypesNET
+ [KnownSubType(1)]
+ [KnownSubType, Witness>(2)]
+ [KnownSubType, Witness>(3)]
+ class Animal
+ {
+ public string? Name { get; set; }
+ }
+
+ [GenerateShape]
+ partial class Horse : Animal { }
+
+ partial class Cow : Animal { }
+
+ [GenerateShape>]
+ [GenerateShape>]
+ partial class Witness;
+
+ class SolidHoof { }
+
+ class ClovenHoof { }
+ #endregion
+#else
+ #region ClosedGenericSubTypesNETFX
+ [KnownSubType(1, typeof(Horse))]
+ [KnownSubType(2, typeof(Cow))]
+ [KnownSubType(3, typeof(Cow))]
+ class Animal
+ {
+ public string? Name { get; set; }
+ }
- [GenerateShape]
- partial class Horse : Animal { }
+ [GenerateShape]
+ partial class Horse : Animal { }
- partial class Cow : Animal { }
+ partial class Cow : Animal { }
- [GenerateShape>]
- [GenerateShape>]
- partial class Witness;
+ [GenerateShape>]
+ [GenerateShape>]
+ partial class Witness;
- class SolidHoof { }
+ class SolidHoof { }
- class ClovenHoof { }
- #endregion
+ class ClovenHoof { }
+ #endregion
+#endif
}
diff --git a/src/Nerdbank.MessagePack.Analyzers/ConverterAnalyzers.cs b/src/Nerdbank.MessagePack.Analyzers/ConverterAnalyzers.cs
index 1837d8b9..0c73f74d 100644
--- a/src/Nerdbank.MessagePack.Analyzers/ConverterAnalyzers.cs
+++ b/src/Nerdbank.MessagePack.Analyzers/ConverterAnalyzers.cs
@@ -15,6 +15,8 @@ public class ConverterAnalyzers : DiagnosticAnalyzer
public const string NotExactlyOneStructureDiagnosticId = "NBMsgPack031";
public const string OverrideGetJsonSchemaDiagnosticId = "NBMsgPack032";
+ //// NBMsgPack033 | Usage | Error | Async converters should return the MessagePackWriter
+
public static readonly DiagnosticDescriptor CallbackToTopLevelSerializerDescriptor = new(
CallbackToTopLevelSerializerDiagnosticId,
title: Strings.NBMsgPack030_Title,
diff --git a/src/Nerdbank.MessagePack.Analyzers/KnownSubTypeAnalyzers.cs b/src/Nerdbank.MessagePack.Analyzers/KnownSubTypeAnalyzers.cs
index 20f5bda8..b91eeba9 100644
--- a/src/Nerdbank.MessagePack.Analyzers/KnownSubTypeAnalyzers.cs
+++ b/src/Nerdbank.MessagePack.Analyzers/KnownSubTypeAnalyzers.cs
@@ -82,13 +82,16 @@ public override void Initialize(AnalysisContext context)
context =>
{
INamedTypeSymbol appliedSymbol = (INamedTypeSymbol)context.Symbol;
- AttributeData[] attributeDatas = context.Symbol.FindAttributes(referenceSymbols.IKnownSubTypeAttribute).ToArray();
+ AttributeData[] attributeDatas = context.Symbol.FindAttributes(referenceSymbols.KnownSubTypeAttribute).ToArray();
Dictionary? typesByAlias = null;
Dictionary? aliasesByType = null;
foreach (AttributeData att in attributeDatas)
{
- int? alias = att.ConstructorArguments is [{ Value: int a }] ? a : null;
- ITypeSymbol? subType = att.AttributeClass?.TypeArguments[0];
+ int? alias = att.ConstructorArguments is [{ Value: int a }, ..] ? a : null;
+ (ITypeSymbol? subType, Location? subTypeLocation) =
+ att.AttributeClass?.TypeArguments.Length >= 1 ? (att.AttributeClass?.TypeArguments[0], GetTypeArgumentLocation(0)) :
+ att.ConstructorArguments.Length >= 2 ? ((ITypeSymbol?)att.ConstructorArguments[1].Value, GetArgumentLocation(1)) :
+ (null, null);
if (alias is not null)
{
@@ -112,7 +115,7 @@ public override void Initialize(AnalysisContext context)
{
context.ReportDiagnostic(Diagnostic.Create(
NonUniqueTypeDescriptor,
- GetTypeArgumentLocation(0),
+ subTypeLocation,
existingAlias));
}
else
@@ -125,7 +128,7 @@ public override void Initialize(AnalysisContext context)
{
context.ReportDiagnostic(Diagnostic.Create(
NonDerivedTypeDescriptor,
- GetTypeArgumentLocation(0),
+ subTypeLocation,
subType.Name));
}
@@ -133,7 +136,7 @@ public override void Initialize(AnalysisContext context)
{
context.ReportDiagnostic(Diagnostic.Create(
OpenGenericTypeDescriptor,
- GetTypeArgumentLocation(0)));
+ subTypeLocation));
}
Location? GetArgumentLocation(int argumentIndex)
diff --git a/src/Nerdbank.MessagePack.Analyzers/ReferenceSymbols.cs b/src/Nerdbank.MessagePack.Analyzers/ReferenceSymbols.cs
index 32d80e9d..d10cfba1 100644
--- a/src/Nerdbank.MessagePack.Analyzers/ReferenceSymbols.cs
+++ b/src/Nerdbank.MessagePack.Analyzers/ReferenceSymbols.cs
@@ -12,7 +12,7 @@ public record ReferenceSymbols(
INamedTypeSymbol MessagePackReader,
INamedTypeSymbol MessagePackWriter,
INamedTypeSymbol KeyAttribute,
- INamedTypeSymbol IKnownSubTypeAttribute,
+ INamedTypeSymbol KnownSubTypeAttribute,
INamedTypeSymbol GenerateShapeAttribute,
INamedTypeSymbol PropertyShapeAttribute,
INamedTypeSymbol ConstructorShapeAttribute)
@@ -79,8 +79,8 @@ public static bool TryCreate(Compilation compilation, [NotNullWhen(true)] out Re
return false;
}
- INamedTypeSymbol? iknownSubTypeAttribute = libraryAssembly.GetTypeByMetadataName("Nerdbank.MessagePack.IKnownSubTypeAttribute");
- if (iknownSubTypeAttribute is null)
+ INamedTypeSymbol? knownSubTypeAttribute = libraryAssembly.GetTypeByMetadataName("Nerdbank.MessagePack.KnownSubTypeAttribute");
+ if (knownSubTypeAttribute is null)
{
referenceSymbols = null;
return false;
@@ -121,7 +121,7 @@ public static bool TryCreate(Compilation compilation, [NotNullWhen(true)] out Re
messagePackReader,
messagePackWriter,
keyAttribute,
- iknownSubTypeAttribute,
+ knownSubTypeAttribute,
generateShapeAttribute,
propertyShapeAttribute,
constructorShapeAttribute);
diff --git a/src/Nerdbank.MessagePack/BufferWriter.cs b/src/Nerdbank.MessagePack/BufferWriter.cs
index cadf3b82..bfe6922d 100644
--- a/src/Nerdbank.MessagePack/BufferWriter.cs
+++ b/src/Nerdbank.MessagePack/BufferWriter.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// This file was originally derived from https://github.com/MessagePack-CSharp/MessagePack-CSharp/
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft;
@@ -13,13 +14,19 @@ namespace Nerdbank.MessagePack;
///
internal ref struct BufferWriter
{
+ private readonly bool usingBufferMemoryWriter;
+
+#if NET
+ private ref BufferMemoryWriter memoryWriter;
+#else
+ private BufferMemoryWriter memoryWriter;
+#endif
+
///
/// The underlying .
///
private IBufferWriter? output;
- private ref BufferMemoryWriter memoryWriter;
-
///
/// The result of the last call to , less any bytes already "consumed" with .
/// Backing field for the property.
@@ -60,8 +67,13 @@ internal BufferWriter(IBufferWriter output)
/// The async compatible equivalent to this struct, that this struct will temporarily wrap.
internal BufferWriter(ref BufferMemoryWriter memoryWriter)
{
+#if NET
this.memoryWriter = ref memoryWriter;
- this.span = memoryWriter.GetSpan();
+#else
+ this.memoryWriter = memoryWriter;
+#endif
+ this.usingBufferMemoryWriter = true;
+ this.span = this.memoryWriter.GetSpan();
}
///
@@ -93,6 +105,12 @@ internal BufferWriter(SequencePool sequencePool, byte[] array)
///
internal SequencePool.Rental SequenceRental => this.rental;
+ ///
+ /// Gets the underlying this writer.
+ ///
+ [UnscopedRef]
+ internal ref BufferMemoryWriter BufferMemoryWriter => ref this.memoryWriter;
+
///
internal Span GetSpan(int sizeHint = 0)
{
@@ -130,7 +148,7 @@ internal void Commit()
int buffered = this.buffered;
if (buffered > 0)
{
- if (!Unsafe.IsNullRef(ref this.memoryWriter))
+ if (this.usingBufferMemoryWriter)
{
this.memoryWriter.Advance(buffered);
}
@@ -222,7 +240,7 @@ private void EnsureMore(int sizeHint = 0)
this.MigrateToSequence();
}
- if (!Unsafe.IsNullRef(ref this.memoryWriter))
+ if (this.usingBufferMemoryWriter)
{
this.span = this.memoryWriter.GetSpan(sizeHint);
}
diff --git a/src/Nerdbank.MessagePack/ByValueEqualityComparer.cs b/src/Nerdbank.MessagePack/ByValueEqualityComparer.cs
index 8179d5b5..fdbe055a 100644
--- a/src/Nerdbank.MessagePack/ByValueEqualityComparer.cs
+++ b/src/Nerdbank.MessagePack/ByValueEqualityComparer.cs
@@ -7,9 +7,59 @@
namespace Nerdbank.MessagePack;
///
-/// A non-generic class for caching equality comparers.
+/// Provides deep by-value implementations of for arbitrary data types.
///
-internal static class ByValueEqualityComparer
+///
+///
+/// The deep walking of the object graph for deep by-value equality and hashing is based on the same
+/// that is used to generate MessagePack serializers.
+/// The implementation therefore considers all the same properties for equality and hashing that would
+/// be included in a serialized copy.
+///
+///
+/// This implementation is not suitable for all types. Specifically, it is not suitable for types that
+/// have multiple memory representations that are considered equal.
+/// An invariant for behavior must be that if
+/// x.Equals(y) then x.GetHashCode() == y.GetHashCode().
+/// For an auto-generated implementation of these methods for arbitrary types such as this,
+/// no specialization for multiple values that are considered equal is possible.
+///
+///
+/// For example, a value has distinct memory representations for 0.0 and -0.0,
+/// yet these two values are considered equal and must have the same hash code.
+/// In this case and for several other common data types included with .NET, special consideration is built-in
+/// for correct operation.
+/// But this cannot be done automatically for any user-defined types.
+///
+///
+/// When using user-defined types for which this implementation is inappropriate,
+/// a custom implementation of may be used if the type is used directly.
+/// But if the type is referenced in a type reference graph such that is used for by-value comparison,
+/// implementing on that type will allow the type to take control
+/// of just its contribution to the hash code and equality comparison.
+///
+///
+/// Types that define no (public or opted in) properties and do not implement will throw a when attempting to create an equality comparer.
+///
+///
+/// This implementation should only be used for acyclic graphs, since cyclic graphs will cause a
+/// while performing the comparison.
+///
+///
+/// Another consideration is that types used as keys in collections should generally not have a changing hash code
+/// or the collections internal data structures may become corrupted by a key that is stored in the wrong hash bucket.
+/// Keys should generally be immutable to prevent this, or at least immutable in the elements that contribute to the hash code.
+/// In an automated equality comparer such as the one produced by this class, all public properties contribute to the hash code,
+/// even if they are mutable.
+/// Care should therefore be taken to not mutate properties on objects used as keys in collections.
+///
+///
+/// The values are compared by their declared types rather than polymorphism.
+/// If some type has a property of type Foo, and the actual value at runtime derives from Foo, only the properties on Foo will be considered.
+/// If between two object graphs being equality checked, their runtime types do not match, the equality check will return .
+///
+///
+public static class ByValueEqualityComparer
{
///
/// Cache for generated by-value comparers.
@@ -28,4 +78,100 @@ internal static class ByValueEqualityComparer
DelayedValueFactory = new SecureVisitor.DelayedEqualityComparerFactory(),
ValueBuilderFactory = ctx => new SecureVisitor(ctx),
};
+
+#if NET
+ ///
+ /// Gets a deep by-value equality comparer for the type , without hash collision resistance.
+ ///
+ /// The type to be compared.
+ /// The equality comparer.
+ ///
+ /// See the remarks on the class for important notes about correctness of this implementation.
+ ///
+ public static IEqualityComparer GetDefault()
+ where T : IShapeable => (IEqualityComparer)DefaultEqualityComparerCache.GetOrAdd(T.GetShape())!;
+
+ ///
+ /// Gets a deep by-value equality comparer for the type , with hash collision resistance.
+ ///
+ /// The type to be compared.
+ /// The equality comparer.
+ ///
+ /// See the remarks on the class for important notes about correctness of this implementation.
+ ///
+ public static IEqualityComparer GetHashResistant()
+ where T : IShapeable => (IEqualityComparer)HashResistantEqualityComparerCache.GetOrAdd(T.GetShape())!;
+
+ ///
+ /// Gets a deep by-value equality comparer for the type , without hash collision resistance.
+ ///
+ /// The type to be compared.
+ /// The witness type that provides the for .
+ /// An equality comparer.
+ ///
+ /// See the remarks on the class for important notes about correctness of this implementation.
+ ///
+ public static IEqualityComparer GetDefault()
+ where TProvider : IShapeable => (IEqualityComparer)DefaultEqualityComparerCache.GetOrAdd(TProvider.GetShape())!;
+
+ ///
+ /// Gets a deep by-value equality comparer for the type , with hash collision resistance.
+ ///
+ /// The type to be compared.
+ /// The witness type that provides the for .
+ /// An equality comparer.
+ ///
+ /// See the remarks on the class for important notes about correctness of this implementation.
+ ///
+ public static IEqualityComparer GetHashResistant()
+ where TProvider : IShapeable => (IEqualityComparer)HashResistantEqualityComparerCache.GetOrAdd(TProvider.GetShape())!;
+#endif
+
+ ///
+ /// Gets a deep by-value equality comparer for the type , without hash collision resistance.
+ ///
+ /// The type to be compared.
+ ///
+ /// The equality comparer.
+ ///
+ /// See the remarks on the class for important notes about correctness of this implementation.
+ ///
+ public static IEqualityComparer GetDefault(ITypeShapeProvider provider)
+ => (IEqualityComparer)DefaultEqualityComparerCache.GetOrAddOrThrow(typeof(T), provider);
+
+ ///
+ /// Gets a deep by-value equality comparer for the type , with hash collision resistance.
+ ///
+ /// The type to be compared.
+ ///
+ /// The equality comparer.
+ ///
+ /// See the remarks on the class for important notes about correctness of this implementation.
+ ///
+ public static IEqualityComparer GetHashResistant(ITypeShapeProvider provider)
+ => (IEqualityComparer)HashResistantEqualityComparerCache.GetOrAddOrThrow(typeof(T), provider);
+
+ ///
+ /// Gets a deep by-value equality comparer for the type , without hash collision resistance.
+ ///
+ /// The type to be compared.
+ /// The type shape.
+ /// The equality comparer.
+ ///
+ /// See the remarks on the class for important notes about correctness of this implementation.
+ ///
+ public static IEqualityComparer GetDefault(ITypeShape shape)
+ => (IEqualityComparer)DefaultEqualityComparerCache.GetOrAdd(shape)!;
+
+ ///
+ /// Gets a deep by-value equality comparer for the type , with hash collision resistance.
+ ///
+ /// The type to be compared.
+ /// The type shape.
+ /// The equality comparer.
+ ///
+ /// See the remarks on the class for important notes about correctness of this implementation.
+ ///
+ public static IEqualityComparer GetHashResistant(ITypeShape shape)
+ => (IEqualityComparer)HashResistantEqualityComparerCache.GetOrAdd(shape)!;
}
diff --git a/src/Nerdbank.MessagePack/ByValueEqualityComparer`1.cs b/src/Nerdbank.MessagePack/ByValueEqualityComparer`1.cs
deleted file mode 100644
index ae972c81..00000000
--- a/src/Nerdbank.MessagePack/ByValueEqualityComparer`1.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) Andrew Arnott. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace Nerdbank.MessagePack;
-
-///
-public static class ByValueEqualityComparer
- where T : IShapeable
-{
- ///
- public static IEqualityComparer Default => ByValueEqualityComparer.Default;
-
- ///
- public static IEqualityComparer HashResistant => ByValueEqualityComparer.HashResistant;
-}
diff --git a/src/Nerdbank.MessagePack/ByValueEqualityComparer`2.cs b/src/Nerdbank.MessagePack/ByValueEqualityComparer`2.cs
deleted file mode 100644
index 2d90269e..00000000
--- a/src/Nerdbank.MessagePack/ByValueEqualityComparer`2.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) Andrew Arnott. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-namespace Nerdbank.MessagePack;
-
-///
-/// Provides deep by-value implementations of for arbitrary data types.
-///
-/// The data type to be hashed or compared by value.
-/// The provider of the type shape.
-///
-///
-/// The deep walking of the object graph for deep by-value equality and hashing is based on the same
-/// that is used to generate MessagePack serializers.
-/// The implementation therefore considers all the same properties for equality and hashing that would
-/// be included in a serialized copy.
-///
-///
-/// This implementation is not suitable for all types. Specifically, it is not suitable for types that
-/// have multiple memory representations that are considered equal.
-/// An invariant for behavior must be that if
-/// x.Equals(y) then x.GetHashCode() == y.GetHashCode().
-/// For an auto-generated implementation of these methods for arbitrary types such as this,
-/// no specialization for multiple values that are considered equal is possible.
-///
-///
-/// For example, a value has distinct memory representations for 0.0 and -0.0,
-/// yet these two values are considered equal and must have the same hash code.
-/// In this case and for several other common data types included with .NET, special consideration is built-in
-/// for correct operation.
-/// But this cannot be done automatically for any user-defined types.
-///
-///
-/// When using user-defined types for which this implementation is inappropriate,
-/// a custom implementation of may be used if the type is used directly.
-/// But if the type is referenced in a type reference graph such that is used for by-value comparison,
-/// implementing on that type will allow the type to take control
-/// of just its contribution to the hash code and equality comparison.
-///
-///
-/// Types that define no (public or opted in) properties and do not implement will throw a when attempting to create an equality comparer.
-///
-///
-/// This implementation should only be used for acyclic graphs, since cyclic graphs will cause a
-/// while performing the comparison.
-///
-///
-/// Another consideration is that types used as keys in collections should generally not have a changing hash code
-/// or the collections internal data structures may become corrupted by a key that is stored in the wrong hash bucket.
-/// Keys should generally be immutable to prevent this, or at least immutable in the elements that contribute to the hash code.
-/// In an automated equality comparer such as the one produced by this class, all public properties contribute to the hash code,
-/// even if they are mutable.
-/// Care should therefore be taken to not mutate properties on objects used as keys in collections.
-///
-///
-/// The values are compared by their declared types rather than polymorphism.
-/// If some type has a property of type Foo, and the actual value at runtime derives from Foo, only the properties on Foo will be considered.
-/// If between two object graphs being equality checked, their runtime types do not match, the equality check will return .
-///
-///
-public static class ByValueEqualityComparer
- where TProvider : IShapeable
-{
- private static IEqualityComparer? defaultEqualityComparer;
-
- private static IEqualityComparer? hashResistantEqualityComparer;
-
- ///
- /// Gets a deep by-value equality comparer for the type , without hash collision resistance.
- ///
- ///
- /// See the remarks on the class for important notes about correctness of this implementation.
- ///
- public static IEqualityComparer Default => defaultEqualityComparer ??= (IEqualityComparer)ByValueEqualityComparer.DefaultEqualityComparerCache.GetOrAdd(TProvider.GetShape())!;
-
- ///
- /// Gets a deep by-value equality comparer for the type , with hash collision resistance.
- ///
- ///
- /// See the remarks on the class for important notes about correctness of this implementation.
- ///
- public static IEqualityComparer HashResistant => hashResistantEqualityComparer ??= (IEqualityComparer)ByValueEqualityComparer.HashResistantEqualityComparerCache.GetOrAdd(TProvider.GetShape())!;
-}
diff --git a/src/Nerdbank.MessagePack/Converters/ArrayConverter`1.cs b/src/Nerdbank.MessagePack/Converters/ArrayConverter`1.cs
index 003f3217..359ed4d1 100644
--- a/src/Nerdbank.MessagePack/Converters/ArrayConverter`1.cs
+++ b/src/Nerdbank.MessagePack/Converters/ArrayConverter`1.cs
@@ -84,7 +84,7 @@ public override async ValueTask WriteAsync(MessagePackAsyncWriter writer, TEleme
context.CancellationToken.ThrowIfCancellationRequested();
}
- syncWriter.Flush();
+ writer.ReturnWriter(ref syncWriter);
await writer.FlushIfAppropriateAsync(context).ConfigureAwait(false);
}
while (progress < value.Length);
diff --git a/src/Nerdbank.MessagePack/Converters/ArrayWithFlattenedDimensionsConverter`2.cs b/src/Nerdbank.MessagePack/Converters/ArrayWithFlattenedDimensionsConverter`2.cs
index 7c09a9f3..12cd88b7 100644
--- a/src/Nerdbank.MessagePack/Converters/ArrayWithFlattenedDimensionsConverter`2.cs
+++ b/src/Nerdbank.MessagePack/Converters/ArrayWithFlattenedDimensionsConverter`2.cs
@@ -1,6 +1,8 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if NET
+
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -118,6 +120,8 @@ public override void Write(ref MessagePackWriter writer, in TArray? value, Seria
///
/// The array.
/// The span of all elements.
- private static Span AsSpan(Array array) =>
- MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), array.Length);
+ private static Span AsSpan(Array array)
+ => MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), array.Length);
}
+
+#endif
diff --git a/src/Nerdbank.MessagePack/Converters/ArrayWithNestedDimensionsConverter`2.cs b/src/Nerdbank.MessagePack/Converters/ArrayWithNestedDimensionsConverter`2.cs
index d670ad39..7cc1882f 100644
--- a/src/Nerdbank.MessagePack/Converters/ArrayWithNestedDimensionsConverter`2.cs
+++ b/src/Nerdbank.MessagePack/Converters/ArrayWithNestedDimensionsConverter`2.cs
@@ -1,6 +1,8 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if NET
+
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
@@ -78,8 +80,8 @@ public override void Write(ref MessagePackWriter writer, in TArray? value, Seria
///
/// The array.
/// The span of all elements.
- private static Span AsSpan(Array array) =>
- MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), array.Length);
+ private static Span AsSpan(Array array)
+ => MemoryMarshal.CreateSpan(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)), array.Length);
///
/// Writes an array containing one dimension of an array, and its children, recursively.
@@ -155,3 +157,5 @@ private void PeekDimensionsLength(MessagePackReader reader, int[] dimensions)
}
}
}
+
+#endif
diff --git a/src/Nerdbank.MessagePack/Converters/ArraysOfPrimitivesConverters.cs b/src/Nerdbank.MessagePack/Converters/ArraysOfPrimitivesConverters.cs
index 403b5e22..f8d5ad0b 100644
--- a/src/Nerdbank.MessagePack/Converters/ArraysOfPrimitivesConverters.cs
+++ b/src/Nerdbank.MessagePack/Converters/ArraysOfPrimitivesConverters.cs
@@ -61,7 +61,7 @@ public override void Write(ref MessagePackWriter writer, in TEnumerable? value,
else
{
IEnumerable enumerable = getEnumerable(value);
- if (Enumerable.TryGetNonEnumeratedCount(enumerable, out int count))
+ if (PolyfillExtensions.TryGetNonEnumeratedCount(enumerable, out int count))
{
writer.WriteArrayHeader(count);
Span span = writer.GetSpan(count * MsgPackBufferLengthFactor);
@@ -145,9 +145,11 @@ private static bool TryGetSpan(TEnumerable enumerable, out ReadOnlySpan list:
span = CollectionsMarshal.AsSpan(list);
return true;
+#endif
case ReadOnlyMemory rom:
span = rom.Span;
return true;
diff --git a/src/Nerdbank.MessagePack/Converters/EnumAsOrdinalConverter`2.cs b/src/Nerdbank.MessagePack/Converters/EnumAsOrdinalConverter`2.cs
index 718985c7..1b9e15ef 100644
--- a/src/Nerdbank.MessagePack/Converters/EnumAsOrdinalConverter`2.cs
+++ b/src/Nerdbank.MessagePack/Converters/EnumAsOrdinalConverter`2.cs
@@ -29,11 +29,15 @@ public override void Write(ref MessagePackWriter writer, in TEnum value, Seriali
JsonObject schema = new JsonObject { ["type"] = "integer" };
StringBuilder description = new();
+#if NET
Array enumValuesUntyped = typeof(TEnum).GetEnumValuesAsUnderlyingType();
+#else
+ Array enumValuesUntyped = typeof(TEnum).GetEnumValues();
+#endif
JsonNode[] enumValueNodes = new JsonNode[enumValuesUntyped.Length];
for (int i = 0; i < enumValueNodes.Length; i++)
{
- object ordinalValue = enumValuesUntyped.GetValue(i)!;
+ TUnderlyingType ordinalValue = (TUnderlyingType)enumValuesUntyped.GetValue(i)!;
if (description.Length > 0)
{
description.Append(", ");
diff --git a/src/Nerdbank.MessagePack/Converters/EnumAsStringConverter`2.cs b/src/Nerdbank.MessagePack/Converters/EnumAsStringConverter`2.cs
index 192dba63..6ffc5182 100644
--- a/src/Nerdbank.MessagePack/Converters/EnumAsStringConverter`2.cs
+++ b/src/Nerdbank.MessagePack/Converters/EnumAsStringConverter`2.cs
@@ -46,9 +46,17 @@ public EnumAsStringConverter(MessagePackConverter primitiveConv
bool TryPopulateDictionary()
{
bool nonUniqueNameDetected = false;
+#if NET
foreach (TEnum value in Enum.GetValues())
+#else
+ foreach (TEnum value in Enum.GetValues(typeof(TEnum)))
+#endif
{
+#if NET
if (Enum.GetName(value) is string name)
+#else
+ if (Enum.GetName(typeof(TEnum), value) is string name)
+#endif
{
if (!this.valueByName.TryAdd(name, value))
{
@@ -70,10 +78,18 @@ bool TryPopulateDictionary()
if (nonUniqueNameDetected)
{
// Enumerate values and ensure we have all the names indexed so we can deserialize them.
+#if NET
foreach (string name in Enum.GetNames())
{
this.valueByName.TryAdd(name, Enum.Parse(name));
}
+#else
+ foreach (string name in Enum.GetNames(typeof(TEnum)))
+ {
+ Assumes.True(Enum.TryParse(name, out TEnum value));
+ this.valueByName.TryAdd(name, value);
+ }
+#endif
}
return true;
diff --git a/src/Nerdbank.MessagePack/Converters/EnumerableConverter`2.cs b/src/Nerdbank.MessagePack/Converters/EnumerableConverter`2.cs
index 2a1a3b33..3bcb0f70 100644
--- a/src/Nerdbank.MessagePack/Converters/EnumerableConverter`2.cs
+++ b/src/Nerdbank.MessagePack/Converters/EnumerableConverter`2.cs
@@ -43,7 +43,7 @@ public override void Write(ref MessagePackWriter writer, in TEnumerable? value,
context.DepthStep();
IEnumerable enumerable = getEnumerable(value);
- if (Enumerable.TryGetNonEnumeratedCount(enumerable, out int count))
+ if (PolyfillExtensions.TryGetNonEnumeratedCount(enumerable, out int count))
{
writer.WriteArrayHeader(count);
foreach (TElement element in enumerable)
diff --git a/src/Nerdbank.MessagePack/Converters/HardwareAccelerated.cs b/src/Nerdbank.MessagePack/Converters/HardwareAccelerated.cs
index 41530a29..a27eabad 100644
--- a/src/Nerdbank.MessagePack/Converters/HardwareAccelerated.cs
+++ b/src/Nerdbank.MessagePack/Converters/HardwareAccelerated.cs
@@ -1,6 +1,8 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if NET
+
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
@@ -995,3 +997,5 @@ public override void Write(ref MessagePackWriter writer, in TEnumerable? value,
};
}
}
+
+#endif
diff --git a/src/Nerdbank.MessagePack/Converters/ObjectArrayConverter`1.cs b/src/Nerdbank.MessagePack/Converters/ObjectArrayConverter`1.cs
index a9081ea5..bf70e0e9 100644
--- a/src/Nerdbank.MessagePack/Converters/ObjectArrayConverter`1.cs
+++ b/src/Nerdbank.MessagePack/Converters/ObjectArrayConverter`1.cs
@@ -227,7 +227,7 @@ static async ValueTask WriteAsMapAsync(MessagePackAsyncWriter writer, T value, R
allProperties.Span[properties.Span[i]]!.Value.MsgPackWriters!.Value.Serialize(value, ref syncWriter, context);
}
- syncWriter.Flush();
+ writer.ReturnWriter(ref syncWriter);
await writer.FlushIfAppropriateAsync(context).ConfigureAwait(false);
}
@@ -290,7 +290,7 @@ static async ValueTask WriteAsArrayAsync(MessagePackAsyncWriter writer, T value,
}
}
- syncWriter.Flush();
+ writer.ReturnWriter(ref syncWriter);
await writer.FlushIfAppropriateAsync(context).ConfigureAwait(false);
}
diff --git a/src/Nerdbank.MessagePack/Converters/ObjectMapConverter`1.cs b/src/Nerdbank.MessagePack/Converters/ObjectMapConverter`1.cs
index 54cd59d8..1bbe7d2c 100644
--- a/src/Nerdbank.MessagePack/Converters/ObjectMapConverter`1.cs
+++ b/src/Nerdbank.MessagePack/Converters/ObjectMapConverter`1.cs
@@ -105,7 +105,7 @@ public override async ValueTask WriteAsync(MessagePackAsyncWriter writer, T? val
syncWriter.WriteRaw(property.RawPropertyNameString.Span);
if (property.PreferAsyncSerialization)
{
- syncWriter.Flush();
+ writer.ReturnWriter(ref syncWriter);
await property.WriteAsync(value, writer, context).ConfigureAwait(false);
syncWriter = writer.CreateWriter();
}
@@ -116,13 +116,13 @@ public override async ValueTask WriteAsync(MessagePackAsyncWriter writer, T? val
if (writer.IsTimeToFlush(context, syncWriter))
{
- syncWriter.Flush();
+ writer.ReturnWriter(ref syncWriter);
await writer.FlushIfAppropriateAsync(context).ConfigureAwait(false);
syncWriter = writer.CreateWriter();
}
}
- syncWriter.Flush();
+ writer.ReturnWriter(ref syncWriter);
}
finally
{
diff --git a/src/Nerdbank.MessagePack/Converters/PrimitiveConverters.cs b/src/Nerdbank.MessagePack/Converters/PrimitiveConverters.cs
index 63d3fe0c..7b6badc5 100644
--- a/src/Nerdbank.MessagePack/Converters/PrimitiveConverters.cs
+++ b/src/Nerdbank.MessagePack/Converters/PrimitiveConverters.cs
@@ -150,6 +150,8 @@ internal class UriConverter : MessagePackConverter
};
}
+#if NET
+
///
/// Serializes a .
///
@@ -170,6 +172,8 @@ internal class HalfConverter : MessagePackConverter
};
}
+#endif
+
///
/// Serializes a .
///
@@ -307,6 +311,8 @@ public override void Write(ref MessagePackWriter writer, in decimal value, Seria
};
}
+#if NET
+
///
/// Serializes a value.
///
@@ -463,6 +469,8 @@ public override void Write(ref MessagePackWriter writer, in UInt128 value, Seria
};
}
+#endif
+
///
/// Serializes a value.
///
@@ -474,10 +482,15 @@ public override BigInteger Read(ref MessagePackReader reader, SerializationConte
ReadOnlySequence bytes = reader.ReadBytes() ?? throw MessagePackSerializationException.ThrowUnexpectedNilWhileDeserializing();
if (bytes.IsSingleSegment)
{
+#if NET
return new BigInteger(bytes.First.Span);
+#else
+ return new BigInteger(bytes.First.ToArray());
+#endif
}
else
{
+#if NET
byte[] bytesArray = ArrayPool.Shared.Rent((int)bytes.Length);
try
{
@@ -488,17 +501,24 @@ public override BigInteger Read(ref MessagePackReader reader, SerializationConte
{
ArrayPool.Shared.Return(bytesArray);
}
+#else
+ return new BigInteger(bytes.ToArray());
+#endif
}
}
///
public override void Write(ref MessagePackWriter writer, in BigInteger value, SerializationContext context)
{
+#if NET
int byteCount = value.GetByteCount();
writer.WriteBinHeader(byteCount);
Span span = writer.GetSpan(byteCount);
Assumes.True(value.TryWriteBytes(span, out int written));
writer.Advance(written);
+#else
+ writer.Write(value.ToByteArray());
+#endif
}
///
@@ -559,6 +579,8 @@ public override void Write(ref MessagePackWriter writer, in DateTimeOffset value
};
}
+#if NET
+
///
/// Serializes values.
///
@@ -601,6 +623,8 @@ internal class TimeOnlyConverter : MessagePackConverter
};
}
+#endif
+
///
/// Serializes values.
///
@@ -622,6 +646,8 @@ internal class TimeSpanConverter : MessagePackConverter
};
}
+#if NET
+
///
/// Serializes values.
///
@@ -641,6 +667,8 @@ internal class RuneConverter : MessagePackConverter
};
}
+#endif
+
///
/// Serializes values.
///
@@ -757,13 +785,21 @@ public override Guid Read(ref MessagePackReader reader, SerializationContext con
if (bytes.IsSingleSegment)
{
+#if NET
return new Guid(bytes.FirstSpan);
+#else
+ return PolyfillExtensions.CreateGuid(bytes.First.Span);
+#endif
}
else
{
Span guidValue = stackalloc byte[GuidLength];
bytes.CopyTo(guidValue);
+#if NET
return new Guid(guidValue);
+#else
+ return PolyfillExtensions.CreateGuid(guidValue);
+#endif
}
}
diff --git a/src/Nerdbank.MessagePack/IDeepSecureEqualityComparer`1.cs b/src/Nerdbank.MessagePack/IDeepSecureEqualityComparer`1.cs
index b1886549..04705947 100644
--- a/src/Nerdbank.MessagePack/IDeepSecureEqualityComparer`1.cs
+++ b/src/Nerdbank.MessagePack/IDeepSecureEqualityComparer`1.cs
@@ -12,7 +12,7 @@ namespace Nerdbank.MessagePack;
///
/// When a type implements this interface, and
/// is used to determine equality and hash codes for the type by the
-/// equality comparer
+/// equality comparer
/// instead of the deep by-value automatic implementation.
///
///
@@ -24,7 +24,7 @@ public interface IDeepSecureEqualityComparer
/// The other object.
/// if the two objects are deeply equal.
///
- /// An implementation may use to obtain equality comparers for any sub-values that must be tested.
+ /// An implementation may use to obtain equality comparers for any sub-values that must be tested.
///
bool DeepEquals(T? other);
@@ -42,5 +42,10 @@ public interface IDeepSecureEqualityComparer
///
/// The default implementation of this method is to truncate the result of .
///
- int GetHashCode() => unchecked((int)this.GetSecureHashCode());
+ int GetHashCode()
+#if NET
+ => unchecked((int)this.GetSecureHashCode());
+#else
+ ;
+#endif
}
diff --git a/src/Nerdbank.MessagePack/IMessagePackSerializationCallbacks.cs b/src/Nerdbank.MessagePack/IMessagePackSerializationCallbacks.cs
index 08c8aa73..fbb36c40 100644
--- a/src/Nerdbank.MessagePack/IMessagePackSerializationCallbacks.cs
+++ b/src/Nerdbank.MessagePack/IMessagePackSerializationCallbacks.cs
@@ -12,13 +12,21 @@ public interface IMessagePackSerializationCallbacks
/// Performs any additional operations on an object before it is serialized.
///
void OnBeforeSerialize()
+#if NET
{
}
+#else
+ ;
+#endif
///
/// Performs any additional operations on an object after it has been deserialized.
///
void OnAfterDeserialize()
+#if NET
{
}
+#else
+ ;
+#endif
}
diff --git a/src/Nerdbank.MessagePack/KnownSubTypeAttribute.cs b/src/Nerdbank.MessagePack/KnownSubTypeAttribute.cs
index df0af17a..58b63f43 100644
--- a/src/Nerdbank.MessagePack/KnownSubTypeAttribute.cs
+++ b/src/Nerdbank.MessagePack/KnownSubTypeAttribute.cs
@@ -8,21 +8,7 @@
namespace Nerdbank.MessagePack;
-///
-/// A non-generic interface that allows access to the members of the generic attributes that implement it.
-///
-internal interface IKnownSubTypeAttribute
-{
- ///
- /// Gets a value that identifies the subtype in the serialized data. Must be unique among all the attributes applied to the same class.
- ///
- int Alias { get; }
-
- ///
- /// Gets the shape that describes the subtype.
- ///
- ITypeShape Shape { get; }
-}
+#if NET
///
/// Specifies that where the class to which this attribute is applied is the declared type in an object graph
@@ -44,14 +30,13 @@ internal interface IKnownSubTypeAttribute
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)]
[DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")]
-public class KnownSubTypeAttribute(int alias) : Attribute, IKnownSubTypeAttribute
+#pragma warning disable CS0618 // Type or member is obsolete
+public class KnownSubTypeAttribute(int alias) : KnownSubTypeAttribute(alias, typeof(TSubType))
+#pragma warning restore CS0618 // Type or member is obsolete
where TShapeProvider : IShapeable
{
///
- public int Alias => alias;
-
- ///
- ITypeShape IKnownSubTypeAttribute.Shape => TShapeProvider.GetShape();
+ public override ITypeShape? Shape => TShapeProvider.GetShape();
///
/// Gets the value for the .
@@ -67,13 +52,57 @@ public class KnownSubTypeAttribute(int alias) : KnownSubTypeAttribute<
{
}
+#endif
+
///
-/// A non-generic type for getting the name of the attribute, for use in error messages.
+/// Specifies that where the class to which this attribute is applied is the declared type in an object graph
+/// that certain derived types are recorded in the serialized data as well and allowed to be deserialized back
+/// as their derived types.
///
-internal static class KnownSubTypeAttribute
+///
+///
+/// A type with one or more of these attributes applied serializes to a different schema than the same type
+/// without any attributes applied. The serialized data will include a special header that indicates the runtime type.
+/// Consider version compatibility issues when adding the first or removing the last attribute from a type.
+///
+///
+/// Each type referenced by this attribute must have applied to it or a witness class.
+///
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)]
+public class KnownSubTypeAttribute : Attribute
{
///
/// The name of the type.
///
internal const string TypeName = nameof(KnownSubTypeAttribute);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A value that identifies the subtype in the serialized data. Must be unique among all the attributes applied to the same class.
+ /// The derived-type that the represents.
+#if NET
+ [Obsolete("Use the generic version of this attribute instead.")]
+#endif
+ public KnownSubTypeAttribute(int alias, Type subType)
+ {
+ this.Alias = alias;
+ this.SubType = subType;
+ }
+
+ ///
+ /// Gets a value that identifies the subtype in the serialized data. Must be unique among all the attributes applied to the same class.
+ ///
+ public int Alias { get; }
+
+ ///
+ /// Gets the sub-type.
+ ///
+ public Type SubType { get; }
+
+ ///
+ /// Gets the shape that describes the subtype.
+ ///
+ public virtual ITypeShape? Shape => null;
}
diff --git a/src/Nerdbank.MessagePack/MessagePackAsyncReader.cs b/src/Nerdbank.MessagePack/MessagePackAsyncReader.cs
index aa2e15fe..f64adf39 100644
--- a/src/Nerdbank.MessagePack/MessagePackAsyncReader.cs
+++ b/src/Nerdbank.MessagePack/MessagePackAsyncReader.cs
@@ -149,7 +149,7 @@ public async ValueTask ReadMapHeaderAsync()
this.AdvanceTo(readTo);
return (int)count;
case MessagePackPrimitives.DecodeResult.TokenMismatch:
- throw MessagePackReader.ThrowInvalidCode(readResult.Buffer.FirstSpan[0]);
+ throw MessagePackReader.ThrowInvalidCode(readResult.Buffer.First.Span[0]);
case MessagePackPrimitives.DecodeResult.InsufficientBuffer when !readResult.IsCompleted:
// Fetch more data.
this.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
@@ -178,7 +178,7 @@ public async ValueTask ReadArrayHeaderAsync()
this.AdvanceTo(readTo);
return (int)count;
case MessagePackPrimitives.DecodeResult.TokenMismatch:
- throw MessagePackReader.ThrowInvalidCode(readResult.Buffer.FirstSpan[0]);
+ throw MessagePackReader.ThrowInvalidCode(readResult.Buffer.First.Span[0]);
case MessagePackPrimitives.DecodeResult.InsufficientBuffer when !readResult.IsCompleted:
// Fetch more data.
this.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
diff --git a/src/Nerdbank.MessagePack/MessagePackAsyncWriter.cs b/src/Nerdbank.MessagePack/MessagePackAsyncWriter.cs
index 4ce2d3e8..e5ebf695 100644
--- a/src/Nerdbank.MessagePack/MessagePackAsyncWriter.cs
+++ b/src/Nerdbank.MessagePack/MessagePackAsyncWriter.cs
@@ -37,13 +37,35 @@ public class MessagePackAsyncWriter(PipeWriter pipeWriter)
///
/// The writer.
///
- /// The caller must take care to call before discarding the writer.
+ /// The caller must take care to call before discarding the writer.
///
public MessagePackWriter CreateWriter()
{
+#if !NET
+ // ref fields are not supported on .NET Framework, so we have to prepare to copy the struct.
+ this.bufferWriter.Commit();
+#endif
+
return new(new BufferWriter(ref this.bufferWriter));
}
+ ///
+ /// Applies the bytes written with a writer previously obtained from back to this object.
+ ///
+ /// The writer to return. It should not be used after this.
+ public void ReturnWriter(ref MessagePackWriter writer)
+ {
+ writer.Flush();
+
+#if !NET
+ // ref fields are not supported on .NET Framework, so we have to copy the struct since it'll disappear.
+ this.bufferWriter = writer.Writer.BufferMemoryWriter;
+#endif
+
+ // Help prevent misuse of the writer after it's been returned.
+ writer = default;
+ }
+
///
/// Ensures everything previously written has been flushed to the underlying .
///
@@ -61,7 +83,7 @@ public void Write(SyncWriter writer, TState state)
MessagePackWriter syncWriter = this.CreateWriter();
writer(ref syncWriter, state);
- syncWriter.Flush();
+ this.ReturnWriter(ref syncWriter);
}
///
@@ -69,7 +91,7 @@ public void WriteNil()
{
MessagePackWriter writer = this.CreateWriter();
writer.WriteNil();
- writer.Flush();
+ this.ReturnWriter(ref writer);
}
///
@@ -99,7 +121,7 @@ public void WriteRaw(ReadOnlySpan bytes)
{
MessagePackWriter writer = this.CreateWriter();
writer.WriteRaw(bytes);
- writer.Flush();
+ this.ReturnWriter(ref writer);
}
///
@@ -107,7 +129,7 @@ public void WriteRaw(ReadOnlySequence bytes)
{
MessagePackWriter writer = this.CreateWriter();
writer.WriteRaw(bytes);
- writer.Flush();
+ this.ReturnWriter(ref writer);
}
///
diff --git a/src/Nerdbank.MessagePack/MessagePackConverter`1.cs b/src/Nerdbank.MessagePack/MessagePackConverter`1.cs
index f837355b..3ad2f8f9 100644
--- a/src/Nerdbank.MessagePack/MessagePackConverter`1.cs
+++ b/src/Nerdbank.MessagePack/MessagePackConverter`1.cs
@@ -21,7 +21,7 @@ namespace Nerdbank.MessagePack;
///
/// - Read or write exactly one msgpack structure. Use an array or map header for multiple values.
/// - Call before any significant work.
-/// - Delegate serialization of sub-values to a converter obtained using rather than making a top-level call back to .
+/// - Delegate serialization of sub-values to a converter obtained using rather than making a top-level call back to .
///
///
///
@@ -87,7 +87,7 @@ public virtual ValueTask WriteAsync(MessagePackAsyncWriter writer, T? value, Ser
MessagePackWriter syncWriter = writer.CreateWriter();
this.Write(ref syncWriter, value, context);
- syncWriter.Flush();
+ writer.ReturnWriter(ref syncWriter);
// On our way out, pause to flush the pipe if a lot of data has accumulated in the buffer.
return writer.FlushIfAppropriateAsync(context);
diff --git a/src/Nerdbank.MessagePack/MessagePackNamingPolicy.cs b/src/Nerdbank.MessagePack/MessagePackNamingPolicy.cs
index fbe35a64..61b70d52 100644
--- a/src/Nerdbank.MessagePack/MessagePackNamingPolicy.cs
+++ b/src/Nerdbank.MessagePack/MessagePackNamingPolicy.cs
@@ -39,11 +39,15 @@ public override string ConvertName(string name)
return name;
}
+#if NET
return string.Create(name.Length, name, static (span, name) =>
{
span[0] = char.ToLowerInvariant(name[0]);
name.AsSpan(1).CopyTo(span.Slice(1));
});
+#else
+ return char.ToLowerInvariant(name[0]) + name.Substring(1);
+#endif
}
}
@@ -61,11 +65,15 @@ public override string ConvertName(string name)
return name;
}
+#if NET
return string.Create(name.Length, name, static (span, name) =>
{
span[0] = char.ToUpperInvariant(name[0]);
name.AsSpan(1).CopyTo(span.Slice(1));
});
+#else
+ return char.ToUpperInvariant(name[0]) + name.Substring(1);
+#endif
}
}
}
diff --git a/src/Nerdbank.MessagePack/MessagePackPrimitives.Readers.cs b/src/Nerdbank.MessagePack/MessagePackPrimitives.Readers.cs
index 8f4f1f17..e74206c1 100644
--- a/src/Nerdbank.MessagePack/MessagePackPrimitives.Readers.cs
+++ b/src/Nerdbank.MessagePack/MessagePackPrimitives.Readers.cs
@@ -141,7 +141,7 @@ public static DecodeResult TryReadArrayHeader(ReadOnlySpan source, out uin
public static DecodeResult TryReadArrayHeader(ReadOnlySequence source, out uint count, out SequencePosition readTo)
{
// Fast and preferred path is that we have enough data all in one contiguous buffer.
- DecodeResult result = TryReadArrayHeader(source.FirstSpan, out count, out int tokenSize);
+ DecodeResult result = TryReadArrayHeader(source.First.Span, out count, out int tokenSize);
if (result == DecodeResult.Success)
{
readTo = source.GetPosition(tokenSize);
@@ -245,7 +245,7 @@ public static DecodeResult TryReadMapHeader(ReadOnlySpan source, out uint
public static DecodeResult TryReadMapHeader(ReadOnlySequence source, out uint count, out SequencePosition readTo)
{
// Fast and preferred path is that we have enough data all in one contiguous buffer.
- DecodeResult result = TryReadMapHeader(source.FirstSpan, out count, out int tokenSize);
+ DecodeResult result = TryReadMapHeader(source.First.Span, out count, out int tokenSize);
if (result == DecodeResult.Success)
{
readTo = source.GetPosition(tokenSize);
diff --git a/src/Nerdbank.MessagePack/MessagePackPrimitives.Writers.cs b/src/Nerdbank.MessagePack/MessagePackPrimitives.Writers.cs
index f3aa431a..a3c41fad 100644
--- a/src/Nerdbank.MessagePack/MessagePackPrimitives.Writers.cs
+++ b/src/Nerdbank.MessagePack/MessagePackPrimitives.Writers.cs
@@ -667,7 +667,12 @@ public static unsafe bool TryWrite(Span destination, float value, out int
}
destinationRef = MessagePackCode.Float32;
- WriteBigEndian(ref Unsafe.Add(ref destinationRef, 1), Unsafe.BitCast(value));
+#if NET
+ int valueAsInt = Unsafe.BitCast(value);
+#else
+ int valueAsInt = *(int*)&value;
+#endif
+ WriteBigEndian(ref Unsafe.Add(ref destinationRef, 1), valueAsInt);
return true;
}
@@ -694,7 +699,12 @@ public static unsafe bool TryWrite(Span destination, double value, out int
}
destinationRef = MessagePackCode.Float64;
- WriteBigEndian(ref Unsafe.Add(ref destinationRef, 1), Unsafe.BitCast(value));
+#if NET
+ long valueAsLong = Unsafe.BitCast(value);
+#else
+ long valueAsLong = *(long*)&value;
+#endif
+ WriteBigEndian(ref Unsafe.Add(ref destinationRef, 1), valueAsLong);
return true;
}
diff --git a/src/Nerdbank.MessagePack/MessagePackReader.cs b/src/Nerdbank.MessagePack/MessagePackReader.cs
index 34925b3d..fbb03b68 100644
--- a/src/Nerdbank.MessagePack/MessagePackReader.cs
+++ b/src/Nerdbank.MessagePack/MessagePackReader.cs
@@ -1061,17 +1061,19 @@ private string ReadStringSlow(uint byteLength)
int bytesRead = Math.Min(remainingByteLength, this.reader.UnreadSpan.Length);
remainingByteLength -= bytesRead;
bool flush = remainingByteLength == 0;
-#if NETCOREAPP
+#if NET
initializedChars += decoder.GetChars(this.reader.UnreadSpan.Slice(0, bytesRead), charArray.AsSpan(initializedChars), flush);
#else
- unsafe
- {
- fixed (byte* pUnreadSpan = this.reader.UnreadSpan)
- fixed (char* pCharArray = &charArray[initializedChars])
- {
- initializedChars += decoder.GetChars(pUnreadSpan, bytesRead, pCharArray, charArray.Length - initializedChars, flush);
- }
- }
+ unsafe
+ {
+ fixed (byte* pUnreadSpan = this.reader.UnreadSpan)
+ {
+ fixed (char* pCharArray = &charArray[initializedChars])
+ {
+ initializedChars += decoder.GetChars(pUnreadSpan, bytesRead, pCharArray, charArray.Length - initializedChars, flush);
+ }
+ }
+ }
#endif
this.reader.Advance(bytesRead);
}
diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.cs b/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.cs
index caccfe0d..102d80d6 100644
--- a/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.cs
+++ b/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.cs
@@ -1,6 +1,8 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if NET
+
#pragma warning disable RS0026 // optional parameter on a method with overloads
using System.Diagnostics.CodeAnalysis;
@@ -126,3 +128,6 @@ public ValueTask SerializeAsync(Stream stream, in T? value, Cancel
#pragma warning restore RS0027 // optional parameter on a method with overloads
where TProvider : IShapeable => this.SerializeAsync(stream, value, TProvider.GetShape(), cancellationToken);
}
+
+#endif
+
diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.tt b/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.tt
index ec7f1d1e..1b45c9ab 100644
--- a/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.tt
+++ b/src/Nerdbank.MessagePack/MessagePackSerializer.AutomatedFriendlyOverloads.tt
@@ -2,6 +2,8 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if NET
+
#pragma warning disable RS0026 // optional parameter on a method with overloads
using System.Diagnostics.CodeAnalysis;
@@ -113,6 +115,9 @@ public partial record MessagePackSerializer
}
#>
}
+
+#endif
+
<#+
enum SerializeTransport
{
diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.FriendlyOverloads.cs b/src/Nerdbank.MessagePack/MessagePackSerializer.FriendlyOverloads.cs
index 3b578841..3e2ba1a5 100644
--- a/src/Nerdbank.MessagePack/MessagePackSerializer.FriendlyOverloads.cs
+++ b/src/Nerdbank.MessagePack/MessagePackSerializer.FriendlyOverloads.cs
@@ -84,6 +84,71 @@ public async ValueTask SerializeAsync(Stream stream, T? value, ITypeShape
await pipeWriter.CompleteAsync().ConfigureAwait(false);
}
+ ///
+ /// A byte array containing the serialized msgpack.
+ public byte[] Serialize(in T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+ {
+ Requires.NotNull(provider);
+
+ // Although the static array is thread-local, we still want to null it out while using it
+ // to avoid any potential issues with re-entrancy due to a converter that makes a (bad) top-level call to the serializer.
+ (byte[] array, scratchArray) = (scratchArray ?? new byte[65536], null);
+ try
+ {
+ MessagePackWriter writer = new(SequencePool.Shared, array);
+ this.Serialize(ref writer, value, provider, cancellationToken);
+ return writer.FlushAndGetArray();
+ }
+ finally
+ {
+ scratchArray = array;
+ }
+ }
+
+ ///
+ public void Serialize(IBufferWriter writer, in T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+ {
+ MessagePackWriter msgpackWriter = new(writer);
+ this.Serialize(ref msgpackWriter, value, provider, cancellationToken);
+ msgpackWriter.Flush();
+ }
+
+ ///
+ /// The stream to write to.
+#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ public void Serialize(Stream stream, in T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ {
+ Requires.NotNull(stream);
+ this.Serialize(new StreamBufferWriter(stream), value, provider, cancellationToken);
+ }
+
+ ///
+ /// Serializes a value to a .
+ ///
+ ///
+ /// The stream to write to.
+ ///
+ ///
+ ///
+ ///
+#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ public async ValueTask SerializeAsync(Stream stream, T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ {
+ // Fast path for MemoryStream.
+ if (stream is MemoryStream ms)
+ {
+ this.Serialize(stream, value, provider, cancellationToken);
+ return;
+ }
+
+ PipeWriter pipeWriter = PipeWriter.Create(stream, PipeWriterOptions);
+ await this.SerializeAsync(pipeWriter, value, provider, cancellationToken).ConfigureAwait(false);
+ await pipeWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
+ await pipeWriter.CompleteAsync().ConfigureAwait(false);
+ }
+
///
public T? Deserialize(ReadOnlyMemory buffer, ITypeShape shape, CancellationToken cancellationToken = default)
{
@@ -132,7 +197,7 @@ public async ValueTask SerializeAsync(Stream stream, T? value, ITypeShape
}
while (bytesLastRead > 0);
- return this.Deserialize(rental.Value, shape, cancellationToken);
+ return this.Deserialize(rental.Value, shape, cancellationToken);
}
}
@@ -159,4 +224,80 @@ public async ValueTask SerializeAsync(Stream stream, T? value, ITypeShape
await pipeReader.CompleteAsync().ConfigureAwait(false);
return result;
}
+
+ ///
+ public T? Deserialize(ReadOnlyMemory buffer, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+ {
+ MessagePackReader reader = new(buffer);
+ return this.Deserialize(ref reader, provider, cancellationToken);
+ }
+
+ ///
+ /// The msgpack to deserialize from.
+#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ public T? Deserialize(scoped in ReadOnlySequence buffer, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ {
+ MessagePackReader reader = new(buffer);
+ return this.Deserialize(ref reader, provider, cancellationToken);
+ }
+
+ ///
+ /// The stream to deserialize from. If this stream contains more than one top-level msgpack structure, it may be positioned beyond its end after deserialization due to buffering.
+ ///
+ /// The implementation of this method currently is to buffer the entire content of the into memory before deserializing.
+ /// This is for simplicity and perf reasons.
+ /// Callers should only provide streams that are known to be small enough to fit in memory and contain only msgpack content.
+ ///
+#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ public T? Deserialize(Stream stream, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ {
+ Requires.NotNull(stream);
+
+ // Fast path for MemoryStream.
+ if (stream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment buffer))
+ {
+ return this.Deserialize(buffer.AsMemory(), provider, cancellationToken);
+ }
+ else
+ {
+ // We don't have a streaming msgpack reader, so buffer it all into memory instead and read from there.
+ using SequencePool.Rental rental = SequencePool.Shared.Rent();
+ int bytesLastRead;
+ do
+ {
+ Span span = rental.Value.GetSpan(0);
+ bytesLastRead = stream.Read(span);
+ rental.Value.Advance(bytesLastRead);
+ }
+ while (bytesLastRead > 0);
+
+ return this.Deserialize(rental.Value, provider, cancellationToken);
+ }
+ }
+
+ ///
+ /// Deserializes a value from a .
+ ///
+ ///
+ /// The stream to deserialize from. If this stream contains more than one top-level msgpack structure, it may be positioned beyond its end after deserialization due to buffering.
+ ///
+ ///
+ ///
+#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ public async ValueTask DeserializeAsync(Stream stream, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
+ {
+ // Fast path for MemoryStream.
+ if (stream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment buffer))
+ {
+ return this.Deserialize(buffer.AsMemory(), provider, cancellationToken);
+ }
+
+ PipeReader pipeReader = PipeReader.Create(stream, PipeReaderOptions);
+ T? result = await this.DeserializeAsync(pipeReader, provider, cancellationToken).ConfigureAwait(false);
+ await pipeReader.CompleteAsync().ConfigureAwait(false);
+ return result;
+ }
}
diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.GetSchema.cs b/src/Nerdbank.MessagePack/MessagePackSerializer.GetSchema.cs
index a3da9ea7..86d83389 100644
--- a/src/Nerdbank.MessagePack/MessagePackSerializer.GetSchema.cs
+++ b/src/Nerdbank.MessagePack/MessagePackSerializer.GetSchema.cs
@@ -8,6 +8,7 @@ namespace Nerdbank.MessagePack;
public partial record MessagePackSerializer
{
+#if NET
///
///
///
@@ -24,6 +25,19 @@ public JsonObject GetJsonSchema()
///
public JsonObject GetJsonSchema()
where TProvider : IShapeable => this.GetJsonSchema(TProvider.GetShape());
+#endif
+
+ ///
+ ///
+ ///
+ /// The type whose schema should be produced.
+ ///
+ ///
+ public JsonObject GetJsonSchema(ITypeShapeProvider provider)
+ {
+ Requires.NotNull(provider);
+ return this.GetJsonSchema(provider.GetShape(typeof(T)) ?? throw new ArgumentException($"This provider had no type shape for {typeof(T)}.", nameof(provider)));
+ }
///
/// Creates a JSON Schema that describes the msgpack serialization of the given type's shape.
diff --git a/src/Nerdbank.MessagePack/MessagePackSerializer.cs b/src/Nerdbank.MessagePack/MessagePackSerializer.cs
index c84acdbd..e9b1530e 100644
--- a/src/Nerdbank.MessagePack/MessagePackSerializer.cs
+++ b/src/Nerdbank.MessagePack/MessagePackSerializer.cs
@@ -37,11 +37,15 @@ public partial record MessagePackSerializer
private MultiProviderTypeCache? cachedConverters;
private bool preserveReferences;
+#if NET
+
///
/// Gets the format to use when serializing multi-dimensional arrays.
///
public MultiDimensionalArrayFormat MultiDimensionalArrayFormat { get; init; } = MultiDimensionalArrayFormat.Nested;
+#endif
+
///
/// Gets the transformation function to apply to property names before serializing them.
///
@@ -241,24 +245,58 @@ public void RegisterConverter(MessagePackConverter converter)
/// A cancellation token.
public void Serialize(ref MessagePackWriter writer, in T? value, ITypeShape shape, CancellationToken cancellationToken = default)
{
- using DisposableSerializationContext context = this.CreateSerializationContext(cancellationToken);
+ Requires.NotNull(shape);
+ using DisposableSerializationContext context = this.CreateSerializationContext(shape.Provider, cancellationToken);
this.GetOrAddConverter(shape).Write(ref writer, value, context.Value);
}
+ ///
+ /// Serializes a value.
+ ///
+ /// The type to be serialized.
+ /// The msgpack writer to use.
+ /// The value to serialize.
+ ///
+ /// A cancellation token.
+ public void Serialize(ref MessagePackWriter writer, in T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+ {
+ using DisposableSerializationContext context = this.CreateSerializationContext(provider, cancellationToken);
+ this.GetOrAddConverter(provider).Write(ref writer, value, context.Value);
+ }
+
///
/// Deserializes a value.
///
/// The type of value to deserialize.
/// The msgpack reader to deserialize from.
- /// The shape provider of . This may be the same as when the data type is attributed with , or it may be another "witness" partial class that was annotated with where T for the attribute is the same as the used here.
+ /// The shape of .
/// A cancellation token.
/// The deserialized value.
public T? Deserialize(ref MessagePackReader reader, ITypeShape shape, CancellationToken cancellationToken = default)
{
- using DisposableSerializationContext context = this.CreateSerializationContext(cancellationToken);
+ Requires.NotNull(shape);
+ using DisposableSerializationContext context = this.CreateSerializationContext(shape.Provider, cancellationToken);
return this.GetOrAddConverter(shape).Read(ref reader, context.Value);
}
+ ///
+ /// Deserializes a value.
+ ///
+ /// The type of value to deserialize.
+ /// The msgpack reader to deserialize from.
+ ///
+ /// The shape provider of .
+ /// This will typically be obtained by calling the ShapeProvider static property on a witness class
+ /// (a class on which has been applied).
+ ///
+ /// A cancellation token.
+ /// The deserialized value.
+ public T? Deserialize(ref MessagePackReader reader, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+ {
+ using DisposableSerializationContext context = this.CreateSerializationContext(provider, cancellationToken);
+ return this.GetOrAddConverter(provider).Read(ref reader, context.Value);
+ }
+
///
/// Serializes a value using the given .
///
@@ -271,16 +309,39 @@ public void Serialize(ref MessagePackWriter writer, in T? value, ITypeShape(PipeWriter writer, T? value, ITypeShape shape, CancellationToken cancellationToken = default)
{
Requires.NotNull(writer);
+ Requires.NotNull(shape);
cancellationToken.ThrowIfCancellationRequested();
#pragma warning disable NBMsgPackAsync
MessagePackAsyncWriter asyncWriter = new(writer);
- using DisposableSerializationContext context = this.CreateSerializationContext(cancellationToken);
+ using DisposableSerializationContext context = this.CreateSerializationContext(shape.Provider, cancellationToken);
await this.GetOrAddConverter(shape).WriteAsync(asyncWriter, value, context.Value).ConfigureAwait(false);
asyncWriter.Flush();
#pragma warning restore NBMsgPackAsync
}
+ ///
+ /// Serializes a value using the given .
+ ///
+ /// The type to be serialized.
+ /// The writer to use.
+ /// The value to serialize.
+ ///
+ /// A cancellation token.
+ /// A task that tracks the async serialization.
+ public async ValueTask SerializeAsync(PipeWriter writer, T? value, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+ {
+ Requires.NotNull(writer);
+ cancellationToken.ThrowIfCancellationRequested();
+
+#pragma warning disable NBMsgPackAsync
+ MessagePackAsyncWriter asyncWriter = new(writer);
+ using DisposableSerializationContext context = this.CreateSerializationContext(provider, cancellationToken);
+ await this.GetOrAddConverter(provider).WriteAsync(asyncWriter, value, context.Value).ConfigureAwait(false);
+ asyncWriter.Flush();
+#pragma warning restore NBMsgPackAsync
+ }
+
///
/// Deserializes a value from a .
///
@@ -291,13 +352,31 @@ public async ValueTask SerializeAsync(PipeWriter writer, T? value, ITypeShape
/// The deserialized value.
public ValueTask DeserializeAsync(PipeReader reader, ITypeShape shape, CancellationToken cancellationToken = default)
{
+ Requires.NotNull(shape);
cancellationToken.ThrowIfCancellationRequested();
- using DisposableSerializationContext context = this.CreateSerializationContext(cancellationToken);
+ using DisposableSerializationContext context = this.CreateSerializationContext(shape.Provider, cancellationToken);
#pragma warning disable NBMsgPackAsync
return this.GetOrAddConverter(shape).ReadAsync(new MessagePackAsyncReader(reader) { CancellationToken = cancellationToken }, context.Value);
#pragma warning restore NBMsgPackAsync
}
+ ///
+ /// Deserializes a value from a .
+ ///
+ /// The type of value to deserialize.
+ /// The reader to deserialize from.
+ ///
+ /// A cancellation token.
+ /// The deserialized value.
+ public ValueTask DeserializeAsync(PipeReader reader, ITypeShapeProvider provider, CancellationToken cancellationToken = default)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ using DisposableSerializationContext context = this.CreateSerializationContext(provider, cancellationToken);
+#pragma warning disable NBMsgPackAsync
+ return this.GetOrAddConverter(provider).ReadAsync(new MessagePackAsyncReader(reader) { CancellationToken = cancellationToken }, context.Value);
+#pragma warning restore NBMsgPackAsync
+ }
+
///
public static string ConvertToJson(ReadOnlyMemory msgpack) => ConvertToJson(new ReadOnlySequence(msgpack));
@@ -474,6 +553,17 @@ internal MessagePackConverter GetOrAddConverter(ITypeShape shape)
internal IMessagePackConverter GetOrAddConverter(ITypeShape shape)
=> (IMessagePackConverter)this.CachedConverters.GetOrAdd(shape)!;
+ ///
+ /// Gets a converter for the given type shape.
+ /// An existing converter is reused if one is found in the cache.
+ /// If a converter must be created, it is added to the cache for lookup next time.
+ ///
+ /// The type to convert.
+ /// The type shape provider.
+ /// A msgpack converter.
+ internal MessagePackConverter GetOrAddConverter(ITypeShapeProvider provider)
+ => (MessagePackConverter)this.CachedConverters.GetOrAddOrThrow(typeof(T), provider);
+
///
/// Gets a user-defined converter for the specified type if one is available.
///
@@ -517,15 +607,17 @@ internal string GetSerializedPropertyName(string name, ICustomAttributeProvider?
///
/// Creates a new serialization context that is ready to process a serialization job.
///
+ ///
/// A cancellation token for the operation.
/// The serialization context.
///
/// Callers should be sure to always call when done with the context.
///
- protected DisposableSerializationContext CreateSerializationContext(CancellationToken cancellationToken = default)
+ protected DisposableSerializationContext CreateSerializationContext(ITypeShapeProvider provider, CancellationToken cancellationToken = default)
{
+ Requires.NotNull(provider);
this.configurationLocked = true;
- return new(this.StartingContext.Start(this, cancellationToken));
+ return new(this.StartingContext.Start(this, provider, cancellationToken));
}
///
diff --git a/src/Nerdbank.MessagePack/MessagePackWriter.cs b/src/Nerdbank.MessagePack/MessagePackWriter.cs
index 75d68866..14f3b487 100644
--- a/src/Nerdbank.MessagePack/MessagePackWriter.cs
+++ b/src/Nerdbank.MessagePack/MessagePackWriter.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// This file was originally derived from https://github.com/MessagePack-CSharp/MessagePack-CSharp/
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Microsoft;
@@ -64,6 +65,12 @@ internal MessagePackWriter(BufferWriter writer)
///
public bool OldSpec { get; set; }
+ ///
+ /// Gets a reference to the behind this writer.
+ ///
+ [UnscopedRef]
+ internal ref BufferWriter Writer => ref this.writer;
+
///
/// Get the number of bytes required to encode a value in msgpack.
///
diff --git a/src/Nerdbank.MessagePack/Nerdbank.MessagePack.csproj b/src/Nerdbank.MessagePack/Nerdbank.MessagePack.csproj
index 9a6e051b..c633138c 100644
--- a/src/Nerdbank.MessagePack/Nerdbank.MessagePack.csproj
+++ b/src/Nerdbank.MessagePack/Nerdbank.MessagePack.csproj
@@ -1,11 +1,12 @@
- net8.0
+ net8.0;netstandard2.0
true
- true
+ true
true
$(TargetsForTfmSpecificContentInPackage);PackAnalyzers
A fast and more user-friendly MessagePack serialization library for .NET. With ground-up support for trimming and Native AOT.
+ true
@@ -14,6 +15,11 @@
+
+
+
+
+
TextTemplatingFileGenerator
@@ -75,12 +81,15 @@
MessagePackSerializer.AutomatedFriendlyOverloads.tt
+
+
+
-
+
diff --git a/src/Nerdbank.MessagePack/PolyfillExtensions.cs b/src/Nerdbank.MessagePack/PolyfillExtensions.cs
new file mode 100644
index 00000000..855b9544
--- /dev/null
+++ b/src/Nerdbank.MessagePack/PolyfillExtensions.cs
@@ -0,0 +1,305 @@
+// Copyright (c) Andrew Arnott. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#pragma warning disable SA1600 // Elements should be documented
+#pragma warning disable SA1402 // multiple types
+#pragma warning disable SA1403 // multiple namespaces
+
+using System.Buffers.Binary;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft;
+using PolyType.Utilities;
+
+namespace Nerdbank.MessagePack
+{
+ ///
+ /// Utility methods to help make up for cross-targeting support.
+ ///
+ internal static partial class PolyfillExtensions
+ {
+ internal static bool TryGetNonEnumeratedCount(this IEnumerable source, out int count)
+ {
+#if NET
+ return Enumerable.TryGetNonEnumeratedCount(source, out count);
+#else
+ Requires.NotNull(source);
+
+ switch (source)
+ {
+ case ICollection collection:
+ count = collection.Count;
+ return true;
+ case System.Collections.ICollection collection:
+ count = collection.Count;
+ return true;
+ default:
+ count = 0;
+ return false;
+ }
+#endif
+ }
+
+ internal static object GetOrAddOrThrow(this MultiProviderTypeCache cache, Type type, ITypeShapeProvider provider)
+ => cache.GetOrAdd(type, provider) ?? throw ThrowMissingTypeShape(type, provider);
+
+ internal static ITypeShape GetShapeOrThrow(this ITypeShapeProvider provider, Type type)
+ => provider.GetShape(type) ?? throw ThrowMissingTypeShape(type, provider);
+
+ private static Exception ThrowMissingTypeShape(Type type, ITypeShapeProvider provider)
+ => new ArgumentException($"The {provider.GetType().FullName} provider had no type shape for {type.FullName}.", nameof(provider));
+ }
+
+#if !NET
+ ///
+ /// Polyfills specifically for .NET Standard targeting.
+ ///
+ internal static partial class PolyfillExtensions
+ {
+ internal static unsafe int GetChars(this Encoding encoding, ReadOnlySpan source, Span destination)
+ {
+ fixed (byte* pSource = source)
+ {
+ fixed (char* pDestination = destination)
+ {
+ return encoding.GetChars(pSource, source.Length, pDestination, destination.Length);
+ }
+ }
+ }
+
+ internal static unsafe int GetChars(this Encoding encoding, ReadOnlySequence source, Span destination)
+ {
+ if (source.IsSingleSegment)
+ {
+ return GetChars(encoding, source.First.Span, destination);
+ }
+
+ Decoder decoder = encoding.GetDecoder();
+ int charsWritten = 0;
+ bool completed = true;
+ foreach (ReadOnlyMemory sourceSegment in source)
+ {
+ fixed (byte* pSource = sourceSegment.Span)
+ {
+ fixed (char* pDestination = destination)
+ {
+ decoder.Convert(pSource, sourceSegment.Length, pDestination, destination.Length, false, out _, out int charsUsed, out completed);
+ charsWritten += charsUsed;
+ destination = destination[charsUsed..];
+ }
+ }
+ }
+
+ if (!completed)
+ {
+ fixed (char* pDest = destination)
+ {
+ decoder.Convert(null, 0, pDest, destination.Length, flush: true, out _, out int charsUsed, out _);
+ charsWritten += charsUsed;
+ }
+ }
+
+ return charsWritten;
+ }
+
+ internal static unsafe int GetBytes(this Encoding encoding, ReadOnlySpan source, Span destination)
+ {
+ fixed (char* pSource = source)
+ {
+ fixed (byte* pDestination = destination)
+ {
+ return encoding.GetBytes(pSource, source.Length, pDestination, destination.Length);
+ }
+ }
+ }
+
+ internal static unsafe string GetString(this Encoding encoding, ReadOnlySpan bytes)
+ {
+ if (bytes.IsEmpty)
+ {
+ return string.Empty;
+ }
+
+ fixed (byte* pBytes = bytes)
+ {
+ return encoding.GetString(pBytes, bytes.Length);
+ }
+ }
+
+ ///
+ /// Reads from the stream into a memory buffer.
+ ///
+ /// The stream to read from.
+ /// The buffer to read directly into.
+ /// The number of bytes actually read.
+ internal static int Read(this Stream stream, Span buffer)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ byte[] array = ArrayPool.Shared.Rent(buffer.Length);
+ try
+ {
+ int bytesRead = stream.Read(array, 0, buffer.Length);
+ new Span(array, 0, bytesRead).CopyTo(buffer);
+ return bytesRead;
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(array);
+ }
+ }
+
+ ///
+ /// Reads from the stream into a memory buffer.
+ ///
+ /// The stream to read from.
+ /// The buffer to read directly into.
+ /// A cancellation token.
+ /// The number of bytes actually read.
+ ///
+ /// This method shamelessly copied from the .NET Core 2.1 Stream class: https://github.com/dotnet/coreclr/blob/a113b1c803783c9d64f1f0e946ff9a853e3bc140/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L366-L391.
+ ///
+ internal static ValueTask ReadAsync(this Stream stream, Memory buffer, CancellationToken cancellationToken = default)
+ {
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ if (MemoryMarshal.TryGetArray(buffer, out ArraySegment array))
+ {
+ return new ValueTask(stream.ReadAsync(array.Array, array.Offset, array.Count, cancellationToken));
+ }
+ else
+ {
+ byte[] sharedBuffer = ArrayPool.Shared.Rent(buffer.Length);
+ return FinishReadAsync(stream.ReadAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer, buffer);
+
+ async ValueTask FinishReadAsync(Task readTask, byte[] localBuffer, Memory localDestination)
+ {
+ try
+ {
+ int result = await readTask.ConfigureAwait(false);
+ new Span(localBuffer, 0, result).CopyTo(localDestination.Span);
+ return result;
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(localBuffer);
+ }
+ }
+ }
+ }
+
+ internal static bool TryWriteBytes(this Guid value, Span destination)
+ {
+ if (destination.Length < 16)
+ {
+ return false;
+ }
+
+ if (BitConverter.IsLittleEndian)
+ {
+ MemoryMarshal.TryWrite(destination, ref value);
+ }
+ else
+ {
+ // slower path for BigEndian
+ Span guidSpan = stackalloc GuidBits[1];
+ guidSpan[0] = value;
+ GuidBits endianSwitched = new(MemoryMarshal.AsBytes(guidSpan), false);
+ MemoryMarshal.TryWrite(destination, ref endianSwitched);
+ }
+
+ return true;
+ }
+
+ internal static Guid CreateGuid(ReadOnlySpan bytes) => new GuidBits(bytes, bigEndian: false);
+
+ internal static bool IsAssignableTo(this Type left, Type right) => right.IsAssignableFrom(left);
+
+ internal static bool TryAdd(this Dictionary dictionary, TKey key, TValue value)
+ {
+ if (!dictionary.ContainsKey(key))
+ {
+ dictionary.Add(key, value);
+ return true;
+ }
+
+ return false;
+ }
+
+ internal static bool TryPop(this Stack stack, [MaybeNullWhen(false)] out T value)
+ {
+ if (stack.Count > 0)
+ {
+ value = stack.Pop();
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ internal static Exception ThrowNotSupportedOnNETFramework() => throw new PlatformNotSupportedException("This functionality is only supported on .NET.");
+
+ private readonly struct GuidBits
+ {
+ private readonly int a;
+ private readonly short b;
+ private readonly short c;
+#pragma warning disable CS0169 // The field is never used
+ private readonly byte d;
+ private readonly byte e;
+ private readonly byte f;
+ private readonly byte g;
+ private readonly byte h;
+ private readonly byte i;
+ private readonly byte j;
+ private readonly byte k;
+#pragma warning restore CS0169 // The field is never used
+
+ internal GuidBits(ReadOnlySpan b, bool bigEndian = false)
+ {
+ if (b.Length != 16)
+ {
+ throw new ArgumentException();
+ }
+
+ this = MemoryMarshal.Read(b);
+
+ if (bigEndian == BitConverter.IsLittleEndian)
+ {
+ this.a = BinaryPrimitives.ReverseEndianness(this.a);
+ this.b = BinaryPrimitives.ReverseEndianness(this.b);
+ this.c = BinaryPrimitives.ReverseEndianness(this.c);
+ }
+ }
+
+ public static implicit operator Guid(GuidBits value) => Unsafe.As(ref value);
+
+ public static implicit operator GuidBits(Guid value) => Unsafe.As(ref value);
+ }
+ }
+#endif
+}
+
+#if !NET
+
+namespace System.Diagnostics
+{
+ internal class UnreachableException : Exception
+ {
+ internal UnreachableException()
+ : base("This code path should be unreachable.")
+ {
+ }
+ }
+}
+
+#endif
diff --git a/src/Nerdbank.MessagePack/RawMessagePack.cs b/src/Nerdbank.MessagePack/RawMessagePack.cs
index 3e7ccdde..dc52880c 100644
--- a/src/Nerdbank.MessagePack/RawMessagePack.cs
+++ b/src/Nerdbank.MessagePack/RawMessagePack.cs
@@ -112,6 +112,9 @@ public RawMessagePack ToOwned()
}
private static bool SequenceEqual(in ReadOnlySequence a, in ReadOnlySequence b)
+#if !NET
+ where T : IEquatable
+#endif
{
if (a.Length != b.Length)
{
@@ -120,7 +123,11 @@ private static bool SequenceEqual(in ReadOnlySequence a, in ReadOnlySequen
if (a.IsSingleSegment && b.IsSingleSegment)
{
+#if NET
return a.FirstSpan.SequenceEqual(b.FirstSpan);
+#else
+ return a.First.Span.SequenceEqual(b.First.Span);
+#endif
}
ReadOnlySequence.Enumerator aEnumerator = a.GetEnumerator();
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueAggregatingEqualityComparer`1.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueAggregatingEqualityComparer`1.cs
index 11c6f204..d34550b0 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValueAggregatingEqualityComparer`1.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValueAggregatingEqualityComparer`1.cs
@@ -1,6 +1,11 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if !NET
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8767 // null ref annotations
+#endif
+
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueByteArrayEqualityComparer.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueByteArrayEqualityComparer.cs
index f6a66a17..48a9765b 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValueByteArrayEqualityComparer.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValueByteArrayEqualityComparer.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Diagnostics.CodeAnalysis;
-using System.Security.AccessControl;
namespace Nerdbank.MessagePack.SecureHash;
@@ -27,7 +26,14 @@ private ByValueByteArrayEqualityComparer()
public int GetHashCode([DisallowNull] byte[] obj)
{
HashCode hashCode = default;
+#if NET
hashCode.AddBytes(obj);
+#else
+ for (int i = 0; i < obj.Length; i++)
+ {
+ hashCode.Add(obj[i]);
+ }
+#endif
return hashCode.ToHashCode();
}
}
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueCustomEqualityComparer`1.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueCustomEqualityComparer`1.cs
index 083543c5..47f5684f 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValueCustomEqualityComparer`1.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValueCustomEqualityComparer`1.cs
@@ -1,6 +1,11 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if !NET
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8767 // null ref annotations
+#endif
+
using System.Diagnostics.CodeAnalysis;
namespace Nerdbank.MessagePack.SecureHash;
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueDictionaryEqualityComparer`3.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueDictionaryEqualityComparer`3.cs
index e928774d..0fdf5c53 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValueDictionaryEqualityComparer`3.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValueDictionaryEqualityComparer`3.cs
@@ -1,6 +1,11 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if !NET
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8767 // null ref annotations
+#endif
+
using System.Diagnostics.CodeAnalysis;
namespace Nerdbank.MessagePack.SecureHash;
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueEnumerableEqualityComparer`2.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueEnumerableEqualityComparer`2.cs
index 4c8590a9..d271b9cf 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValueEnumerableEqualityComparer`2.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValueEnumerableEqualityComparer`2.cs
@@ -1,6 +1,11 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if !NET
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8767 // null ref annotations
+#endif
+
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
@@ -28,8 +33,8 @@ public bool Equals(TEnumerable? x, TEnumerable? y)
IEnumerable enumerableX = getEnumerable(x);
IEnumerable enumerableY = getEnumerable(y);
- if (Enumerable.TryGetNonEnumeratedCount(enumerableX, out int countX) &&
- Enumerable.TryGetNonEnumeratedCount(enumerableY, out int countY) &&
+ if (PolyfillExtensions.TryGetNonEnumeratedCount(enumerableX, out int countX) &&
+ PolyfillExtensions.TryGetNonEnumeratedCount(enumerableY, out int countY) &&
countX != countY)
{
return false;
@@ -60,6 +65,11 @@ public int GetHashCode([DisallowNull] TEnumerable obj)
hashes.Add(element is null ? 0 : equalityComparer.GetHashCode(element));
}
- return unchecked((int)SipHash.Default.Compute(MemoryMarshal.Cast(CollectionsMarshal.AsSpan(hashes))));
+#if NET
+ Span span = CollectionsMarshal.AsSpan(hashes);
+#else
+ Span span = hashes.ToArray();
+#endif
+ return unchecked((int)SipHash.Default.Compute(MemoryMarshal.Cast(span)));
}
}
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueIReadOnlyListEqualityComparer`2.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueIReadOnlyListEqualityComparer`2.cs
index 007e1d36..dac80878 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValueIReadOnlyListEqualityComparer`2.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValueIReadOnlyListEqualityComparer`2.cs
@@ -1,6 +1,11 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if !NET
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8767 // null ref annotations
+#endif
+
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueNullableEqualityComparer`1.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueNullableEqualityComparer`1.cs
index 10fba6fc..4c1043b4 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValueNullableEqualityComparer`1.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValueNullableEqualityComparer`1.cs
@@ -1,6 +1,11 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if !NET
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8767 // null ref annotations
+#endif
+
using System.Diagnostics.CodeAnalysis;
namespace Nerdbank.MessagePack.SecureHash;
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValuePropertyEqualityComparer`2.cs b/src/Nerdbank.MessagePack/SecureHash/ByValuePropertyEqualityComparer`2.cs
index 6b732596..e09a3702 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValuePropertyEqualityComparer`2.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValuePropertyEqualityComparer`2.cs
@@ -1,6 +1,11 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if !NET
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8767 // null ref annotations
+#endif
+
using System.Diagnostics.CodeAnalysis;
namespace Nerdbank.MessagePack.SecureHash;
diff --git a/src/Nerdbank.MessagePack/SecureHash/ByValueVisitor.cs b/src/Nerdbank.MessagePack/SecureHash/ByValueVisitor.cs
index ae5d7553..cc78de44 100644
--- a/src/Nerdbank.MessagePack/SecureHash/ByValueVisitor.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/ByValueVisitor.cs
@@ -86,6 +86,11 @@ internal class DelayedEqualityComparerFactory : IDelayedValueFactory
public DelayedValue Create(ITypeShape typeShape)
=> new DelayedValue>(self => new DelayedEqualityComparer(self));
+#if !NET
+#pragma warning disable CS8604 // Possible null reference argument.
+#pragma warning disable CS8767 // null ref annotations
+#endif
+
private class DelayedEqualityComparer(DelayedValue> self) : IEqualityComparer
{
public bool Equals(T? x, T? y) => self.Result.Equals(x, y);
diff --git a/src/Nerdbank.MessagePack/SecureHash/CollisionResistantHasherUnmanaged`1.cs b/src/Nerdbank.MessagePack/SecureHash/CollisionResistantHasherUnmanaged`1.cs
index 81d55679..54bc7d23 100644
--- a/src/Nerdbank.MessagePack/SecureHash/CollisionResistantHasherUnmanaged`1.cs
+++ b/src/Nerdbank.MessagePack/SecureHash/CollisionResistantHasherUnmanaged`1.cs
@@ -1,6 +1,10 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+#if !NET
+#pragma warning disable CS1574 // unresolvable cref
+#endif
+
using System.Runtime.InteropServices;
namespace Nerdbank.MessagePack.SecureHash;
@@ -24,8 +28,25 @@ internal class CollisionResistantHasherUnmanaged : SecureEqualityComparer
{
///
public override bool Equals(T x, T y)
- => MemoryMarshal.Cast(new Span(ref x)).SequenceEqual(MemoryMarshal.Cast(new Span(ref y)));
+ {
+#if NET
+ Span ySpan = new(ref y);
+ Span xSpan = new(ref x);
+#else
+ Span