add C# deterministic serialization (#13160) https://github.com/protocolbuffers/protobuf/issues/12881 @jskeet review please :-) as this is my first contrib to protobuf, any design / performance feedback is very sought after Closes #13160 COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/13160 from fmg-lydonchandra:feature/12881_cs_serialization_deterministic ab7e01b8049ec98b78eb9fb6e04e8fe30ecd9a33 PiperOrigin-RevId: 568448399
diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs index f0be958..722cc92 100644 --- a/csharp/src/Google.Protobuf/Collections/MapField.cs +++ b/csharp/src/Google.Protobuf/Collections/MapField.cs
@@ -327,7 +327,7 @@ /// Returns a hash code for this instance. /// </summary> /// <returns> - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// </returns> public override int GetHashCode() { @@ -432,7 +432,13 @@ WriteContext.Initialize(output, out WriteContext ctx); try { - WriteTo(ref ctx, codec); + IEnumerable<KeyValuePair<TKey, TValue>> listToWrite = list; + + if (output.Deterministic) + { + listToWrite = GetSortedListCopy(list); + } + WriteTo(ref ctx, codec, listToWrite); } finally { @@ -440,6 +446,23 @@ } } + internal IEnumerable<KeyValuePair<TKey, TValue>> GetSortedListCopy(IEnumerable<KeyValuePair<TKey, TValue>> listToSort) + { + // We can't sort the list in place, as that would invalidate the linked list. + // Instead, we create a new list, sort that, and then write it out. + var listToWrite = new List<KeyValuePair<TKey, TValue>>(listToSort); + listToWrite.Sort((pair1, pair2) => + { + if (typeof(TKey) == typeof(string)) + { + // Use Ordinal, otherwise Comparer<string>.Default uses StringComparer.CurrentCulture + return StringComparer.Ordinal.Compare(pair1.Key.ToString(), pair2.Key.ToString()); + } + return Comparer<TKey>.Default.Compare(pair1.Key, pair2.Key); + }); + return listToWrite; + } + /// <summary> /// Writes the contents of this map to the given write context, using the specified codec /// to encode each entry. @@ -449,7 +472,18 @@ [SecuritySafeCritical] public void WriteTo(ref WriteContext ctx, Codec codec) { - foreach (var entry in list) + IEnumerable<KeyValuePair<TKey, TValue>> listToWrite = list; + if (ctx.state.CodedOutputStream?.Deterministic ?? false) + { + listToWrite = GetSortedListCopy(list); + } + WriteTo(ref ctx, codec, listToWrite); + } + + [SecuritySafeCritical] + private void WriteTo(ref WriteContext ctx, Codec codec, IEnumerable<KeyValuePair<TKey, TValue>> listKvp) + { + foreach (var entry in listKvp) { ctx.WriteTag(codec.MapTag); @@ -631,7 +665,7 @@ this.containsCheck = containsCheck; } - public int Count => parent.Count; + public int Count => parent.Count; public bool IsReadOnly => true;