Serializing null values in collection throws NullReferenceException in Protobuf V2.0.0668 - c#

1. NullReference Exception on null values inside nullable collection.
[ProtoContract]
public class ProtoTest
{
[ProtoMember(1)]
public List<string> Collecton { get; set; }
}
static void Main(string[] args)
{
ProtoTest test = new ProtoTest();
test.Collecton = new List<string>();
test.Collecton.Add("A");
test.Collecton.Add(null);
test.Collecton.Add("B");
test.Collecton.Add(null);
//This code works fine on Protobuf 1.0.0.282
//But throws exception on Protobuf 2.0.0.668
byte[] buffer = Serialize<ProtoTest>(test);
ProtoTest converted = Deserialize<ProtoTest>(buffer);
//In 1.0.0.282 converted.Collection is having 2 items excluded null values.
//In 2.0.0.668 Serialization fails with NullReference exception.
Console.Read();
}
2. Trying to Creates default instance on null values in side reference type collection
[ProtoContract]
public abstract class NullableBase
{
public NullableBase()
{
}
[ProtoMember(1)]
public int? Value { get; set; }
}
[ProtoContract]
public class NullableChild:NullableBase
{
[ProtoMember(2)]
public string StrValue { get; set; }
}
[ProtoContract]
public class ProtoTest
{
[ProtoMember(1)]
public List<NullableBase> RefCollecton { get; set; }
}
static void Main(string[] args)
{
var nullableBaseType=ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(NullableBase), true);
nullableBaseType.AddSubType(100, typeof(NullableChild));
ProtoTest test = new ProtoTest();
test.RefCollecton = new List<NullableBase>();
test.RefCollecton.Add(new NullableChild() { StrValue = "A" });
test.RefCollecton.Add(new NullableChild() { StrValue = "B" });
test.RefCollecton.Add(null);
byte[] buffer = Serialize<ProtoTest>(test);
//For null values on reference type Protobuf is trying to create default instance.
//Here the type is NullBase and its an abstract class. Protobuf wont be able to create it and throwing exception.
//Why Protobuf creates default instance for null values.
ProtoTest converted = Deserialize<ProtoTest>(buffer);
Console.Read();
}
We were using Protobuf 1.0.0.282 and it was excluding null values for reference types and nullable collections on serializing. But in V 2.0.0.668, its behaving in differently for nullable types and reference types . For nullable types it throws NullReferenceException on null values while serializing. But for reference types it got serialized and while deserializing its trying to create default instance for null values.
Why they removed this functionality?. Is there any specific reason for that?.
If there is any good reason, then for reference and nullable type collection it should throw nullreferenece exception right? We have tried this solution of "SupportNull" https://code.google.com/p/protobuf-net/issues/detail?id=217.

Related

Nullable Reference Types usage in classes with temporally nullable fields

In my following elaboration I am referencing to the new feature of Nullable Reference Types introduced in C# 8.
I ask me very often, whether I should or not decorate this or that reference type property nullable (eg. object? prop { get; } or [MaybeNull] T prop { get; })
I would like to show you the class that gives me a headache.
public sealed class LinkedBucketListNode<KeyType, ValueType>
where KeyType : notnull
{
public ValueType Value { get; }
public LinkedBucketListNodePart ListPart { get; }
public LinkedBucketListNodePart BucketPart { get; }
public YetNullable<KeyType> Key => bucket.Key;
public ILinkedBucketList<KeyType, ValueType>? Bucket => bucket; // <------- Declare as nullable or not????
internal LinkedBucketListBase<KeyType, ValueType>.LinkedBucket bucket = null!;
public LinkedBucketListNode(ValueType value)
{
ListPart = new LinkedBucketListNodeListPart(this);
BucketPart = new LinkedBucketListNodeBucketPart(this);
Value = value;
}
internal void Invalidate()
{
ListPart.Invalidate();
bucket = null!;
BucketPart.Invalidate();
}
...
}
This sealed class LinkedBucketListNode<KeyType, ValueType> has one of the SAME behaviour like LinkedListNode<T>:
Instantiate outside of LinkedBucketList
Add via AddLast, AddFirst, ... to a LinkedBucketList of your choice.
If getting invalidated due to remove, bucket gets null and is disconnected from bucket.
So you are going to expect bucket being null before you add the node and after you remove the node.
Now to my question. Is it appropiate to mark LinkedBucketListNode<KeyType, ValueType>.Bucket as nullable reference type or should I expect from the user, that he should now that LinkedBucketListNode<KeyType, ValueType>.Bucket will be null before adding and after removing it and dismiss it as expected API-behaviour?

