protobuf-net tries to avoid coding to any specific collection types, and instead tries to detect some key patterns; in particular, anything that looks like IList
, ICollection<T>
, or even just a custom iterator and Add(Foo)
, where the GetEnumerator()
returns an iterator where the .Current
is of type Foo
.
SortedDictionary<TKey,TValue>
implements ICollection<KeyValuePair<TKey, TValue>>
, which is what protobuf-net detects and uses; so the library treats it simply as a collection of key-value-pairs, calling .Add
etc. KeyValuePair<TKey,TValue>
is treated again by pattern logic as an "auto tuple": it has a constructor that takes all of the public members, so it maps them up in that order (the constructor is KeyValuePair(TKey key, TValue value)
, so .Key
is mapped as field 1, and .Value
is mapped as field 2). Putting this together, protobuf-net maps:
[ProtoContract]
class Foo
{
private readonly SortedDictionary<string, int> items =
new SortedDictionary<string, int>(
StringComparer.InvariantCultureIgnoreCase);
[ProtoMember(1)]
SortedDictionary<string, int> Items { get { return items; } }
}
via the schema (verifiable via Serializer.GetProto<Foo>()
):
message Foo {
repeated KeyValuePair_String_Int32 Items = 1;
}
message KeyValuePair_String_Int32 {
optional string Key = 1;
optional int32 Value = 2;
}
So basically, it should work fine; the only thing to watch would be to ensure that any custom IComparer<CustomTKey>
gets applied. I've done that above by only giving protobuf-net access to the getter, and performing the collection initiation inside our custom type - but you could also use a before-deserialization callback to do the same thing if you wanted to enable constructor-skipping.