比较属性和项
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 开始,目标依赖项注入已弃用。请改用 AfterTargets 和 BeforeTargets 特性。有关更多信息,请参见目标生成顺序。
字符串和项列表之间的转换
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 属性由一种属性类型表示,该属性类型的值可在字符串之间来回转换。支持的属性类型包括 bool、char、DateTime、Decimal、Double、int、string,以及 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