diff --git a/src/Transit/Impl/ListWrapper.cs b/src/Transit/Impl/ListWrapper.cs new file mode 100644 index 0000000..4476e8d --- /dev/null +++ b/src/Transit/Impl/ListWrapper.cs @@ -0,0 +1,8 @@ +namespace Transit.Net.Impl; + +/// +/// A wrapper class for lists, to semantically preserve how the list should be serialized. +/// Normally things tagged with "list" would be interpreted as LinkedLists, but in C# we want to +/// use regular Lists for performance. +/// +internal sealed class ListWrapper : List; diff --git a/src/Transit/Impl/NullKeyDictionary.cs b/src/Transit/Impl/NullKeyDictionary.cs index 6c674df..a8f9666 100644 --- a/src/Transit/Impl/NullKeyDictionary.cs +++ b/src/Transit/Impl/NullKeyDictionary.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; namespace Transit.Net.Impl; @@ -6,7 +7,7 @@ namespace Transit.Net.Impl; /// A dictionary that supports null keys, matching Java's HashMap behavior. /// Used by cmap (composite-key map) read handling where null can be a valid key. /// -internal sealed class NullKeyDictionary : IDictionary +internal sealed class NullKeyDictionary : IDictionary, IDictionary, IReadOnlyDictionary { private readonly Dictionary _inner = new(); private bool _hasNullKey; @@ -26,10 +27,16 @@ public object? this[object? key] public int Count => _inner.Count + (_hasNullKey ? 1 : 0); - public bool Contains(object? key) + public bool ContainsKey(object? key) => key is null ? _hasNullKey : _inner.ContainsKey(key); - public ICollection Keys + bool IDictionary.Contains(object? key) => ContainsKey(key); + + bool ICollection>.Contains(KeyValuePair kvp) => kvp.Key is null + ? _hasNullKey && EqualityComparer.Default.Equals(kvp.Value, _nullValue) + : ((ICollection>)_inner).Contains(kvp); + + public ICollection Keys { get { @@ -41,11 +48,15 @@ public ICollection Keys } } - public ICollection Values + IEnumerable IReadOnlyDictionary.Keys => Keys; + + ICollection IDictionary.Keys => (ICollection)Keys; + + public ICollection Values { get { - if (!_hasNullKey) return (ICollection)_inner.Values; + if (!_hasNullKey) return _inner.Values; var values = new List(_inner.Count + 1); foreach (var v in _inner.Values) values.Add(v); values.Add(_nullValue); @@ -53,26 +64,69 @@ public ICollection Values } } + IEnumerable IReadOnlyDictionary.Values => Values; + + ICollection IDictionary.Values => (ICollection)Values; + public bool IsFixedSize => false; public bool IsReadOnly => false; public bool IsSynchronized => false; public object SyncRoot => this; public void Add(object key, object? value) => this[key] = value; + + void ICollection>.Add(KeyValuePair kvp) => Add(kvp.Key, kvp.Value); + public void Clear() { _inner.Clear(); _hasNullKey = false; _nullValue = null; } - public void Remove(object key) + + public bool Remove(object? key) { - if (key is null) { _hasNullKey = false; _nullValue = null; } - else _inner.Remove(key); + if (key is null) + { + if (_hasNullKey) + { + _hasNullKey = false; + _nullValue = null; + return true; + } + return false; + } + return _inner.Remove(key); } + void IDictionary.Remove(object key) => Remove(key); + + bool ICollection>.Remove(KeyValuePair item) + => throw new NotImplementedException(); + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + => throw new NotImplementedException(); + public IDictionaryEnumerator GetEnumerator() => new NullKeyEnumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - private sealed class NullKeyEnumerator : IDictionaryEnumerator + IEnumerator> IEnumerable>.GetEnumerator() + => new NullKeyEnumerator(this); + + public bool TryGetValue(object? key, out object? value) + { + if (key is null) + { + if (_hasNullKey) + { + value = _nullValue; + return true; + } + value = default; + return false; + } + return _inner.TryGetValue(key, out value); + } + + private sealed class NullKeyEnumerator : IDictionaryEnumerator, IEnumerator> { private readonly NullKeyDictionary _dict; private readonly IEnumerator> _innerEnum; @@ -93,6 +147,12 @@ public NullKeyEnumerator(NullKeyDictionary dict) public object? Value => Entry.Value; public object Current => Entry; + KeyValuePair IEnumerator>.Current => _onNull + ? new KeyValuePair(null!, _dict._nullValue) + : new KeyValuePair(_innerEnum.Current.Key, _innerEnum.Current.Value); + + public void Dispose() { } + public bool MoveNext() { if (_innerEnum.MoveNext()) { _onNull = false; return true; } diff --git a/src/Transit/Impl/ReadHandlers/ReadHandlers.cs b/src/Transit/Impl/ReadHandlers/ReadHandlers.cs index 4504971..a948f00 100644 --- a/src/Transit/Impl/ReadHandlers/ReadHandlers.cs +++ b/src/Transit/Impl/ReadHandlers/ReadHandlers.cs @@ -136,12 +136,12 @@ internal sealed class ListReadHandler : IListReadHandler { public object FromRepresentation(object representation) => representation; - public IListReader ListReader() => new LinkedListReader(); + public IListReader ListReader() => new ListWrapperReader(); - private sealed class LinkedListReader : IListReader + private sealed class ListWrapperReader : IListReader { - public object Init() => new LinkedList(); - public object Add(object list, object item) { ((LinkedList)list).AddLast(item); return list; } + public object Init() => new ListWrapper(); + public object Add(object list, object item) { ((ListWrapper)list).Add(item); return list; } public object Complete(object list) => list; } } diff --git a/src/Transit/Impl/WriterFactory.cs b/src/Transit/Impl/WriterFactory.cs index 0bca58e..89d4000 100644 --- a/src/Transit/Impl/WriterFactory.cs +++ b/src/Transit/Impl/WriterFactory.cs @@ -47,6 +47,7 @@ private static FrozenDictionary BuildDefaultHandlers() [typeof(ITaggedValue)] = new TaggedValueWriteHandler(), [typeof(ISet<>)] = new SetWriteHandler(), [typeof(IEnumerable)] = new EnumerableWriteHandler(), + [typeof(ListWrapper)] = new EnumerableWriteHandler(), [typeof(IList<>)] = listHandler, [typeof(IDictionary<,>)] = new DictionaryWriteHandler(), [typeof(NullKeyDictionary)] = new DictionaryWriteHandler(),