| File: TaskParameterTypeRegistry.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.Concurrent; using System.Diagnostics.CodeAnalysis; namespace Microsoft.Build.Framework; /// <summary> /// A statically-known, reflection-free map from a task parameter type name (the <c>ParameterType</c> /// string of a <c><UsingTask></c> <c><ParameterGroup></c> parameter) to its <see cref="Type"/>. /// </summary> /// <remarks> /// <para> /// MSBuild restricts task parameter types to a small, well-defined set (see /// <c>TaskParameterTypeVerifier</c>): any value type, <see cref="string"/>, and the /// <see cref="ITaskItem"/> family - each also allowed as an array. Resolving such a type from its /// declared name historically used <see cref="Type.GetType(string)"/>, which is incompatible with /// trimming and Native AOT (the type cannot be determined statically, so the trimmer cannot know to /// preserve it). /// </para> /// <para> /// This registry replaces that lookup for the common, product-known types: the intrinsic value types, /// <see cref="string"/>, and the MSBuild <see cref="ITaskItem"/> types are pre-registered here, so they /// resolve with no reflection in a trimmed/AOT image. Registered <em>value</em> types are member-rooted /// for trimming (a value parameter may be converted from its string form) via the /// <c>[DynamicallyAccessedMembers]</c> on <see cref="RegisterValueType{T}"/>; <em>item</em> types are /// validated by assignability only and are never member-reflected, so they need no member rooting. A host /// that uses additional task parameter types can register them at startup through /// <c>Microsoft.Build.Utilities.TaskItem.RegisterTaskParameterValueType</c> and /// <c>RegisterTaskParameterItemType</c>, which forward here. /// </para> /// <para> /// Names that the registry does not know fall back to <see cref="Type.GetType(string)"/> only when the /// <c>Microsoft.Build.EnableReflectiveTaskParameterTypes</c> feature switch is enabled (the JIT default); /// in a trimmed/AOT application that switch is substituted <see langword="false"/> and an unknown name /// fails observably instead. This type lives in Microsoft.Build.Framework so both the engine /// (Microsoft.Build) and the public registration surface (Microsoft.Build.Utilities) can reach it. /// </para> /// </remarks> internal static class TaskParameterTypeRegistry { /// <summary> /// Maps a type's <see cref="Type.FullName"/> (for example <c>System.Int32</c>, <c>System.Int32[]</c>, /// or <c>Microsoft.Build.Framework.ITaskItem</c>) to the type. Keyed case-insensitively to mirror the /// case-insensitive fallback the by-name <see cref="Type.GetType(string)"/> resolution used. /// </summary> private static readonly ConcurrentDictionary<string, Type> s_typesByName = new(StringComparer.OrdinalIgnoreCase); static TaskParameterTypeRegistry() { // string is the single most common task parameter type. It is neither a value type nor an // ITaskItem, so it is registered directly; the BCL already preserves the members it needs. Add(typeof(string)); Add(typeof(string[])); // The intrinsic value types MSBuild accepts as task parameter types. Each call also roots the // type (and its array form) for trimming via the [DynamicallyAccessedMembers] on RegisterValueType. RegisterValueType<bool>(); RegisterValueType<byte>(); RegisterValueType<sbyte>(); RegisterValueType<char>(); RegisterValueType<short>(); RegisterValueType<ushort>(); RegisterValueType<int>(); RegisterValueType<uint>(); RegisterValueType<long>(); RegisterValueType<ulong>(); RegisterValueType<float>(); RegisterValueType<double>(); RegisterValueType<decimal>(); RegisterValueType<DateTime>(); // The MSBuild ITaskItem types visible from Framework. The concrete item types in higher assemblies // cannot be referenced from here: the engine's internal ProjectItemInstance.TaskItem is registered // from Microsoft.Build, and the public Microsoft.Build.Utilities.TaskItem - a higher-layer type the // engine does not reference - is registered by a host through the public API if it is declared as a // parameter type. (The out-of-proc task host's private TaskParameterTaskItem is never declared, so // it is not registered.) RegisterTaskItemType<ITaskItem>(); RegisterTaskItemType<ITaskItem2>(); RegisterTaskItemType<TaskItemData>(); } /// <summary> /// Registers a value type (and its array form) as a resolvable task parameter type, rooting it for /// trimming so it can be resolved with no reflection in a trimmed/AOT image. /// </summary> /// <typeparam name="T">The value type to register. Enums and user-defined structs are permitted.</typeparam> internal static void RegisterValueType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>() where T : struct { Add(typeof(T)); Add(typeof(T[])); } /// <summary> /// Registers an <see cref="ITaskItem"/> type (and its array form) as a resolvable task parameter type. /// Item-typed parameters are validated by assignability and are never member-reflected through the /// registry, so only the type reference is needed (no <c>[DynamicallyAccessedMembers]</c> rooting) and /// resolution stays reflection-free in a trimmed/AOT image. /// </summary> /// <typeparam name="T">The <see cref="ITaskItem"/> type to register.</typeparam> internal static void RegisterTaskItemType<T>() where T : ITaskItem { Add(typeof(T)); Add(typeof(T[])); } /// <summary> /// Looks up a task parameter type by its declared name, with no reflection. /// </summary> /// <param name="typeName">The expanded <c>ParameterType</c> name, for example <c>System.String</c>.</param> /// <returns>The registered <see cref="Type"/>, or <see langword="null"/> if no type is registered under that name.</returns> internal static Type? TryGetType(string typeName) => s_typesByName.TryGetValue(typeName, out Type? type) ? type : null; private static void Add(Type type) { string? fullName = type.FullName; if (fullName is not null) { s_typesByName[fullName] = type; } } }