How to deserialize a custom readonly struct requiring ctor invocation using protobuf-net?

I have created a custom (readonly) struct to encapsulate a decimal. I am using the struct everywhere, including a public-facing API consumed by various programming languages, and therefore would like to avoid exposing decimal data types.
This shows the relevant parts of the struct:
[ProtoContract(SkipConstructor = false, ImplicitFields=ImplicitFields.None)]
public readonly struct Amount
{
[ProtoIgnore]
public const decimal Scale = 100000m;
[ProtoIgnore]
public decimal Value { get; }
[ProtoMember(1)]
public long ScaledValue { get; }
public Amount(decimal value)
{
Value = value;
ScaledValue = checked((long)(value * Scale).Round(0));
}
public Amount(long scaledValue)
{
Value = scaledValue / Scale;
ScaledValue = scaledValue;
}
public static Amount CreateFrom(long scaledValue) => new Amount(scaledValue);
}
The problem I have is that the ctor is not invoked during deserialization, despite the SkipConstructor=false on ProtoContract, causing only the ScaledValue property to be correctly initialized.
I cannot use a ProtoAfterDeserialization method to set the Value property because the struct is readonly.
I have tried to configure a custom factory method for protobuf-net to use when creating the object, by doing this:
var createFrom = typeof(Amount).GetMethod("CreateFrom", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(long) }, null);
RuntimeTypeModel.Default[typeof(Amount)].SetFactory(createFrom);
But this invariably results in "InvalidOperationException: Operation is not valid due to the current state of the object.". I have verified that the CreateFrom method is found (so am passing in a valid MethodInfo object).
Any ideas on how to make this work?
struct, and readonly struct in particular, is something that I plan to address more in v3 which has plans for new serializer APIs. In the interim, it isn't a scenario it handles well, but your best bet may be "surrogates" - meaning the serializer largely ignores Amount, using something else more serialization-friendly in its place. This also means you can remove any serializer attributes or APIs from Amount:
using ProtoBuf;
using ProtoBuf.Meta;
static class P
{
static void Main()
{
// only need to do this once, *before*
// serializing/deserialing anything
RuntimeTypeModel.Default.Add(typeof(Amount), false)
.SetSurrogate(typeof(AmountSurrogate));
// test it works
var obj = new Foo { Amount = new Amount(123.45M) };
var clone = Serializer.DeepClone(obj);
System.Console.WriteLine(clone.Amount.Value);
}
}
[ProtoContract]
public class Foo
{
[ProtoMember(1)]
public Amount Amount { get; set; }
}
[ProtoContract]
struct AmountSurrogate
{ // a nice simple type for serialization
[ProtoMember(1)]
public long Value { get; set; }
// operators define how to get between the two types
public static implicit operator Amount(AmountSurrogate value)
=> Amount.CreateFrom(value.Value);
public static implicit operator AmountSurrogate(Amount value)
=> new AmountSurrogate { Value = value.ScaledValue };
}

Why does this nested object initializer throw a null reference exception?

The following testcase throws a null-reference exception, when it tries to assign Id to an object which is null, since the code is missing the "new R" before the object initializer.
Why is this not caught by the compiler? Why is it allowed, in which use-cases would this be a meaningful construct?
[TestClass]
public class ThrowAway
{
public class H
{
public int Id { get; set; }
}
public class R
{
public H Header { get; set; }
}
[TestMethod]
public void ThrowsException()
{
var request = new R
{
Header =
{
Id = 1
},
};
}
}
The compiler doesn't give a warning because you could have:
public class R
{
public H Header { get; set; }
public R()
{
Header = new H();
}
}
so Header could be initialized by someone/something. Solving if someone/something will initialize Header is a complex problem (probably similar to the Halting problem)... Not something that a compiler wants to solve for you :-)
From the C# specifications:
A member initializer that specifies an object initializer after the equals sign is a nested object initializer, i.e. an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type.
We are in the case of nested initializer, and see the bolded part. I didn't know it.
Now, note that new R { } is, by C# spec, an 7.6.10.1 Object creation expressions followed by an object-initializer, while the Header = { } is a "pure" 7.6.10.2 Object initializers.

protobuf-net: Deserializing onto an instance; setting property to null

