| File: Evaluation\Expander.SpanBasedConcatenator.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 Microsoft.Build.Framework; using Microsoft.NET.StringTools; #nullable disable namespace Microsoft.Build.Evaluation; internal partial class Expander<P, I> where P : class, IProperty where I : class, IItem { /// <summary> /// A helper struct wrapping a <see cref="SpanBasedStringBuilder"/> and providing file path conversion /// as used in e.g. property expansion. /// </summary> /// <remarks> /// If exactly one value is added and no concatenation takes places, this value is returned without /// conversion. In other cases values are stringified and attempted to be interpreted as file paths /// before concatenation. /// </remarks> private struct SpanBasedConcatenator : IDisposable { /// <summary> /// The backing <see cref="SpanBasedStringBuilder"/>, null until the second value is added. /// </summary> private SpanBasedStringBuilder _builder; /// <summary> /// The first value added to the concatenator. Tracked in its own field so it can be returned /// without conversion if no concatenation takes place. /// </summary> private object _firstObject; /// <summary> /// The first value added to the concatenator if it is a span. Tracked in its own field so the /// <see cref="SpanBasedStringBuilder"/> functionality doesn't have to be invoked if no concatenation /// takes place. /// </summary> private ReadOnlyMemory<char> _firstSpan; /// <summary> /// True if this instance is already disposed. /// </summary> private bool _disposed; /// <summary> /// Adds an object to be concatenated. /// </summary> public void Add(object obj) { CheckDisposed(); FlushFirstValueIfNeeded(); if (_builder != null) { _builder.Append(FileUtilities.MaybeAdjustFilePath(obj.ToString())); } else { _firstObject = obj; } } /// <summary> /// Adds a span to be concatenated. /// </summary> public void Add(ReadOnlyMemory<char> span) { CheckDisposed(); FlushFirstValueIfNeeded(); if (_builder != null) { _builder.Append(FileUtilities.MaybeAdjustFilePath(span)); } else { _firstSpan = span; } } /// <summary> /// Returns the result of the concatenation. /// </summary> /// <returns> /// If only one value has been added and it is not a string, it is returned unchanged. /// In all other cases (no value, one string value, multiple values) the result is a /// concatenation of the string representation of the values, each additionally subjected /// to file path adjustment. /// </returns> public readonly object GetResult() { CheckDisposed(); if (_firstObject != null) { return (_firstObject is string stringValue) ? FileUtilities.MaybeAdjustFilePath(stringValue) : _firstObject; } return _firstSpan.IsEmpty ? _builder?.ToString() ?? string.Empty : FileUtilities.MaybeAdjustFilePath(_firstSpan).ToString(); } /// <summary> /// Disposes of the struct by delegating the call to the underlying <see cref="SpanBasedStringBuilder"/>. /// </summary> public void Dispose() { CheckDisposed(); _builder?.Dispose(); _disposed = true; } /// <summary> /// Throws <see cref="ObjectDisposedException"/> if this concatenator is already disposed. /// </summary> private readonly void CheckDisposed() => ObjectDisposedException.ThrowIf(_disposed, typeof(SpanBasedConcatenator)); /// <summary> /// Lazily initializes <see cref="_builder"/> and populates it with the first value /// when the second value is being added. /// </summary> private void FlushFirstValueIfNeeded() { if (_firstObject != null) { _builder = Strings.GetSpanBasedStringBuilder(); _builder.Append(FileUtilities.MaybeAdjustFilePath(_firstObject.ToString())); _firstObject = null; } else if (!_firstSpan.IsEmpty) { _builder = Strings.GetSpanBasedStringBuilder(); #if FEATURE_SPAN _builder.Append(FileUtilities.MaybeAdjustFilePath(_firstSpan)); #else _builder.Append(FileUtilities.MaybeAdjustFilePath(_firstSpan.ToString())); #endif _firstSpan = new ReadOnlyMemory<char>(); } } } }