When you target a framework in an app or library, you're specifying the set of APIs that you'd like to make available to the app or library. You specify the target framework in your project file using a target framework moniker (TFM). An app or library can target a version of .
Conditional compilation provides a way of including or omitting selected lines of source code depending on the values of literals specified by the DEFINE directive. In this way, you can create multiple variants of the same program without the need to maintain separate source streams.
In Visual Studio, you can specify the version of . NET that you want your project to target. Framework targeting helps guarantee that the application uses only functionality that is available in the specified framework version.
Conditional compilation can be useful when compiling code for a debug build or when compiling for a specific configuration. A conditional directive beginning with an #if directive must explicitly be terminated with an #endif directive.
One of the best ways to accomplish this is to create different build configurations in your project:
<PropertyGroup Condition=" '$(Framework)' == 'NET20' ">
<DefineConstants>NET20</DefineConstants>
<OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Framework)' == 'NET35' ">
<DefineConstants>NET35</DefineConstants>
<OutputPath>bin\$(Configuration)\$(Framework)</OutputPath>
</PropertyGroup>
And in one of your default configurations:
<Framework Condition=" '$(Framework)' == '' ">NET35</Framework>
Which would set the default if it wasn't defined anywhere else. In the above case the OutputPath will give you a separate assembly each time you build each version.
Then create a AfterBuild target to compile your different versions:
<Target Name="AfterBuild">
<MSBuild Condition=" '$(Framework)' != 'NET20'"
Projects="$(MSBuildProjectFile)"
Properties="Framework=NET20"
RunEachTargetSeparately="true" />
</Target>
This example will recompile the entire project with the Framework variable set to NET20 after the first build (compiling both and assuming that the first build was the default NET35 from above). Each compile will have the conditional define values set correctly.
In this manner you can even exclude certain files in the project file if you want w/o having to #ifdef the files:
<Compile Include="SomeNet20SpecificClass.cs" Condition=" '$(Framework)' == 'NET20' " />
or even references
<Reference Include="Some.Assembly" Condition="" '$(Framework)' == 'NET20' " >
<HintPath>..\Lib\$(Framework)\Some.Assembly.dll</HintPath>
</Reference>
An alternative that is working for me so far is to add the following to the project file:
<PropertyGroup>
<DefineConstants Condition=" !$(DefineConstants.Contains(';NET')) ">$(DefineConstants);$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
<DefineConstants Condition=" $(DefineConstants.Contains(';NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(";NET"))));$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
</PropertyGroup>
This takes the value of TargetFrameworkVersion property, which is like "v3.5", replaces the "v" and "." to get "NET35" (using the new Property Functions feature). It then removes any existing "NETxx" value and adds it to the end of the DefinedConstants. It may be possible to streamline this, but I haven't got the time to fiddle.
Looking on the Build tab of the project properties in VS you will see the resulting value in the conditional compilation symbols section. Changing the target framework version on the Application tab then changes the symbol automatically. You can then use #if NETxx
preprocessor directives in the usual way. Changing the project in VS does not seem to lose the custom PropertyGroup.
Note that this doesn't give seem to give you anything different for the Client Profile target options, but that's not an issue for me.
I had problems with these solutions, possibly because my initial constants were pre-built by these properties.
<DefineConstants />
<DefineDebug>true</DefineDebug>
<DefineTrace>true</DefineTrace>
<DebugSymbols>true</DebugSymbols>
Visual Studio 2010 also threw up an error because of the semi-colons, claiming they are illegal characters. The error message gave me a hint as I could see the pre-built constants seperated by commas, eventually followed by my "illegal" semi-colon. After some reformatting and massaging I was able to come up with a solution that works for me.
<PropertyGroup>
<!-- Adding a custom constant will auto-magically append a comma and space to the pre-built constants. -->
<!-- Move the comma delimiter to the end of each constant and remove the trailing comma when we're done. -->
<DefineConstants Condition=" !$(DefineConstants.Contains(', NET')) ">$(DefineConstants)$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
<DefineConstants Condition=" $(DefineConstants.Contains(', NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", NET"))))$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", "")), </DefineConstants>
<DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 2.0 ">$(DefineConstants)NET_20_OR_GREATER, </DefineConstants>
<DefineConstants Condition=" $(TargetFrameworkVersion.Replace('v', '')) >= 3.5 ">$(DefineConstants)NET_35_OR_GREATER</DefineConstants>
<DefineConstants Condition=" $(DefineConstants.EndsWith(', ')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(", "))))</DefineConstants>
</PropertyGroup>
I would post a screenshot of the Advanced Compiler Settings dialog (opened by clicking the "Advanced Compile Options..." button on the Compile tab of your project). But as a new user, I lack the rep to do so. If you could see the screenshot, you would see the custom constants auto-filled by the property group and then you'd be saying, "I gotta get me some of that."
EDIT: Got that rep surprisingly fast.. Thanks guys! Here's that screenshot:
Begin with clearing the constants:
<PropertyGroup>
<DefineConstants/>
</PropertyGroup>
Next, build up your debug, trace and other constants like:
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>TRACE;DEBUG;$(DefineConstants)</DefineConstants>
</PropertyGroup>
Last, build your framework constants:
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">
<DefineConstants>NET10;NET20;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.0' ">
<DefineConstants>NET10;NET20;NET30;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v3.5' ">
<DefineConstants>NET10;NET20;NET30;NET35;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
<DefineConstants>NET10;NET20;NET30;NET35;NET40;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.5' ">
<DefineConstants>NET10;NET20;NET30;NET35;NET40;NET45;$(DefineConstants)</DefineConstants>
</PropertyGroup>
I think this approach is very readable and understandable.
In a .csproj file, after an existing <DefineConstants>DEBUG;TRACE</DefineConstants>
line, add this:
<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace("v",""))' >= '4.0' ">NET_40_OR_GREATER</DefineConstants>
<DefineConstants Condition=" '$(TargetFrameworkVersion.Replace("v",""))' == '4.0' ">NET_40_EXACTLY</DefineConstants>
Do this for both Debug and Release build configurations. Then use in your code:
#if NET_40_OR_GREATER
// can use dynamic, default and named parameters
#endif
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With