| File: Collections\LookasideStringInterner.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using Microsoft.Build.BackEnd; #nullable disable namespace Microsoft.Build.Collections { /// <summary> /// A simple string interner designed for IPC. /// </summary> /// <remarks> /// This interner works by providing a way to convert strings to integer IDs. When used as a form of compression, /// clients will intern their strings and record the set of IDs returned, then transmit those IDs instead of the /// original strings. The interner itself is also transmitted ahead of time, with the IDs, allowing /// reconstruction of the original strings. This ensures each string is transmitted exactly once. /// </remarks> internal class LookasideStringInterner : ITranslatable { /// <summary> /// Index used for null strings. /// </summary> private const int NullStringIndex = -1; /// <summary> /// Index used for empty strings. /// </summary> private const int EmptyStringIndex = -2; /// <summary> /// The map used to intern strings for serialization. This map doesn't exist when the strings /// are deserialized (it is not needed.) /// </summary> private readonly Dictionary<string, int> _stringToIdsMap; /// <summary> /// The list of strings by ID. /// </summary> private List<string> _strings; /// <summary> /// Constructor to be used during serialization. /// </summary> public LookasideStringInterner(StringComparer comparer, int defaultCollectionSize) { _stringToIdsMap = new Dictionary<string, int>(defaultCollectionSize, comparer); _strings = new List<string>(defaultCollectionSize); } /// <summary> /// Constructor to be used during deserialization. /// </summary> /// <remarks> /// Intern cannot be used on this interner if it came from serialization, since we do /// not reconstruct the interning dictionary. /// </remarks> public LookasideStringInterner(ITranslator translator) { Translate(translator); } /// <summary> /// Interns the specified string. /// </summary> /// <param name="str">The string to intern.</param> /// <returns>The index representing the string.</returns> public int Intern(string str) { if (str == null) { return NullStringIndex; } else if (str.Length == 0) { return EmptyStringIndex; } else { // If stringToIdsMap is null here, it means we probably tried to intern a string to an interner which came from // deserialization (and thus doesn't support further interning for efficiency reasons.) No VerifyThrow here // because this function is called a lot. if (!_stringToIdsMap.TryGetValue(str, out int index)) { index = _strings.Count; // This will be the index of the string we are about to add. _stringToIdsMap.Add(str, index); _strings.Add(str); } return index; } } /// <summary> /// Retrieves a string corresponding to the provided index. /// </summary> /// <param name="index">The index.</param> /// <returns>The corresponding string.</returns> public string GetString(int index) { return index switch { NullStringIndex => null, EmptyStringIndex => String.Empty, _ => _strings[index], }; } /// <summary> /// The translator, for serialization. /// </summary> public void Translate(ITranslator translator) { translator.Translate(ref _strings); } } }