diff --git a/src/Build/Instance/TaskRegistry.cs b/src/Build/Instance/TaskRegistry.cs
index 89d96753b1d..8b9db196161 100644
--- a/src/Build/Instance/TaskRegistry.cs
+++ b/src/Build/Instance/TaskRegistry.cs
@@ -950,18 +950,16 @@ private static bool IdentityParametersMatch(in TaskHostParameters x, in TaskHost
return false;
}
- // For exact match, all properties must be equal (case-insensitive)
+ // For exact match, only identity properties must be equal
+ // Paths are NOT part of identity - they're just resolved locations
return string.Equals(x.Runtime, y.Runtime, StringComparison.OrdinalIgnoreCase) &&
string.Equals(x.Architecture, y.Architecture, StringComparison.OrdinalIgnoreCase) &&
- string.Equals(x.DotnetHostPath, y.DotnetHostPath, StringComparison.OrdinalIgnoreCase) &&
- string.Equals(x.MSBuildAssemblyPath, y.MSBuildAssemblyPath, StringComparison.OrdinalIgnoreCase) &&
x.TaskHostFactoryExplicitlyRequested == y.TaskHostFactoryExplicitlyRequested;
}
else
{
// Fuzzy match: null is treated as "don't care"
// Only check runtime and architecture for fuzzy matching
-
string runtimeX = x.Runtime;
string runtimeY = y.Runtime;
string architectureX = x.Architecture;
diff --git a/src/Framework/TaskHostParameters.cs b/src/Framework/TaskHostParameters.cs
index 73b78e38182..97ee9223a96 100644
--- a/src/Framework/TaskHostParameters.cs
+++ b/src/Framework/TaskHostParameters.cs
@@ -1,6 +1,7 @@
// 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
@@ -8,7 +9,7 @@ namespace Microsoft.Build.Framework
///
/// A readonly struct that represents task host parameters used to determine which host process to launch.
///
- public readonly struct TaskHostParameters
+ public readonly struct TaskHostParameters : IEquatable
{
///
/// A static empty instance to avoid allocations when default parameters are needed.
@@ -72,6 +73,29 @@ internal TaskHostParameters(
///
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;
+ }
+ }
+
///
/// Gets a value indicating whether returns true if parameters were unset.
///
@@ -134,7 +158,7 @@ internal TaskHostParameters WithTaskHostFactoryExplicitlyRequested(bool taskHost
///
/// The method was added to sustain compatibility with ITaskFactory2 factoryIdentityParameters parameters dictionary.
///
- internal Dictionary ToDictionary() => new(3)
+ internal Dictionary ToDictionary() => new(3, StringComparer.OrdinalIgnoreCase)
{
{ nameof(Runtime), Runtime ?? string.Empty },
{ nameof(Architecture), Architecture ?? string.Empty },