| File: Model\KeyValueCollection.cs | Web Access |
| Project: Microsoft.NET.ProjectData.csproj (Microsoft.NET.ProjectData) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Immutable; using System.Diagnostics; namespace Microsoft.NET.ProjectData; /// <summary> /// Optimized key-value collection that exploits the regular key structure of /// project data. Keys are defined by a shared <see cref="KeySchema"/>, and values /// are stored as a flat array of strings indexed by the schema. This avoids /// dictionary overhead for what is essentially a fixed set of known keys. /// </summary> public readonly struct KeyValueCollection { private readonly KeySchema schema; private readonly ImmutableArray<string?> values; public KeyValueCollection(KeySchema schema, ImmutableArray<string?> values) { Debug.Assert(schema is not null, "KeyValueCollection must be constructed with a non-null schema."); Debug.Assert(!values.IsDefault, "KeyValueCollection must be constructed with a non-default values array."); Debug.Assert(values.Length <= schema.Count, $"Values array length ({values.Length}) exceeds schema key count ({schema.Count})."); this.schema = schema; this.values = values; } public static KeyValueCollection Empty { get; } = new(new KeySchema([]), []); /// <summary> /// Gets the schema that defines the key layout. /// </summary> public KeySchema Schema => this.schema; /// <summary> /// Gets whether this collection has no values (was constructed with an empty values array). /// </summary> public bool IsEmpty => this.values.IsEmpty; /// <summary> /// Tries to get the value for a given key. /// Returns <see langword="true"/> if the key exists and has a non-null value. /// </summary> public bool TryGetValue(string key, out string? value) { Debug.Assert(this.schema is not null, "KeyValueCollection used before initialization."); if (this.schema.TryGetIndex(key, out int index) && index < this.values.Length) { value = this.values[index]; return value is not null; } value = null; return false; } /// <summary> /// Gets the value for a given key, or <see langword="null"/> if not present. /// </summary> public string? this[string key] { get { this.TryGetValue(key, out string? value); return value; } } /// <summary> /// Enumerates all non-null key-value pairs. /// </summary> public Enumerator GetEnumerator() => new(this); /// <summary> /// Converts to a dictionary for serialization across process boundaries. /// Only includes non-null values. /// </summary> public Dictionary<string, string> ToDictionary() { Debug.Assert(this.schema is not null, "KeyValueCollection used before initialization."); Dictionary<string, string> dict = new(StringComparers.PropertyName); if (!this.values.IsEmpty) { for (int i = 0; i < this.values.Length; i++) { if (this.values[i] is string value) { dict[this.schema.GetKey(i)] = value; } } } return dict; } public struct Enumerator { private readonly KeyValueCollection collection; private int index; internal Enumerator(KeyValueCollection collection) { this.collection = collection; this.index = -1; } public KeyValuePair<string, string> Current { get { string key = this.collection.schema.GetKey(this.index); string value = this.collection.values[this.index]!; return new(key, value); } } public bool MoveNext() { if (this.collection.values.IsEmpty) return false; // values.Length <= schema.Count is enforced by the constructor. while (++this.index < this.collection.values.Length) { if (this.collection.values[this.index] is not null) return true; } return false; } } }