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(),