/
ActivityTagsCollection.cs
296 lines (260 loc) · 11.2 KB
/
ActivityTagsCollection.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
namespace System.Diagnostics
{
/// <summary>
/// ActivityTagsCollection is a collection class used to store tracing tags.
/// This collection will be used with classes like <see cref="ActivityEvent"/> and <see cref="ActivityLink"/>.
/// This collection behaves as follows:
/// - The collection items will be ordered according to how they are added.
/// - Don't allow duplication of items with the same key.
/// - When using the indexer to store an item in the collection:
/// - If the item has a key that previously existed in the collection and the value is null, the collection item matching the key will be removed from the collection.
/// - If the item has a key that previously existed in the collection and the value is not null, the new item value will replace the old value stored in the collection.
/// - Otherwise, the item will be added to the collection.
/// - Add method will add a new item to the collection if an item doesn't already exist with the same key. Otherwise, it will throw an exception.
/// </summary>
public class ActivityTagsCollection : IDictionary<string, object?>
{
private List<KeyValuePair<string, object?>> _list = new List<KeyValuePair<string, object?>>();
/// <summary>
/// Create a new instance of the collection.
/// </summary>
public ActivityTagsCollection()
{
}
/// <summary>
/// Create a new instance of the collection and store the input list items in the collection.
/// </summary>
/// <param name="list">Initial list to store in the collection.</param>
public ActivityTagsCollection(IEnumerable<KeyValuePair<string, object?>> list)
{
if (list is null)
{
throw new ArgumentNullException(nameof(list));
}
foreach (KeyValuePair<string, object?> kvp in list)
{
if (kvp.Key != null)
{
this[kvp.Key] = kvp.Value;
}
}
}
/// <summary>
/// Get or set collection item
/// When setting a value to this indexer property, the following behavior will be observed:
/// - If the key previously existed in the collection and the value is null, the collection item matching the key will get removed from the collection.
/// - If the key previously existed in the collection and the value is not null, the value will replace the old value stored in the collection.
/// - Otherwise, a new item will get added to the collection.
/// </summary>
/// <value>Object mapped to the key</value>
public object? this[string key]
{
get
{
int index = FindIndex(key);
return index < 0 ? null : _list[index].Value;
}
set
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
int index = FindIndex(key);
if (value == null)
{
if (index >= 0)
{
_list.RemoveAt(index);
}
return;
}
if (index >= 0)
{
_list[index] = new KeyValuePair<string, object?>(key, value);
}
else
{
_list.Add(new KeyValuePair<string, object?>(key, value));
}
}
}
/// <summary>
/// Get the list of the keys of all stored tags.
/// </summary>
public ICollection<string> Keys
{
get
{
List<string> list = new List<string>(_list.Count);
foreach (KeyValuePair<string, object?> kvp in _list)
{
list.Add(kvp.Key);
}
return list;
}
}
/// <summary>
/// Get the list of the values of all stored tags.
/// </summary>
public ICollection<object?> Values
{
get
{
List<object?> list = new List<object?>(_list.Count);
foreach (KeyValuePair<string, object?> kvp in _list)
{
list.Add(kvp.Value);
}
return list;
}
}
/// <summary>
/// Gets a value indicating whether the collection is read-only.
/// </summary>
public bool IsReadOnly => false;
/// <summary>
/// Gets the number of elements contained in the collection.
/// </summary>
public int Count => _list.Count;
/// <summary>
/// Adds a tag with the provided key and value to the collection.
/// This collection doesn't allow adding two tags with the same key.
/// </summary>
/// <param name="key">The tag key.</param>
/// <param name="value">The tag value.</param>
public void Add(string key, object? value)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}
int index = FindIndex(key);
if (index >= 0)
{
throw new InvalidOperationException(SR.Format(SR.KeyAlreadyExist, key));
}
_list.Add(new KeyValuePair<string, object?>(key, value));
}
/// <summary>
/// Adds an item to the collection
/// </summary>
/// <param name="item">Key and value pair of the tag to add to the collection.</param>
public void Add(KeyValuePair<string, object?> item)
{
if (item.Key == null)
{
throw new ArgumentNullException(nameof(item));
}
int index = FindIndex(item.Key);
if (index >= 0)
{
throw new InvalidOperationException(SR.Format(SR.KeyAlreadyExist, item.Key));
}
_list.Add(item);
}
/// <summary>
/// Removes all items from the collection.
/// </summary>
public void Clear() => _list.Clear();
public bool Contains(KeyValuePair<string, object?> item) => _list.Contains(item);
/// <summary>
/// Determines whether the collection contains an element with the specified key.
/// </summary>
/// <param name="key"></param>
/// <returns>True if the collection contains tag with that key. False otherwise.</returns>
public bool ContainsKey(string key) => FindIndex(key) >= 0;
/// <summary>
/// Copies the elements of the collection to an array, starting at a particular array index.
/// </summary>
/// <param name="array">The array that is the destination of the elements copied from collection.</param>
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
public void CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
IEnumerator<KeyValuePair<string, object?>> IEnumerable<KeyValuePair<string, object?>>.GetEnumerator() => new Enumerator(_list);
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
public Enumerator GetEnumerator() => new Enumerator(_list);
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_list);
/// <summary>
/// Removes the tag with the specified key from the collection.
/// </summary>
/// <param name="key">The tag key</param>
/// <returns>True if the item existed and removed. False otherwise.</returns>
public bool Remove(string key)
{
if (key is null)
{
throw new ArgumentNullException(nameof(key));
}
int index = FindIndex(key);
if (index >= 0)
{
_list.RemoveAt(index);
return true;
}
return false;
}
/// <summary>
/// Removes the first occurrence of a specific item from the collection.
/// </summary>
/// <param name="item">The tag key value pair to remove.</param>
/// <returns>True if item was successfully removed from the collection; otherwise, false. This method also returns false if item is not found in the original collection.</returns>
public bool Remove(KeyValuePair<string, object?> item) => _list.Remove(item);
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">The tag key.</param>
/// <param name="value">The tag value.</param>
/// <returns>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 value parameter. This parameter is passed uninitialized.</returns>
public bool TryGetValue(string key, out object? value)
{
int index = FindIndex(key);
if (index >= 0)
{
value = _list[index].Value;
return true;
}
value = null;
return false;
}
/// <summary>
/// FindIndex finds the index of item in the list having a key matching the input key.
/// We didn't use List.FindIndex to avoid the extra allocation caused by the closure when calling the Predicate delegate.
/// </summary>
/// <param name="key">The key to search the item in the list</param>
/// <returns>The index of the found item, or -1 if the item not found.</returns>
private int FindIndex(string key)
{
for (int i = 0; i < _list.Count; i++)
{
if (_list[i].Key == key)
{
return i;
}
}
return -1;
}
public struct Enumerator : IEnumerator<KeyValuePair<string, object?>>, IEnumerator
{
private List<KeyValuePair<string, object?>>.Enumerator _enumerator;
internal Enumerator(List<KeyValuePair<string, object?>> list) => _enumerator = list.GetEnumerator();
public KeyValuePair<string, object?> Current => _enumerator.Current;
object IEnumerator.Current => ((IEnumerator)_enumerator).Current;
public void Dispose() => _enumerator.Dispose();
public bool MoveNext() => _enumerator.MoveNext();
void IEnumerator.Reset() => ((IEnumerator)_enumerator).Reset();
}
}
}