Reputation: 535
I'm using the protobuf-net
package version 3.0.101.
The following code generates a runtime exception when executing typeModel.Serialize(ms, value)
. The exception is:
GenericArguments[0], 'UserQuery+Option`1[System.Int32]', on 'ProtoBuf.Serializers.RepeatedSerializer`2[TCollection,T] CreateEnumerable[TCollection,T]()' violates the constraint of type 'TCollection'.
void Main()
{
var typeModel = RuntimeTypeModel.Create();
typeModel.SetSurrogate<Option<int>, OptionSurrogate<int>>();
//typeModel[typeof(Option<int>)].IgnoreListHandling = true; // This doesn't help.
var value = new Option<int>(true, 5);
var ms = new MemoryStream();
typeModel.Serialize(ms, value);
}
struct Option<T> : IEnumerable<T>
{
public Option(bool hasValue, T value)
{
HasValue = hasValue;
Value = value;
}
public readonly bool HasValue;
public readonly T Value;
public IEnumerator<T> GetEnumerator()
{
if (HasValue) {
yield return Value;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[ProtoContract]
struct OptionSurrogate<T>
{
public OptionSurrogate(Option<T> thing) {
HasValue = thing.HasValue;
Value = thing.Value;
}
[ProtoMember(1)]
public readonly bool HasValue;
[ProtoMember(2)]
public readonly T Value;
public static implicit operator Option<T>(OptionSurrogate<T> surrogate) => new Option<T>(surrogate.HasValue, surrogate.Value);
public static implicit operator OptionSurrogate<T>(Option<T> value) => new OptionSurrogate<T>(value);
}
There are two things that will fix this:
struct Option<T>
to class Option<T>
IEnumerable<T>
from struct Option<T>
However, neither of these are possible because the struct
I want to serialize is in a 3rd party library.
Is this a bug in protobuf-net
or is there a workaround?
Upvotes: 1
Views: 265
Reputation: 2292
One possible workaround that might work is wrapping the object in your own class, where you will be able to control which properties get serialized and how.
So I pulled the protobuf
repo and stepped through their code for surrogate handling for IEnumerable<T>
(the Option<T>
) and their implementation makes heavy assumptions that any IEnumerable<T>
is an ICollection
by default leading to this error. Despite the fact that you explicitly give the runtime the information that Option<T>
is not serializable using SetSurrogate
, their implementation does not check surrogates until after the item it packed into a more manageable Enumerable
type. The problem here is that they're trying to pack:
public IEnumerator<T> GetEnumerator()
{
if (HasValue) {
yield return Value;
}
}
and that's not possible (in-fact they don't even 'see'/'look' for this kind of implementation of IEnumerable.
All that being said, I recommend just explictly casting the objects before and after deserialization, I provided an example that I got working below.
static void Main()
{
var typeModel = RuntimeTypeModel.Create();
var value = new Option<int>(true, 5);
using var writer = new FileInfo("test.txt").OpenWrite();
typeModel.Serialize(writer , (OptionSurrogate<int>)value, typeModel);
writer .Dispose();
using var reader = new FileInfo("test.txt").OpenRead();
Option<int> deserializedValue = (OptionSurrogate<int>)typeModel.Deserialize(reader, null, typeof(OptionSurrogate<int>));
}
Edit
Added simple implementation of ICollection<T>
to possibly prevent constraint error
Edit
Added Working work-around this time.
Editors Note
I am not an expert at protobuf
and my opinion of how and why this bug is happening is based on some very simple debugging and stepping through their github source. This should not be taken as a fact as I neither wrote protobuf
or have worked with the source code of protobuf
long enough to properly opine on the cause of this error.
Upvotes: 1