MSBuild: By Example—Understanding the Difference between @ and %
Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio
| CSharp-Online.NET:Articles |
| Visual Studio Articles |
| © 2006 Hashimi & Hashimi |
Understanding the Difference Between @ and %
When using MSBuild, you have three ways to evaluate an item/property expression to a value, as described in Table 3-3.
Table 3-3. Methods to Access Item/Property Values
| Syntax | Description |
$()
| Extracts the value of a property |
@()
| Extracts the value of an item as a list, that is, vector |
%()
| Extracts value of an item as a single string, that is, scalar |
The simplest of these methods is the $() syntax. You will use this syntax when referencing properties and only for properties.
Now we will discuss the two methods to retrieve item values. Why is there one method to access a property and two methods to access an item? As mentioned, a property is a simple key/value pair. So, for every key, there is only a single value. In other words, a property has a one-to-one relationship between the key and value. An item, on the other hand, has a one-to-many relationship. An item can contain many values inside it, but it can also contain just one. Examine the following item definition:
<ItemGroup>
<MDForm Include="MetaDataFrm.cs">
<Name>Sayed Ibrahim Hashimi</Name>
<Email>sayed.hashimi@gmail.com</Email>
</MDForm>
<!-- Remainder truncated to conserve space -->
</ItemGroup>
The MDForm item here contains one value inside it, which is MetaDataFrm.cs. Now you will see what happens when you use both methods to access the value for this item and compare the results. The following is a simple target to demonstrate this:
<Target Name="VectorScalar1"> <Message Text="%40(MDForm->'% (Filename)'): @(MDForm->'%(Filename)')" /> <Message Text="%25(MDForm.Filename): %(MDForm.Filename)" /> </Target>
This target will simply print the value for the filename of this item. Note the use of %40, which is the escape code for the at (@) character; similarly, the % character is escaped with %25. Figure 3-12 shows the output from this target.
![]()
Figure 3-12. Output from the target VectorScalar1
As you can see, the results of both reference methods are the same! Well, now you will see another target that is similar and see whether there is any difference. The following is the definition of the target and the property that it references:
<Target Name="VectorScalar2"> <Message Text="%40(Compile->'% (Filename)'): @(Compile->'%(Filename)')"/> <Message Text=" "/> <!-- Empty line for readability --> <Message Text="%25(Compile.Filename): %(Compile.Filename)"/> </Target> <ItemGroup> <Compile Include="MetaDataFrm.cs"> <SubType>Form</SubType> </Compile> <Compile Include="MetaDataFrm.Designer.cs"> <DependentUpon>MetaDataFrm.cs</DependentUpon> </Compile> <Compile Include="Program.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <EmbeddedResource Include="Properties\Resources.resx"> <Generator>ResXFileCodeGenerator</Generator> <LastGenOutput>Resources.Designer.cs</LastGenOutput> <SubType>Designer</SubType> </EmbeddedResource> <Compile Include="Properties\Resources.Designer.cs"> <AutoGen>True</AutoGen> <DependentUpon>Resources.resx</DependentUpon> </Compile> <None Include="Properties\Settings.settings"> <Generator>SettingsSingleFileGenerator</Generator> <LastGenOutput>Settings.Designer.cs</LastGenOutput> </None> <Compile Include="Properties\Settings.Designer.cs"> <AutoGen>True</AutoGen> <DependentUpon>Settings.settings</DependentUpon> <DesignTimeSharedInput>True</DesignTimeSharedInput> </Compile> </ItemGroup>
This target is almost identical to the previous target, with the exception that there is a blank line printed to increase readability, and you’ll see why. Execute this target with the following command: >msbuild MetaDataEx.csproj /t:VectorScalar2. Figure 3-13 shows the output from this execution.
![]()
Figure 3-13. Output from the VectorScalar2 target
In this target, you can see that the output for both syntaxes certainly differs. Now let’s examine it a bit closer. The task call that generated the first section is <Message Text="@ (Compile->'% (Filename)'): @(Compile->'%(Filename)')"/>. This call generated a single string to be output to the console. That was all of the names of the Compile files separated by the default semicolon delimiter. This is the behavior of the @ syntax. You specify the item that you are acting upon, and then you pass the optional metadata name that you want returned. The Filename metadata reference name is not defined in the project file; it is well-known metadata. (We discussed well-known metadata earlier in the chapter.) No matter what item you pass in, the result will act upon the group, not the individual items.
Contrast this to the behavior of using the % syntax; for each item in the Compile list, the last Message task is executed. Thinking in terms of typical build steps, you will most likely be using the % syntax when dealing with files. On the other hand, if you are passing the item to a custom task, you will most likely send the entire item as a list. The task will then decompose the list and act upon the individual items if necessary.
The @ notation signifies you are acting upon an array of items and that you would like to process each item in the list individually. The % notation is called a transform. A transform is a one-to-one mapping from one item into another. In this sample, you transformed the element to provide the filename of the included file. You can also perform multiple transformations at once. For instance, how could you get the filename and extension into your lists? Let’s create a new target from a modified version of the previous target. This target, VectorScalar3, is as follows. Also, you will see an added call to the Message task to further clarify the use of these transformations.
<Target Name="VectorScalar3"> <Message Text="%40(Compile->'%25(Filename)%25(Extension)'):~CCC @(Compile->'%(Filename)%(Extension)')"/> <Message Text=" "/> <!-- Empty line for readability --> <Message Text="%25(Compile.Filename)%25(Extension):~CCC %(Compile.Filename)%(Extension)"/> <Message Text=" "/> <!-- Empty line for readability --> <!-- Most likely not what you want to do --> <Message Text="%40(Compile->'%25(Filename)')%25(Extension):~CCC @(Compile->'%(Filename)')%(Extension)"/> </Target>
In the first message, you are passing the value @(Compile->'%(Filename)%(Extension)'). The @ character says you are acting upon the list items of the Compile item, and the % character determines which transformations are to be executed. In this case, you have %(Filename)%(Extension). Since this is inside the @ reference, this will be executed for each list item individually. Figure 3-14 shows the output from this message task and for the remainder of the target. We will explain the other items in this target shortly.
![]()
Figure 3-14. Output from the VectorScalar3 target
As you can see from Figure 3-14, the extensions of the files, which all happen to be .cs, were appended to the text sent to the Message task. As mentioned, the %(Extension) transformation is within the @ reference. You will examine the behavior when this is not the case in a bit. For now you will proceed to the individual transformations.
The next call to the Message task is provided %(Compile.Filename)%(Extension) for the text value. For each item in the Compile list, a message task will be executed as before, but this time the filename followed by the extension is sent to the Message task. Imagine that you iterate over the Compile item list and that inside this loop you build a string with the filename and extension from the current Compile item list. Notice in the %(Extension) transformation, the item name was not specified. This is OK because this information is gathered from the Compile reference.
You may not find the last example to be too useful; instead, it further clarifies the distinction between these two methods. In the last call, the Message task is provided with @(Compile->'%(Filename)')%(Extension). Notice the difference between this text and the text from the first invocation of the Message task. The key difference here is that the %(Extension) is outside the @() reference. So in the output, instead of being included in the array output, %(Extension) was simply appended to the output from the array output. Now you should be prepared to use these mechanisms in your MSBuild files without getting tripped up.
|

