比较属性和项

MSBuild 属性和项均用于将信息传递到任务、计算条件,以及存储可在整个项目文件中引用的值。

  • 属性是名称/值对。 有关更多信息,请参见 MSBuild 属性

  • 项是一些对象,通常用于表示文件。 项对象可以有关联的元数据集合。 元数据是名称/值对。 有关更多信息,请参见 MSBuild 项

标量和向量

由于 MSBuild 属性是只有一个字符串值的名称/值对,因此通常将这些属性描述为“标量”。 由于 MSBuild 项类型是项列表,因此通常将它们描述为“向量”。 但实际上,属性可表示多个值,而项类型可以有零个或一个项。

目标依赖项注入

要了解属性如何能够表示多个值,请考虑用于将目标添加到要生成的目标列表的常见使用模式。 此列表通常由属性值表示,其目标名称由分号分隔。

<PropertyGroup>
    <BuildDependsOn>
        BeforeBuild;
        CoreBuild;
        AfterBuild
    </BuildDependsOn>
</PropertyGroup>

BuildDependsOn 属性通常用作目标 DependsOnTargets 特性的参数,从而实际上将其转换为项列表。 可以重写此属性,以便添加目标或更改目标执行顺序。 例如,

<PropertyGroup>
    <BuildDependsOn>
        $(BuildDependsOn);
        CustomBuild;
    </BuildDependsOn>
</PropertyGroup>

将 CustomBuild 目标添加到目标列表,并为 BuildDependsOn 指定值 BeforeBuild;CoreBuild;AfterBuild;CustomBuild。

从 MSBuild 4.0 开始,目标依赖项注入已弃用。 请改用 RunAfterTargets 和 RunBeforeTargets 特性。 有关更多信息,请参见目标生成顺序

字符串和项列表之间的转换

MSBuild 将根据需要在项类型和字符串值之间执行转换。 要了解项列表如何能够成为字符串值,请考虑在将项类型用作 MSBuild 属性的值时发生的情况:

<ItemGroup>
    <OutputDir Include="KeyFiles\;Certificates\" />
  </ItemGroup>
<PropertyGroup>
    <OutputDirList>@(OutputDir)</OutputDirList>
</PropertyGroup>

项类型 OutputDir 具有值为“KeyFiles\;Certificates\”的 Include 特性。 MSBuild 将此字符串解析为两个项:KeyFiles\ 和 Certificates\。 将项类型 OutputDir 用作 OutputDirList 属性的值时,MSBuild 会将该项类型转换或“修整”为分号分隔的字符串“KeyFiles\;Certificates\”。

任务中的属性和项

属性和项用作 MSBuild 任务的输入和输出。 有关更多信息,请参见 MSBuild 任务

属性将作为特性传递到任务。 在任务内,MSBuild 属性由一种属性类型表示,该属性类型的值可在字符串之间来回转换。 支持的属性类型包括 boolcharDateTimeDecimalDoubleintstring,以及 ChangeType 可处理的任何类型。

项将作为 ITaskItem 对象传递到任务。 在任务内,ItemSpec 表示项的值,GetMetadata 将检索其元数据。

可以将某种项类型的项列表作为 ITaskItem 对象的数组进行传递。 从 .NET Framework 3.5 开始,可以使用 Remove 特性从目标内的项列表中移除项。 由于可以从项列表中移除项,因此项类型可能具有零个项。 如果将项列表传递到任务,任务中的代码应检查是否存在这种可能性。

属性和项的计算顺序

在生成的计算阶段中,导入的文件将按出现顺序合并到生成中。 将按以下顺序分三轮定义属性和项:

  • 按属性的出现顺序定义和修改属性。

  • 按项定义的出现顺序定义和修改项定义。

  • 按项的出现顺序定义和修改项。

在生成的执行阶段中,将按属性和项的出现顺序,在一个阶段中同时计算目标内定义的属性和项。

但是,情况并非仅仅如此。 定义了属性、项定义或项后,将会计算其值。 表达式计算器将扩展用于指定值的字符串。 字符串扩展依赖于生成阶段。 下面是更详细的属性和值计算顺序:

  • 在生成的计算阶段中:

    • 按属性的出现顺序定义和修改属性。 执行属性函数。 在表达式内扩展 $(PropertyName) 格式的属性值。 属性值设置为扩展的表达式。

    • 按项定义的出现顺序定义和修改项定义。 属性函数已在表达式内扩展。 元数据值设置为扩展的表达式。

    • 按项类型的出现顺序定义和修改项类型。 扩展 @(ItemType) 格式的项值。 项转换也会扩展。 属性函数和值已在表达式内扩展。 项列表和元数据值设置为扩展的表达式。

  • 在生成的执行阶段中:

    • 按属性和项的出现顺序同时计算目标内定义的属性和项。 执行属性函数,并在表达式内扩展属性值。 项值和项转换也会扩展。 属性值、项类型值和元数据值设置为扩展的表达式。

计算顺序的细微影响

在生成的计算阶段中,属性计算优先于项计算。 但是,属性可能具有似乎依赖于项值的值。 请看下面的脚本。

<ItemGroup>
    <KeyFile Include="KeyFile.cs">
        <Version>1.0.0.3</Version>
    </KeyFile>
</ItemGroup>
<PropertyGroup>
    <KeyFileVersion>@(KeyFile->'%(Version)')</KeyFileVersion>
</PropertyGroup>
<Target Name="AfterBuild">
    <Message Text="KeyFileVersion: $(KeyFileVersion)" />
</Target>

如果执行 Message 任务,将会显示以下消息:

KeyFileVersion: 1.0.0.3

这是因为 KeyFileVersion 的值实际上是字符串“@(KeyFile->'%(Version)')”。 在第一次定义属性时,不会扩展项和项转换,因此会将未扩展字符串的值赋给 KeyFileVersion 属性。

在生成的执行阶段,当其处理 Message 任务时,MSBuild 将扩展字符串“@(KeyFile->'%(Version)')”以生成“1.0.0.3”。

请注意,即使按顺序反转了属性和项组,同样的消息也会出现。

作为另一个示例,请考虑在目标内查找属性和项组时可能发生的情况:

<Target Name="AfterBuild">
    <PropertyGroup>
        <KeyFileVersion>@(KeyFile->'%(Version)')</KeyFileVersion>
    </PropertyGroup>
    <ItemGroup>
        <KeyFile Include="KeyFile.cs">
            <Version>1.0.0.3</Version>
        </KeyFile>
    </ItemGroup>
    <Message Text="KeyFileVersion: $(KeyFileVersion)" />
</Target>

Message 任务显示以下消息:

KeyFileVersion: 

这是因为,在生成的执行阶段中,将同时按从上到下的顺序计算目标内定义的属性和项组。 在定义 KeyFileVersion 时,KeyFile 未知。 因此,项转换会扩展为空字符串。

在这种情况下,如果反转属性和项组的顺序,将会恢复原始消息:

<Target Name="AfterBuild">
    <ItemGroup>
        <KeyFile Include="KeyFile.cs">
            <Version>1.0.0.3</Version>
        </KeyFile>
    </ItemGroup>
    <PropertyGroup>
        <KeyFileVersion>@(KeyFile->'%(Version)')</KeyFileVersion>
    </PropertyGroup>
    <Message Text="KeyFileVersion: $(KeyFileVersion)" />
</Target>

KeyFileVersion 的值设置为“1.0.0.3”,而不是“@(KeyFile->'%(Version)')”。 Message 任务显示以下消息:

KeyFileVersion: 1.0.0.3

请参见

概念

MSBuild 高级概念