Ported .Net Core commits:
hondacrx: - Initial commit: Switch to .Net Core 2.0 - Fix build and removed not needed files Fabi: - Updated solution platforms. - Changed folder structure. - Change library target framework to netstandard2.0. - Updated solution platforms again... - Removed windows specific kernel32 function usage (Ctrl-C handler).
This commit is contained in:
@@ -0,0 +1,770 @@
|
||||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#endregion
|
||||
|
||||
using Google.Protobuf.Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Google.Protobuf.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Representation of a map field in a Protocol Buffer message.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam>
|
||||
/// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Null values are not permitted in the map, either for wrapper types or regular messages.
|
||||
/// If a map is deserialized from a data stream and the value is missing from an entry, a default value
|
||||
/// is created instead. For primitive types, that is the regular default value (0, the empty string and so
|
||||
/// on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length
|
||||
/// encoded value for the field.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This implementation does not generally prohibit the use of key/value types which are not
|
||||
/// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee
|
||||
/// that all operations will work in such cases.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The order in which entries are returned when iterating over this object is undefined, and may change
|
||||
/// in future versions.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary
|
||||
#if !NET35
|
||||
, IReadOnlyDictionary<TKey, TValue>
|
||||
#endif
|
||||
{
|
||||
// TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
|
||||
private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
|
||||
new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
|
||||
private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of this object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A deep clone of this object.
|
||||
/// </returns>
|
||||
public MapField<TKey, TValue> Clone()
|
||||
{
|
||||
var clone = new MapField<TKey, TValue>();
|
||||
// Keys are never cloneable. Values might be.
|
||||
if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
|
||||
{
|
||||
foreach (var pair in list)
|
||||
{
|
||||
clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nothing is cloneable, so we don't need to worry.
|
||||
clone.Add(this);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified key/value pair to the map.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This operation fails if the key already exists in the map. To replace an existing entry, use the indexer.
|
||||
/// </remarks>
|
||||
/// <param name="key">The key to add</param>
|
||||
/// <param name="value">The value to add.</param>
|
||||
/// <exception cref="System.ArgumentException">The given key already exists in map.</exception>
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
// Validation of arguments happens in ContainsKey and the indexer
|
||||
if (ContainsKey(key))
|
||||
{
|
||||
throw new ArgumentException("Key already exists in map", nameof(key));
|
||||
}
|
||||
this[key] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified key is present in the map.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to check.</param>
|
||||
/// <returns><c>true</c> if the map contains the given key; <c>false</c> otherwise.</returns>
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
|
||||
return map.ContainsKey(key);
|
||||
}
|
||||
|
||||
private bool ContainsValue(TValue value)
|
||||
{
|
||||
var comparer = EqualityComparer<TValue>.Default;
|
||||
return list.Any(pair => comparer.Equals(pair.Value, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the entry identified by the given key from the map.
|
||||
/// </summary>
|
||||
/// <param name="key">The key indicating the entry to remove from the map.</param>
|
||||
/// <returns><c>true</c> if the map contained the given key before the entry was removed; <c>false</c> otherwise.</returns>
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
|
||||
LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
||||
if (map.TryGetValue(key, out node))
|
||||
{
|
||||
map.Remove(key);
|
||||
node.List.Remove(node);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key whose value to get.</param>
|
||||
/// <param name="value">When this method returns, the value associated with the specified key, if the key is found;
|
||||
/// otherwise, the default value for the type of the <paramref name="value"/> parameter.
|
||||
/// This parameter is passed uninitialized.</param>
|
||||
/// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns>
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
||||
if (map.TryGetValue(key, out node))
|
||||
{
|
||||
value = node.Value.Value;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default(TValue);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to get or set.</param>
|
||||
/// <exception cref="KeyNotFoundException">The property is retrieved and key does not exist in the collection.</exception>
|
||||
/// <returns>The value associated with the specified key. If the specified key is not found,
|
||||
/// a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
|
||||
TValue value;
|
||||
if (TryGetValue(key, out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
set
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
|
||||
// value == null check here is redundant, but avoids boxing.
|
||||
if (value == null)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
|
||||
}
|
||||
LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
||||
var pair = new KeyValuePair<TKey, TValue>(key, value);
|
||||
if (map.TryGetValue(key, out node))
|
||||
{
|
||||
node.Value = pair;
|
||||
}
|
||||
else
|
||||
{
|
||||
node = list.AddLast(pair);
|
||||
map[key] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection containing the keys in the map.
|
||||
/// </summary>
|
||||
public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection containing the values in the map.
|
||||
/// </summary>
|
||||
public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified entries to the map. The keys and values are not automatically cloned.
|
||||
/// </summary>
|
||||
/// <param name="entries">The entries to add to the map.</param>
|
||||
public void Add(IDictionary<TKey, TValue> entries)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(entries, nameof(entries));
|
||||
foreach (var pair in entries)
|
||||
{
|
||||
Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through a collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified item to the map.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add to the map.</param>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the map.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
list.Clear();
|
||||
map.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether map contains an entry equivalent to the given key/value pair.
|
||||
/// </summary>
|
||||
/// <param name="item">The key/value pair to find.</param>
|
||||
/// <returns></returns>
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
TValue value;
|
||||
return TryGetValue(item.Key, out value)
|
||||
&& EqualityComparer<TValue>.Default.Equals(item.Value, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the key/value pairs in this map to an array.
|
||||
/// </summary>
|
||||
/// <param name="array">The array to copy the entries into.</param>
|
||||
/// <param name="arrayIndex">The index of the array at which to start copying values.</param>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified key/value pair from the map.
|
||||
/// </summary>
|
||||
/// <remarks>Both the key and the value must be found for the entry to be removed.</remarks>
|
||||
/// <param name="item">The key/value pair to remove.</param>
|
||||
/// <returns><c>true</c> if the key/value pair was found and removed; <c>false</c> otherwise.</returns>
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
if (item.Key == null)
|
||||
{
|
||||
throw new ArgumentException("Key is null", nameof(item));
|
||||
}
|
||||
LinkedListNode<KeyValuePair<TKey, TValue>> node;
|
||||
if (map.TryGetValue(item.Key, out node) &&
|
||||
EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value))
|
||||
{
|
||||
map.Remove(item.Key);
|
||||
node.List.Remove(node);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements contained in the map.
|
||||
/// </summary>
|
||||
public int Count { get { return list.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the map is read-only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly { get { return false; } }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="System.Object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return Equals(other as MapField<TKey, TValue>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var valueComparer = EqualityComparer<TValue>.Default;
|
||||
int hash = 0;
|
||||
foreach (var pair in list)
|
||||
{
|
||||
hash ^= pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(pair.Value);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares this map with another for equality.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The order of the key/value pairs in the maps is not deemed significant in this comparison.
|
||||
/// </remarks>
|
||||
/// <param name="other">The map to compare this with.</param>
|
||||
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal map; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(MapField<TKey, TValue> other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (other == this)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (other.Count != this.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var valueComparer = EqualityComparer<TValue>.Default;
|
||||
foreach (var pair in this)
|
||||
{
|
||||
TValue value;
|
||||
if (!other.TryGetValue(pair.Key, out value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!valueComparer.Equals(value, pair.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds entries to the map from the given stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is assumed that the stream is initially positioned after the tag specified by the codec.
|
||||
/// This method will continue reading entries from the stream until the end is reached, or
|
||||
/// a different tag is encountered.
|
||||
/// </remarks>
|
||||
/// <param name="input">Stream to read from</param>
|
||||
/// <param name="codec">Codec describing how the key/value pairs are encoded</param>
|
||||
public void AddEntriesFrom(CodedInputStream input, Codec codec)
|
||||
{
|
||||
var adapter = new Codec.MessageAdapter(codec);
|
||||
do
|
||||
{
|
||||
adapter.Reset();
|
||||
input.ReadMessage(adapter);
|
||||
this[adapter.Key] = adapter.Value;
|
||||
} while (input.MaybeConsumeTag(codec.MapTag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of this map to the given coded output stream, using the specified codec
|
||||
/// to encode each entry.
|
||||
/// </summary>
|
||||
/// <param name="output">The output stream to write to.</param>
|
||||
/// <param name="codec">The codec to use for each entry.</param>
|
||||
public void WriteTo(CodedOutputStream output, Codec codec)
|
||||
{
|
||||
var message = new Codec.MessageAdapter(codec);
|
||||
foreach (var entry in list)
|
||||
{
|
||||
message.Key = entry.Key;
|
||||
message.Value = entry.Value;
|
||||
output.WriteTag(codec.MapTag);
|
||||
output.WriteMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size of this map based on the given entry codec.
|
||||
/// </summary>
|
||||
/// <param name="codec">The codec to use to encode each entry.</param>
|
||||
/// <returns></returns>
|
||||
public int CalculateSize(Codec codec)
|
||||
{
|
||||
if (Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var message = new Codec.MessageAdapter(codec);
|
||||
int size = 0;
|
||||
foreach (var entry in list)
|
||||
{
|
||||
message.Key = entry.Key;
|
||||
message.Value = entry.Value;
|
||||
size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag);
|
||||
size += CodedOutputStream.ComputeMessageSize(message);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of this repeated field, in the same
|
||||
/// way as it would be represented by the default JSON formatter.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
JsonFormatter.Default.WriteDictionary(writer, this);
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
#region IDictionary explicit interface implementation
|
||||
void IDictionary.Add(object key, object value)
|
||||
{
|
||||
Add((TKey)key, (TValue)value);
|
||||
}
|
||||
|
||||
bool IDictionary.Contains(object key)
|
||||
{
|
||||
if (!(key is TKey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return ContainsKey((TKey)key);
|
||||
}
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
{
|
||||
return new DictionaryEnumerator(GetEnumerator());
|
||||
}
|
||||
|
||||
void IDictionary.Remove(object key)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(key, nameof(key));
|
||||
if (!(key is TKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Remove((TKey)key);
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
// This is ugly and slow as heck, but with any luck it will never be used anyway.
|
||||
ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList();
|
||||
temp.CopyTo(array, index);
|
||||
}
|
||||
|
||||
bool IDictionary.IsFixedSize { get { return false; } }
|
||||
|
||||
ICollection IDictionary.Keys { get { return (ICollection)Keys; } }
|
||||
|
||||
ICollection IDictionary.Values { get { return (ICollection)Values; } }
|
||||
|
||||
bool ICollection.IsSynchronized { get { return false; } }
|
||||
|
||||
object ICollection.SyncRoot { get { return this; } }
|
||||
|
||||
object IDictionary.this[object key]
|
||||
{
|
||||
get
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(key, nameof(key));
|
||||
if (!(key is TKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
TValue value;
|
||||
TryGetValue((TKey)key, out value);
|
||||
return value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this[(TKey)key] = (TValue)value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IReadOnlyDictionary explicit interface implementation
|
||||
#if !NET35
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
private class DictionaryEnumerator : IDictionaryEnumerator
|
||||
{
|
||||
private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
|
||||
|
||||
internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)
|
||||
{
|
||||
this.enumerator = enumerator;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
return enumerator.MoveNext();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
enumerator.Reset();
|
||||
}
|
||||
|
||||
public object Current { get { return Entry; } }
|
||||
public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } }
|
||||
public object Key { get { return enumerator.Current.Key; } }
|
||||
public object Value { get { return enumerator.Current.Value; } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A codec for a specific map field. This contains all the information required to encode and
|
||||
/// decode the nested messages.
|
||||
/// </summary>
|
||||
public sealed class Codec
|
||||
{
|
||||
private readonly FieldCodec<TKey> keyCodec;
|
||||
private readonly FieldCodec<TValue> valueCodec;
|
||||
private readonly uint mapTag;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new entry codec based on a separate key codec and value codec,
|
||||
/// and the tag to use for each map entry.
|
||||
/// </summary>
|
||||
/// <param name="keyCodec">The key codec.</param>
|
||||
/// <param name="valueCodec">The value codec.</param>
|
||||
/// <param name="mapTag">The map tag to use to introduce each map entry.</param>
|
||||
public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)
|
||||
{
|
||||
this.keyCodec = keyCodec;
|
||||
this.valueCodec = valueCodec;
|
||||
this.mapTag = mapTag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The tag used in the enclosing message to indicate map entries.
|
||||
/// </summary>
|
||||
internal uint MapTag { get { return mapTag; } }
|
||||
|
||||
/// <summary>
|
||||
/// A mutable message class, used for parsing and serializing. This
|
||||
/// delegates the work to a codec, but implements the <see cref="IMessage"/> interface
|
||||
/// for interop with <see cref="CodedInputStream"/> and <see cref="CodedOutputStream"/>.
|
||||
/// This is nested inside Codec as it's tightly coupled to the associated codec,
|
||||
/// and it's simpler if it has direct access to all its fields.
|
||||
/// </summary>
|
||||
internal class MessageAdapter : IMessage
|
||||
{
|
||||
private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
|
||||
|
||||
private readonly Codec codec;
|
||||
internal TKey Key { get; set; }
|
||||
internal TValue Value { get; set; }
|
||||
|
||||
internal MessageAdapter(Codec codec)
|
||||
{
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
Key = codec.keyCodec.DefaultValue;
|
||||
Value = codec.valueCodec.DefaultValue;
|
||||
}
|
||||
|
||||
public void MergeFrom(CodedInputStream input)
|
||||
{
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0)
|
||||
{
|
||||
if (tag == codec.keyCodec.Tag)
|
||||
{
|
||||
Key = codec.keyCodec.Read(input);
|
||||
}
|
||||
else if (tag == codec.valueCodec.Tag)
|
||||
{
|
||||
Value = codec.valueCodec.Read(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
input.SkipLastField();
|
||||
}
|
||||
}
|
||||
|
||||
// Corner case: a map entry with a key but no value, where the value type is a message.
|
||||
// Read it as if we'd seen an input stream with no data (i.e. create a "default" message).
|
||||
if (Value == null)
|
||||
{
|
||||
Value = codec.valueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteTo(CodedOutputStream output)
|
||||
{
|
||||
codec.keyCodec.WriteTagAndValue(output, Key);
|
||||
codec.valueCodec.WriteTagAndValue(output, Value);
|
||||
}
|
||||
|
||||
public int CalculateSize()
|
||||
{
|
||||
return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value);
|
||||
}
|
||||
|
||||
MessageDescriptor IMessage.Descriptor { get { return null; } }
|
||||
}
|
||||
}
|
||||
|
||||
private class MapView<T> : ICollection<T>, ICollection
|
||||
{
|
||||
private readonly MapField<TKey, TValue> parent;
|
||||
private readonly Func<KeyValuePair<TKey, TValue>, T> projection;
|
||||
private readonly Func<T, bool> containsCheck;
|
||||
|
||||
internal MapView(
|
||||
MapField<TKey, TValue> parent,
|
||||
Func<KeyValuePair<TKey, TValue>, T> projection,
|
||||
Func<T, bool> containsCheck)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.projection = projection;
|
||||
this.containsCheck = containsCheck;
|
||||
}
|
||||
|
||||
public int Count { get { return parent.Count; } }
|
||||
|
||||
public bool IsReadOnly { get { return true; } }
|
||||
|
||||
public bool IsSynchronized { get { return false; } }
|
||||
|
||||
public object SyncRoot { get { return parent; } }
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return containsCheck(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
if (arrayIndex + Count > array.Length)
|
||||
{
|
||||
throw new ArgumentException("Not enough space in the array", nameof(array));
|
||||
}
|
||||
foreach (var item in this)
|
||||
{
|
||||
array[arrayIndex++] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return parent.list.Select(projection).GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
if (index + Count > array.Length)
|
||||
{
|
||||
throw new ArgumentException("Not enough space in the array", nameof(array));
|
||||
}
|
||||
foreach (var item in this)
|
||||
{
|
||||
array.SetValue(item, index++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Google.Protobuf.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Read-only wrapper around another dictionary.
|
||||
/// </summary>
|
||||
internal sealed class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
|
||||
{
|
||||
private readonly IDictionary<TKey, TValue> wrapped;
|
||||
|
||||
public ReadOnlyDictionary(IDictionary<TKey, TValue> wrapped)
|
||||
{
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return wrapped.ContainsKey(key);
|
||||
}
|
||||
|
||||
public ICollection<TKey> Keys
|
||||
{
|
||||
get { return wrapped.Keys; }
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return wrapped.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public ICollection<TValue> Values
|
||||
{
|
||||
get { return wrapped.Values; }
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get { return wrapped[key]; }
|
||||
set { throw new InvalidOperationException(); }
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return wrapped.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
wrapped.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return wrapped.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return wrapped.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable) wrapped).GetEnumerator();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return wrapped.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return wrapped.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return wrapped.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
#region Copyright notice and license
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Google.Protobuf.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// The contents of a repeated field: essentially, a collection with some extra
|
||||
/// restrictions (no null values) and capabilities (deep cloning).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This implementation does not generally prohibit the use of types which are not
|
||||
/// supported by Protocol Buffers but nor does it guarantee that all operations will work in such cases.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The element type of the repeated field.</typeparam>
|
||||
public sealed class RepeatedField<T> : IList<T>, IList, IDeepCloneable<RepeatedField<T>>, IEquatable<RepeatedField<T>>
|
||||
#if !NET35
|
||||
, IReadOnlyList<T>
|
||||
#endif
|
||||
{
|
||||
private static readonly T[] EmptyArray = new T[0];
|
||||
private const int MinArraySize = 8;
|
||||
|
||||
private T[] array = EmptyArray;
|
||||
private int count = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of this repeated field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the field type is
|
||||
/// a message type, each element is also cloned; otherwise, it is
|
||||
/// assumed that the field type is primitive (including string and
|
||||
/// bytes, both of which are immutable) and so a simple copy is
|
||||
/// equivalent to a deep clone.
|
||||
/// </remarks>
|
||||
/// <returns>A deep clone of this repeated field.</returns>
|
||||
public RepeatedField<T> Clone()
|
||||
{
|
||||
RepeatedField<T> clone = new RepeatedField<T>();
|
||||
if (array != EmptyArray)
|
||||
{
|
||||
clone.array = (T[])array.Clone();
|
||||
IDeepCloneable<T>[] cloneableArray = clone.array as IDeepCloneable<T>[];
|
||||
if (cloneableArray != null)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
clone.array[i] = cloneableArray[i].Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
clone.count = count;
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the entries from the given input stream, decoding them with the specified codec.
|
||||
/// </summary>
|
||||
/// <param name="input">The input stream to read from.</param>
|
||||
/// <param name="codec">The codec to use in order to read each entry.</param>
|
||||
public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)
|
||||
{
|
||||
// TODO: Inline some of the Add code, so we can avoid checking the size on every
|
||||
// iteration.
|
||||
uint tag = input.LastTag;
|
||||
var reader = codec.ValueReader;
|
||||
// Non-nullable value types can be packed or not.
|
||||
if (FieldCodec<T>.IsPackedRepeatedField(tag))
|
||||
{
|
||||
int length = input.ReadLength();
|
||||
if (length > 0)
|
||||
{
|
||||
int oldLimit = input.PushLimit(length);
|
||||
while (!input.ReachedLimit)
|
||||
{
|
||||
Add(reader(input));
|
||||
}
|
||||
input.PopLimit(oldLimit);
|
||||
}
|
||||
// Empty packed field. Odd, but valid - just ignore.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not packed... (possibly not packable)
|
||||
do
|
||||
{
|
||||
Add(reader(input));
|
||||
} while (input.MaybeConsumeTag(tag));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size of this collection based on the given codec.
|
||||
/// </summary>
|
||||
/// <param name="codec">The codec to use when encoding each field.</param>
|
||||
/// <returns>The number of bytes that would be written to a <see cref="CodedOutputStream"/> by <see cref="WriteTo"/>,
|
||||
/// using the same codec.</returns>
|
||||
public int CalculateSize(FieldCodec<T> codec)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
uint tag = codec.Tag;
|
||||
if (codec.PackedRepeatedField)
|
||||
{
|
||||
int dataSize = CalculatePackedDataSize(codec);
|
||||
return CodedOutputStream.ComputeRawVarint32Size(tag) +
|
||||
CodedOutputStream.ComputeLengthSize(dataSize) +
|
||||
dataSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sizeCalculator = codec.ValueSizeCalculator;
|
||||
int size = count * CodedOutputStream.ComputeRawVarint32Size(tag);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
size += sizeCalculator(array[i]);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculatePackedDataSize(FieldCodec<T> codec)
|
||||
{
|
||||
int fixedSize = codec.FixedSize;
|
||||
if (fixedSize == 0)
|
||||
{
|
||||
var calculator = codec.ValueSizeCalculator;
|
||||
int tmp = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
tmp += calculator(array[i]);
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fixedSize * Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of this collection to the given <see cref="CodedOutputStream"/>,
|
||||
/// encoding each value using the specified codec.
|
||||
/// </summary>
|
||||
/// <param name="output">The output stream to write to.</param>
|
||||
/// <param name="codec">The codec to use when encoding each value.</param>
|
||||
public void WriteTo(CodedOutputStream output, FieldCodec<T> codec)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var writer = codec.ValueWriter;
|
||||
var tag = codec.Tag;
|
||||
if (codec.PackedRepeatedField)
|
||||
{
|
||||
// Packed primitive type
|
||||
uint size = (uint)CalculatePackedDataSize(codec);
|
||||
output.WriteTag(tag);
|
||||
output.WriteRawVarint32(size);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
writer(output, array[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not packed: a simple tag/value pair for each value.
|
||||
// Can't use codec.WriteTagAndValue, as that omits default values.
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
output.WriteTag(tag);
|
||||
writer(output, array[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSize(int size)
|
||||
{
|
||||
if (array.Length < size)
|
||||
{
|
||||
size = Math.Max(size, MinArraySize);
|
||||
int newSize = Math.Max(array.Length * 2, size);
|
||||
var tmp = new T[newSize];
|
||||
Array.Copy(array, 0, tmp, 0, array.Length);
|
||||
array = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified item to the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
public void Add(T item)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
|
||||
EnsureSize(count + 1);
|
||||
array[count++] = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all items from the collection.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
array = EmptyArray;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this collection contains the given item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to find.</param>
|
||||
/// <returns><c>true</c> if this collection contains the given item; <c>false</c> otherwise.</returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return IndexOf(item) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies this collection to the given array.
|
||||
/// </summary>
|
||||
/// <param name="array">The array to copy to.</param>
|
||||
/// <param name="arrayIndex">The first index of the array to copy to.</param>
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
Array.Copy(this.array, 0, array, arrayIndex, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified item from the collection
|
||||
/// </summary>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
/// <returns><c>true</c> if the item was found and removed; <c>false</c> otherwise.</returns>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
int index = IndexOf(item);
|
||||
if (index == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Array.Copy(array, index + 1, array, index, count - index - 1);
|
||||
count--;
|
||||
array[count] = default(T);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements contained in the collection.
|
||||
/// </summary>
|
||||
public int Count => count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the collection is read-only.
|
||||
/// </summary>
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// Adds all of the specified values into this collection.
|
||||
/// </summary>
|
||||
/// <param name="values">The values to add to this collection.</param>
|
||||
public void AddRange(IEnumerable<T> values)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNull(values, nameof(values));
|
||||
|
||||
// Optimization 1: If the collection we're adding is already a RepeatedField<T>,
|
||||
// we know the values are valid.
|
||||
var otherRepeatedField = values as RepeatedField<T>;
|
||||
if (otherRepeatedField != null)
|
||||
{
|
||||
EnsureSize(count + otherRepeatedField.count);
|
||||
Array.Copy(otherRepeatedField.array, 0, array, count, otherRepeatedField.count);
|
||||
count += otherRepeatedField.count;
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimization 2: The collection is an ICollection, so we can expand
|
||||
// just once and ask the collection to copy itself into the array.
|
||||
var collection = values as ICollection;
|
||||
if (collection != null)
|
||||
{
|
||||
var extraCount = collection.Count;
|
||||
// For reference types and nullable value types, we need to check that there are no nulls
|
||||
// present. (This isn't a thread-safe approach, but we don't advertise this is thread-safe.)
|
||||
// We expect the JITter to optimize this test to true/false, so it's effectively conditional
|
||||
// specialization.
|
||||
if (default(T) == null)
|
||||
{
|
||||
// TODO: Measure whether iterating once to check and then letting the collection copy
|
||||
// itself is faster or slower than iterating and adding as we go. For large
|
||||
// collections this will not be great in terms of cache usage... but the optimized
|
||||
// copy may be significantly faster than doing it one at a time.
|
||||
foreach (var item in collection)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentException("Sequence contained null element", nameof(values));
|
||||
}
|
||||
}
|
||||
}
|
||||
EnsureSize(count + extraCount);
|
||||
collection.CopyTo(array, count);
|
||||
count += extraCount;
|
||||
return;
|
||||
}
|
||||
|
||||
// We *could* check for ICollection<T> as well, but very very few collections implement
|
||||
// ICollection<T> but not ICollection. (HashSet<T> does, for one...)
|
||||
|
||||
// Fall back to a slower path of adding items one at a time.
|
||||
foreach (T item in values)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all of the specified values into this collection. This method is present to
|
||||
/// allow repeated fields to be constructed from queries within collection initializers.
|
||||
/// Within non-collection-initializer code, consider using the equivalent <see cref="AddRange"/>
|
||||
/// method instead for clarity.
|
||||
/// </summary>
|
||||
/// <param name="values">The values to add to this collection.</param>
|
||||
public void Add(IEnumerable<T> values)
|
||||
{
|
||||
AddRange(values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
yield return array[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as RepeatedField<T>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through a collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
hash = hash * 31 + array[i].GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares this repeated field with another for equality.
|
||||
/// </summary>
|
||||
/// <param name="other">The repeated field to compare this with.</param>
|
||||
/// <returns><c>true</c> if <paramref name="other"/> refers to an equal repeated field; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(RepeatedField<T> other)
|
||||
{
|
||||
if (ReferenceEquals(other, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (other.Count != this.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!comparer.Equals(array[i], other.array[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the given item within the collection, or -1 if the item is not
|
||||
/// present.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to find in the collection.</param>
|
||||
/// <returns>The zero-based index of the item, or -1 if it is not found.</returns>
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
|
||||
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (comparer.Equals(array[i], item))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the given item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index at which to insert the item.</param>
|
||||
/// <param name="item">The item to insert.</param>
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
|
||||
if (index < 0 || index > count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
EnsureSize(count + 1);
|
||||
Array.Copy(array, index, array, index + 1, count - index);
|
||||
array[index] = item;
|
||||
count++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
Array.Copy(array, index + 1, array, index, count - index - 1);
|
||||
count--;
|
||||
array[count] = default(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of this repeated field, in the same
|
||||
/// way as it would be represented by the default JSON formatter.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
JsonFormatter.Default.WriteList(writer, this);
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item at the specified index.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The element at the specified index.
|
||||
/// </value>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
/// <returns>The item at the specified index.</returns>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
return array[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
|
||||
array[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#region Explicit interface implementation for IList and ICollection.
|
||||
bool IList.IsFixedSize => false;
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
Array.Copy(this.array, 0, array, index, count);
|
||||
}
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
object IList.this[int index]
|
||||
{
|
||||
get { return this[index]; }
|
||||
set { this[index] = (T)value; }
|
||||
}
|
||||
|
||||
int IList.Add(object value)
|
||||
{
|
||||
Add((T) value);
|
||||
return count - 1;
|
||||
}
|
||||
|
||||
bool IList.Contains(object value)
|
||||
{
|
||||
return (value is T && Contains((T)value));
|
||||
}
|
||||
|
||||
int IList.IndexOf(object value)
|
||||
{
|
||||
if (!(value is T))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return IndexOf((T)value);
|
||||
}
|
||||
|
||||
void IList.Insert(int index, object value)
|
||||
{
|
||||
Insert(index, (T) value);
|
||||
}
|
||||
|
||||
void IList.Remove(object value)
|
||||
{
|
||||
if (!(value is T))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Remove((T)value);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user