| File: TaskItem_T.cs | Web Access |
| Project: src\msbuild\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// 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; using System.Collections.Generic; using Microsoft.Build.Shared; namespace Microsoft.Build.Framework { /// <summary> /// A strongly-typed wrapper around <see cref="ITaskItem"/> that parses the item's identity /// as a value of type <typeparamref name="T"/>. /// </summary> /// <typeparam name="T">The type to parse the item identity as. Can be a value type, FileInfo, or DirectoryInfo.</typeparam> /// <remarks> /// This allows tasks to receive strongly-typed parameters while still working with MSBuild's item system. /// The identity (ItemSpec) is parsed using <see cref="ValueTypeParser"/> which handles value types, /// AbsolutePath, FileInfo, and DirectoryInfo. /// </remarks> public readonly struct TaskItem<T> : ITaskItem<T>, IEquatable<TaskItem<T>> { private readonly ITaskItem? _backingItem; /// <summary> /// Gets the strongly-typed value parsed from the item's identity. /// </summary> public T Value { get; } /// <summary> /// Initializes a new instance of <see cref="TaskItem{T}"/> from a strongly-typed value. /// </summary> /// <param name="value">The value to wrap.</param> public TaskItem(T value) { Value = value; // Use a mutable backing so metadata mutations are supported string itemSpec = ValueTypeParser.ToString(value); _backingItem = new MutableTaskItem(itemSpec); } /// <summary> /// Initializes a new instance of <see cref="TaskItem{T}"/> from an <see cref="ITaskItem"/>. /// </summary> /// <param name="item">The task item to parse.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="item"/> is null.</exception> /// <exception cref="ArgumentException">Thrown if the item's identity cannot be parsed as type <typeparamref name="T"/>.</exception> public TaskItem(ITaskItem item) { if (item == null) { throw new ArgumentNullException(nameof(item)); } _backingItem = item; string itemSpec = item.ItemSpec; if (string.IsNullOrEmpty(itemSpec)) { throw new ArgumentException($"Cannot create TaskItem<{typeof(T).Name}> from an item with empty identity.", nameof(item)); } // For path-like types, prefer the FullPath metadata (which is always absolute) // over ItemSpec which may be relative. string parseFrom = TaskItemTypeDetector.IsSupportedPathType(typeof(T)) ? GetFullPathOrItemSpec(item, itemSpec) : itemSpec; Value = ParseValue(parseFrom); } /// <summary>Returns the FullPath metadata value if non-empty, otherwise falls back to itemSpec.</summary> private static string GetFullPathOrItemSpec(ITaskItem item, string itemSpec) { string fullPath = item.GetMetadata("FullPath"); return !string.IsNullOrEmpty(fullPath) ? fullPath : itemSpec; } /// <summary> /// Parses a string value as type <typeparamref name="T"/>. /// Uses the unified ValueTypeParser for consistent parsing behavior across MSBuild. /// </summary> private static T ParseValue(string value) => (T)ValueTypeParser.Parse(value, typeof(T)); /// <summary> /// Converts the strongly-typed value to its string representation for use as ItemSpec. /// Uses the unified ValueTypeParser for consistent formatting behavior across MSBuild. /// </summary> private string ValueToString() => ValueTypeParser.ToString(Value); #region ITaskItem Implementation /// <inheritdoc/> public string? ItemSpec { get => _backingItem?.ItemSpec; set => throw new NotSupportedException("TaskItem<T> ItemSpec is read-only. Create a new instance to change the value."); } /// <inheritdoc/> public ICollection? MetadataNames => _backingItem?.MetadataNames; /// <inheritdoc/> public int MetadataCount => _backingItem?.MetadataCount ?? 0; /// <inheritdoc/> public string? GetMetadata(string metadataName) => _backingItem?.GetMetadata(metadataName); /// <inheritdoc/> public void SetMetadata(string metadataName, string metadataValue) => _backingItem?.SetMetadata(metadataName, metadataValue); /// <inheritdoc/> public void RemoveMetadata(string metadataName) => _backingItem?.RemoveMetadata(metadataName); /// <inheritdoc/> public void CopyMetadataTo(ITaskItem destinationItem) => _backingItem?.CopyMetadataTo(destinationItem); /// <inheritdoc/> public IDictionary? CloneCustomMetadata() => _backingItem?.CloneCustomMetadata(); #endregion #region ITaskItem2 Implementation /// <inheritdoc/> public string? EvaluatedIncludeEscaped { get => (_backingItem as ITaskItem2)?.EvaluatedIncludeEscaped ?? ItemSpec; set => throw new NotSupportedException("TaskItem<T> EvaluatedIncludeEscaped is read-only. Create a new instance to change the value."); } /// <inheritdoc/> public string GetMetadataValueEscaped(string metadataName) { return (_backingItem as ITaskItem2)?.GetMetadataValueEscaped(metadataName) ?? string.Empty; } /// <inheritdoc/> public void SetMetadataValueLiteral(string metadataName, string metadataValue) { if (_backingItem is ITaskItem2 taskItem2) { taskItem2.SetMetadataValueLiteral(metadataName, metadataValue); } else if (_backingItem is ITaskItem taskItem) { var escapedValue = EscapingUtilities.Escape(metadataValue); taskItem.SetMetadata(metadataName, escapedValue); } } /// <inheritdoc/> public IDictionary CloneCustomMetadataEscaped() { return (_backingItem as ITaskItem2)?.CloneCustomMetadataEscaped() ?? new Hashtable(); } #endregion #region Equality and Conversion /// <inheritdoc/> public bool Equals(TaskItem<T> other) => EqualityComparer<T>.Default.Equals(Value, other.Value); /// <inheritdoc/> public override bool Equals(object? obj) => obj is TaskItem<T> other && Equals(other); /// <inheritdoc/> public override int GetHashCode() => Value is null ? 0 : EqualityComparer<T>.Default.GetHashCode(Value); /// <summary> /// Determines whether two <see cref="TaskItem{T}"/> instances are equal. /// </summary> public static bool operator ==(TaskItem<T> left, TaskItem<T> right) => left.Equals(right); /// <summary> /// Determines whether two <see cref="TaskItem{T}"/> instances are not equal. /// </summary> public static bool operator !=(TaskItem<T> left, TaskItem<T> right) => !left.Equals(right); /// <summary> /// Implicitly converts a <see cref="TaskItem{T}"/> to its strongly-typed value. /// </summary> public static implicit operator T(TaskItem<T> taskItem) => taskItem.Value; /// <summary> /// Implicitly converts a strongly-typed value to a <see cref="TaskItem{T}"/>. /// </summary> public static implicit operator TaskItem<T>(T value) => new TaskItem<T>(value); #endregion /// <inheritdoc/> public override string ToString() => ValueToString(); } }