22

I can recall back when working with MFC you could support multiple versions of the MFC framework by checking the _MFC_VER macro.

I'm doing some stuff now with .NET 4 and would like to use Tuple in a couple of spots but still keep everything else 3.5 compatible.

I'm looking to do something like:

#if DOTNET4
    public Tuple<TSource, TResult> SomeMethod<TSource, TResult>(){...}
#else
    public KeyValuePair<TSource, TResult> SomeMethod<TSource, TResult>(){...}
#endif
dkackman
  • 15,179
  • 13
  • 69
  • 123
  • Perhaps vote up this: https://connect.microsoft.com/VisualStudio/feedback/details/679124/c-conditional-compilation-based-on-availability-of-a-type – Brad Robinson Jul 12 '11 at 02:58
  • possible duplicate of [Conditional Compilation and Framework Targets](http://stackoverflow.com/questions/2923210/conditional-compilation-and-framework-targets) – JohnC Apr 25 '15 at 10:38
  • 2
    This user voice is not quite the same and anyways is closed. I couldn't find any other that speaks to framework version so I created one, http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/7699920-built-in-conditional-compilation-by-framework-vers. Please upvote it. – JohnC Apr 25 '15 at 10:57

5 Answers5

12

If you are using the .NET Core build system, you can use its predefined symbols:

#if NET40
    public Tuple<TSource, TResult> SomeMethod<TSource, TResult>(){...}
#else
    public KeyValuePair<TSource, TResult> SomeMethod<TSource, TResult>(){...}
#endif

The list of predefined symbols is documented in Developing Libraries with Cross Platform Tools and #if (C# Reference):

.NET Framework: NETFRAMEWORK, NET48, NET472, NET471, NET47, NET462, NET461, NET46, NET452, NET451, NET45, NET40, NET35, NET20

.NET Standard: NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, NETSTANDARD1_1, NETSTANDARD1_0

.NET 5+ (and .NET Core): NET, NET6_0, NET6_0_ANDROID, NET6_0_IOS, NET6_0_MACOS, NET6_0_MACCATALYST, NET6_0_TVOS, NET6_0_WINDOWS, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0

Kevinoid
  • 4,180
  • 40
  • 25
  • 1
    With .Net 5.0 (6.0) the are some additional predefined symbols: NET, NET6_0, NET6_0_ANDROID, NET6_0_IOS, NET6_0_MACOS, NET6_0_MACCATALYST, NET6_0_TVOS, NET6_0_WINDOWS, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0 --- See the link above (#if (C# Reference)) provided by Kevinoid – Valvestino Oct 07 '21 at 11:33
  • Thanks @Valvestino! Answer updated. – Kevinoid Oct 07 '21 at 15:12
11

There is one big caveat to be aware of when defining custom compilation symbols in your .csproj (or .vbproj, theoretically): they overwrite all previously-defined compilation symbols. For example, consider MSBuild snippet:

  <PropertyGroup Condition="'$(TargetFrameworkVersion)' == 'v4.0'">
    <DefineConstants>$(DefineConstants);DOTNET_40</DefineConstants>
  </PropertyGroup>
  <PropertyGroup>
    <DefineConstants>ITS_CLOBBERING_TIME</DefineConstants>
  </PropertyGroup>

The second DefineConstants element will, as its value suggests, clobber the first value of DefineConstants. To avoid this, you'll want to rewrite the second DefineConstants element to look like this:

    <DefineConstants>$(DefineConstants);ITS_CLOBBERING_TIME</DefineConstants>

Also, you'll want to place this inside of a PropertyGroup defined after all other PropertyGroups, as Visual Studio 2010 currently adds in custom compilation symbols in such a way that it will clobber any other custom compilation symbols you define if they are placed before Visual Studio plops down its definition. I've filed this issue with Microsoft. You can track its progress at Microsoft Connect.

rmiesen
  • 2,470
  • 1
  • 21
  • 15
  • 3
    You don't even need to do this conditionally, you could just define: `NETFX$(TargetFrameworkVersion.Replace("v", "").Replace(".", "_"));$(DefineConstants)` – BrainSlugs83 Aug 28 '14 at 01:31
7

There are no builtin precompiler constants that you can use. But, it is easy enough create your own build configurations in VS with each configuration having its own set of defined constants and of course a target framework version. A lot of people do this to conditionally compile based 32 or 64 bit differences.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
  • I wonder why they don't build these in like they do DEBUG and CODE_ANALYSIS. – dkackman Sep 20 '09 at 00:41
  • dkackman: Eric Lippert always says that these features don't build themselves; the feature does not exist simply because nobody ever designed, specified, implemented, tested, documented and shipped that feature. See http://blogs.msdn.com/ericlippert/archive/2009/06/22/why-doesn-t-c-implement-top-level-methods.aspx – configurator Sep 20 '09 at 03:32
  • 4
    A build-configuration does not cover Platform and Referenced Assemblies. You will have to configure a different Project for each platform. – H H Sep 20 '09 at 08:44
  • @configurator: Fair point. It just seems like a logical thing to do for the most common non-build configuration sorts of things (i.e. things that have to be handled at the project level and is a pattern that is already part of vs.net. A compact framework project defines PocketPC automatically for instance. – dkackman Sep 20 '09 at 13:54
  • 1
    @dkackman: I don't think DEBUG and CODE_ANALYSIS are build in, at least there not automatically defined if you compile with debug or so. For example: DEBUG is explicitly set in the csproj (i.e. msbuild file) when you run the "Debug" configuration. – Christian.K Sep 23 '09 at 11:07
7

On a side note, your conditional compilation code will frustrate programmers that encounter it.

Edited, based on comments

It's probably better to write your own class so you can guarantee what it's going to do, and you don't have any weird signature or inheritance issues:

public class Pair<TSource, TResult>
{
    public TSource Source { get; set; }
    public TResult Result { get; set; }

    public Pair() {}
    public Pair(TSource source, TResult result)
    {
        Source = source;
        Result = result;
    }

    // Perhaps override Equals() and GetHashCode() as well
}

As always, it's good to weigh using the built-in stuff vs. rolling out your own code. Generally that means asking yourself, "Am I OK maintaining and supporting this code?" vs. "Does the code do what I need it to, out of the box?"

In this case, since you're not guaranteed to have Tuple<T1, T2>, I'd just write your own simple one so other developers can breathe easy :)

Doug
  • 5,208
  • 3
  • 29
  • 33
1

As you should have different projects, you could have partial classes and only reference the one you need for each project with the specific logic for them:

classname.cs public partial classname { ... }

classname.40.cs public partial classname { public Tuple SomeMethod(){...} }

classname.35.cs public partial classname { public KeyValuePair SomeMethod(){...} }

Mr.O
  • 37
  • 3