File: TaskHostParameters.cs
Web Access
Project: ..\..\..\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.Generic;
 
namespace Microsoft.Build.Framework
{
    /// <summary>
    /// A readonly struct that represents task host parameters used to determine which host process to launch.
    /// </summary>
    public readonly struct TaskHostParameters : IEquatable<TaskHostParameters>
    {
        /// <summary>
        /// A static empty instance to avoid allocations when default parameters are needed.
        /// </summary>
        public static readonly TaskHostParameters Empty = new();
 
        private readonly string? _runtime;
        private readonly string? _architecture;
        private readonly string? _dotnetHostPath;
        private readonly string? _msBuildAssemblyPath;
        private readonly bool? _taskHostFactoryExplicitlyRequested;
 
        /// <summary>
        /// Initializes a new instance of the TaskHostParameters struct with the specified parameters.
        /// </summary>
        /// <param name="runtime">The target runtime identifier (e.g., "net8.0", "net472").</param>
        /// <param name="architecture">The target architecture (e.g., "x64", "x86", "arm64").</param>
        /// <param name="dotnetHostPath">The path to the dotnet host executable.</param>
        /// <param name="msBuildAssemblyPath">The path to the MSBuild assembly.</param>
        /// <param name="taskHostFactoryExplicitlyRequested">Defines if Task Host Factory was explicitly requested.</param>
        internal TaskHostParameters(
            string? runtime = null,
            string? architecture = null,
            string? dotnetHostPath = null,
            string? msBuildAssemblyPath = null,
            bool? taskHostFactoryExplicitlyRequested = null)
        {
            _runtime = runtime;
            _architecture = architecture;
            _dotnetHostPath = dotnetHostPath;
            _msBuildAssemblyPath = msBuildAssemblyPath;
            _taskHostFactoryExplicitlyRequested = taskHostFactoryExplicitlyRequested;
        }
 
        /// <summary>
        /// Gets the target runtime identifier (e.g., "net8.0", "net472").
        /// </summary>
        /// <value>The runtime identifier, or null if not specified.</value>
        public string? Runtime => _runtime;
 
        /// <summary>
        /// Gets the target architecture (e.g., "x64", "x86", "arm64").
        /// </summary>
        /// <value>The architecture identifier, or an empty string if not specified.</value>
        public string? Architecture => _architecture;
 
        /// <summary>
        /// Gets the path to the dotnet host executable.
        /// </summary>
        /// <value>The dotnet host path, or null if not specified.</value>
        public string? DotnetHostPath => _dotnetHostPath;
 
        /// <summary>
        /// Gets the path to the MSBuild assembly.
        /// </summary>
        /// <value>The MSBuild assembly path, or null if not specified.</value>
        public string? MSBuildAssemblyPath => _msBuildAssemblyPath;
 
        /// <summary>
        /// Gets if Task Host Factory was requested explicitly (by specifying TaskHost="TaskHostFactory" in UsingTask element).
        /// </summary>
        public bool? TaskHostFactoryExplicitlyRequested => _taskHostFactoryExplicitlyRequested;
 
        public override bool Equals(object? obj) => obj is TaskHostParameters other && Equals(other);
 
        public bool Equals(TaskHostParameters other) =>
            StringComparer.OrdinalIgnoreCase.Equals(Runtime ?? string.Empty, other.Runtime ?? string.Empty)
            && StringComparer.OrdinalIgnoreCase.Equals(Architecture ?? string.Empty, other.Architecture ?? string.Empty)
            && TaskHostFactoryExplicitlyRequested == other.TaskHostFactoryExplicitlyRequested;
 
        public override int GetHashCode()
        {
            // Manual hash code implementation for compatibility with .NET Framework 4.7.2
            var comparer = StringComparer.OrdinalIgnoreCase;
 
            unchecked
            {
                int hash = 17;
                hash = hash * 31 + comparer.GetHashCode(Runtime ?? string.Empty);
                hash = hash * 31 + comparer.GetHashCode(Architecture ?? string.Empty);
                hash = hash * 31 + (TaskHostFactoryExplicitlyRequested?.GetHashCode() ?? 0);
 
                return hash;
            }
        }
 
        /// <summary>
        /// Gets a value indicating whether returns true if parameters were unset.
        /// </summary>
        internal bool IsEmpty => Equals(Empty);
 
        /// <summary>
        /// Merges two TaskHostParameters instances, with the second parameter values taking precedence when both are specified.
        /// </summary>
        /// <param name="baseParameters">The base parameters.</param>
        /// <param name="overrideParameters">The override parameters that take precedence.</param>
        /// <returns>A new TaskHostParameters with merged values.</returns>
        internal static TaskHostParameters MergeTaskHostParameters(TaskHostParameters baseParameters, TaskHostParameters overrideParameters)
        {
            // If both are empty, return empty
            if (baseParameters.IsEmpty && overrideParameters.IsEmpty)
            {
                return Empty;
            }
 
            // If override is empty, return base
            if (overrideParameters.IsEmpty)
            {
                return baseParameters;
            }
 
            // If base is empty, return override
            if (baseParameters.IsEmpty)
            {
                return overrideParameters;
            }
 
            // Merge: override values take precedence, fall back to base values
            return new TaskHostParameters(
                runtime: overrideParameters.Runtime ?? baseParameters.Runtime,
                architecture: overrideParameters.Architecture ?? baseParameters.Architecture,
                dotnetHostPath: overrideParameters.DotnetHostPath ?? baseParameters.DotnetHostPath,
                msBuildAssemblyPath: overrideParameters.MSBuildAssemblyPath ?? baseParameters.MSBuildAssemblyPath,
                taskHostFactoryExplicitlyRequested: overrideParameters.TaskHostFactoryExplicitlyRequested ?? baseParameters.TaskHostFactoryExplicitlyRequested);
        }
 
        /// <summary>
        /// Creates a new instance of <see cref="TaskHostParameters"/> with the specified value for the
        /// <see cref="TaskHostFactoryExplicitlyRequested"/> property.
        /// </summary>
        internal TaskHostParameters WithTaskHostFactoryExplicitlyRequested(bool taskHostFactoryExplicitlyRequested)
        {
            if (_taskHostFactoryExplicitlyRequested == taskHostFactoryExplicitlyRequested)
            {
                return this;
            }
 
            return new TaskHostParameters(
                runtime: _runtime,
                architecture: _architecture,
                dotnetHostPath: _dotnetHostPath,
                msBuildAssemblyPath: _msBuildAssemblyPath,
                taskHostFactoryExplicitlyRequested: taskHostFactoryExplicitlyRequested);
        }
 
        /// <summary>
        /// The method was added to sustain compatibility with ITaskFactory2 factoryIdentityParameters parameters dictionary.
        /// </summary>
        internal Dictionary<string, string> ToDictionary() => new(3, StringComparer.OrdinalIgnoreCase)
        {
            { nameof(Runtime), Runtime ?? string.Empty },
            { nameof(Architecture), Architecture ?? string.Empty },
            { nameof(TaskHostFactoryExplicitlyRequested), TaskHostFactoryExplicitlyRequested?.ToString() ?? string.Empty },
        };
    }
}