I'm using protobuf-net's serialization library and my team came across an interesting use case. In our application, we're deserializing a byte array onto an existing instance. The existing instance has properties, some are nullable and some aren't. The issue that I'm facing is that if a nullable property on the existing instance has a value, but the serialized instance just received over the wire doesn't have a value for that property. Since Protocol Buffers doesn't have a concept of null, null isn't serialized into the byte array and thus the deserialize call doesn't overwrite the nullable property with null.
In case you couldn't follow that, I wrote a quick unit test to illustrate my point/question:
[TestFixture]
public class WhenSerializingNullableProperties
{
private RuntimeTypeModel _runtimeTypeModel;
[TestFixtureSetUp]
public void FixtureSetup()
{
_runtimeTypeModel = TypeModel.Create();
_runtimeTypeModel.Add(typeof (Foo), false).Add("Id", "Name");
}
internal class Foo
{
public string Name { get; set; }
public int? Id { get; set; }
}
[Test]
public void ShouldSetNullablePropertiesToNull()
{
var foo = new Foo { Name = "name", Id = null };
byte[] serialized;
using (var stream = new MemoryStream())
{
_runtimeTypeModel.Serialize(stream, security);
serialized = stream.ToArray();
}
foo.Id = 18;
foo.Name = "other";
using (var stream = new MemoryStream(serialized))
{
_runtimeTypeModel.Deserialize(stream, foo, typeof(Foo));
}
foo.Name.Should().Be("name"); // this PASSES
foo.Id.Should().NotHaveValue(); // this FAILS
}
}
I understand (and am grateful for) protobuf's compactness with excluding nulls. I don't want to throw in a default value and have my streams be larger. Instead, I'm looking for protobuf-net to set a nullable property to null if it's not included in the stream being deserialized.
Thanks in advance for any and all help provided.

Serializing List<T> using a surrogate with protobuf-net exception

I'm using protobuf-net (version 2.0.0.621) and having a problem serializing List type where T is my own class (it does't matter what it contains) and a surrogate is set for T.
The surrogate is set like this:
ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(MyClass), false).SetSurrogate(typeof(MyClassSurrogate));
MyClass:
public class MyClass
{
public int Number { get; set; }
}
[ProtoContract]
MyClassSurrogate
{
[ProtoMember(1)]
public int Number { get; set; }
}
Then I create a generic list of type MyClass instance, fill it with items and serialize it like this:
ProtoBuf.Serializer.Serialize(stream, list);
The problem occurs on deserialization, I keep getting "null" in the surrogate in the implicit operator conversion:
static public implicit operator MyClassSurrogate(MyClass myClass)
then 'myClass' is null.
If I remove the surrogate and decorate MyClass with the proto attributes, everything works fine.
Can you tell me what I'm doing wrong?
Thanks.
Adding a null check to the implicit operator conversion seems to fix the issue, i.e:
public static implicit operator MyClassSurrogate(MyClass myClass)
{
return myClass != null ? new MyClassSurrogate { Number = myClass.Number } : null;
}
The implicit operator is initially called once with a null value on deserialization with the result appearing to be ignored.
Full implementation of MyClassSurrogate:
[ProtoContract]
public class MyClassSurrogate
{
[ProtoMember(1)]
public int Number { get; set; }
public static implicit operator MyClassSurrogate(MyClass myClass)
{
return
myClass != null
? new MyClassSurrogate { Number = myClass.Number }
: null;
}
public static implicit operator MyClass(MyClassSurrogate myClass)
{
return new MyClass { Number = myClass.Number };
}
}
Full Serialization/Deserialization example:
var model = ProtoBuf.Meta.RuntimeTypeModel.Default;
model.Add(typeof(MyClassSurrogate), true);
model.Add(typeof(MyClass), false).SetSurrogate(typeof(MyClassSurrogate));
var stream = new System.IO.MemoryStream();
var list = new List<MyClass>();
for (int x = 0; x < 10; x++) list.Add(new MyClass { Number = x });
ProtoBuf.Serializer.Serialize(stream, list);
stream.Seek(0, SeekOrigin.Begin);
var xs = ProtoBuf.Serializer.Deserialize<List<MyClass>>(stream);
foreach (var x in xs) Debug.WriteLine(x.Number);
The value null is used pretty often, including during deserialization. You should be able to fix this simply by telling the conversion operator to translate null as null:
if(value == null) return null;
Thinking about it, I can probably safely add a "if both are reference types, translate null as null automatically".

Categories

Resources