From dc08a60f5edebdf3d6048afb1b147de776661562 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 14 Apr 2019 16:09:17 +0100 Subject: [PATCH] Lazy allocate dictionary in ItemsDictionary (#9284) --- ...Microsoft.AspNetCore.Http.netcoreapp3.0.cs | 2 +- src/Http/Http/src/Internal/ItemsDictionary.cs | 128 ++++++++++++------ 2 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs b/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs index c2ac47f401..4070311a3b 100644 --- a/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs +++ b/src/Http/Http/ref/Microsoft.AspNetCore.Http.netcoreapp3.0.cs @@ -406,7 +406,7 @@ namespace Microsoft.AspNetCore.Http.Internal { public ItemsDictionary() { } public ItemsDictionary(System.Collections.Generic.IDictionary items) { } - public System.Collections.Generic.IDictionary Items { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.Generic.IDictionary Items { get { throw null; } } int System.Collections.Generic.ICollection>.Count { get { throw null; } } bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } object System.Collections.Generic.IDictionary.this[object key] { get { throw null; } set { } } diff --git a/src/Http/Http/src/Internal/ItemsDictionary.cs b/src/Http/Http/src/Internal/ItemsDictionary.cs index 4821912240..7f5f458ba1 100644 --- a/src/Http/Http/src/Internal/ItemsDictionary.cs +++ b/src/Http/Http/src/Internal/ItemsDictionary.cs @@ -8,111 +8,155 @@ namespace Microsoft.AspNetCore.Http.Internal { public class ItemsDictionary : IDictionary { + private IDictionary _items; + public ItemsDictionary() - : this(new Dictionary()) - { - } + {} public ItemsDictionary(IDictionary items) { - Items = items; + _items = items; } - public IDictionary Items { get; } + public IDictionary Items => this; // Replace the indexer with one that returns null for missing values object IDictionary.this[object key] { get { - object value; - if (Items.TryGetValue(key, out value)) + if (_items != null && _items.TryGetValue(key, out var value)) { return value; } return null; } - set { Items[key] = value; } + set + { + EnsureDictionary(); + _items[key] = value; + } } void IDictionary.Add(object key, object value) { - Items.Add(key, value); + EnsureDictionary(); + _items.Add(key, value); } bool IDictionary.ContainsKey(object key) - { - return Items.ContainsKey(key); - } + => _items != null && _items.ContainsKey(key); ICollection IDictionary.Keys { - get { return Items.Keys; } + get + { + if (_items == null) + { + return EmptyDictionary.Dictionary.Keys; + } + + return _items.Keys; + } } bool IDictionary.Remove(object key) - { - return Items.Remove(key); - } + => _items != null && _items.Remove(key); bool IDictionary.TryGetValue(object key, out object value) { - return Items.TryGetValue(key, out value); + value = null; + return _items != null && _items.TryGetValue(key, out value); } ICollection IDictionary.Values { - get { return Items.Values; } + get + { + if (_items == null) + { + return EmptyDictionary.Dictionary.Values; + } + + return _items.Values; + } } void ICollection>.Add(KeyValuePair item) { - Items.Add(item); + EnsureDictionary(); + _items.Add(item); } - void ICollection>.Clear() - { - Items.Clear(); - } + void ICollection>.Clear() => _items?.Clear(); bool ICollection>.Contains(KeyValuePair item) - { - return Items.Contains(item); - } + => _items != null && _items.Contains(item); void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { - Items.CopyTo(array, arrayIndex); + if (_items == null) + { + //Delegate to Empty Dictionary to do the argument checking. + EmptyDictionary.Collection.CopyTo(array, arrayIndex); + } + + _items.CopyTo(array, arrayIndex); } - int ICollection>.Count - { - get { return Items.Count; } - } + int ICollection>.Count => _items?.Count ?? 0; - bool ICollection>.IsReadOnly - { - get { return Items.IsReadOnly; } - } + bool ICollection>.IsReadOnly => _items?.IsReadOnly ?? false; bool ICollection>.Remove(KeyValuePair item) { - object value; - if (Items.TryGetValue(item.Key, out value) && Equals(item.Value, value)) + if (_items == null) { - return Items.Remove(item.Key); + return false; + } + + if (_items.TryGetValue(item.Key, out var value) && Equals(item.Value, value)) + { + return _items.Remove(item.Key); } return false; } - IEnumerator> IEnumerable>.GetEnumerator() + private void EnsureDictionary() { - return Items.GetEnumerator(); + if (_items == null) + { + _items = new Dictionary(); + } } - IEnumerator IEnumerable.GetEnumerator() + IEnumerator> IEnumerable>.GetEnumerator() + => _items?.GetEnumerator() ?? EmptyEnumerator.Instance; + + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator() ?? EmptyEnumerator.Instance; + + private class EmptyEnumerator : IEnumerator> { - return Items.GetEnumerator(); + // In own class so only initalized if GetEnumerator is called on an empty ItemsDictionary + public readonly static IEnumerator> Instance = new EmptyEnumerator(); + public KeyValuePair Current => default; + + object IEnumerator.Current => null; + + public void Dispose() + { } + + public bool MoveNext() => false; + + public void Reset() + { } + } + + private static class EmptyDictionary + { + // In own class so only initalized if CopyTo is called on an empty ItemsDictionary + public readonly static IDictionary Dictionary = new Dictionary(); + public static ICollection> Collection => Dictionary; } } } \ No newline at end of file