Обнаружить версию целевой рамок во время компиляции

У меня есть код, который использует методы расширения, но компилируется в .NET 2.0 с использованием компилятора в VS2008. Чтобы облегчить это, мне пришлось объявить ExtensionAttribute:

/// <summary>
/// ExtensionAttribute is required to define extension methods under .NET 2.0
/// </summary>
public sealed class ExtensionAttribute : Attribute
{
}

Однако теперь мне нравится библиотека, в которой этот класс содержится, также может быть скомпилирован в .NET 3.0, 3.5 и 4.0 - без предупреждения ExtensionAttribute в нескольких местах.

Есть ли какая-либо директива времени компиляции, которую я могу использовать, чтобы включать только ExtensionAttribute, когда запланированная версия рамки -.NET 2?

Ответ 1

Связанный вопрос SO с "create N различными конфигурациями", безусловно, является одним из вариантов, но когда мне было нужно это, я просто добавил условные элементы DefineConstants, поэтому в моем Debug | x86 (например) после существующего DefineConstants для DEBUG; TRACE, я добавил эти 2, проверив значение в TFV, которое было установлено в первой PropertyGroup файла csproj.

<DefineConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">RUNNING_ON_4</DefineConstants>
<DefineConstants Condition=" '$(TargetFrameworkVersion)' != 'v4.0' ">NOT_RUNNING_ON_4</DefineConstants>

Вам, разумеется, не нужны оба, но это просто для того, чтобы привести примеры поведения eq и ne - #else и #elif тоже отлично работают:)

class Program
{
    static void Main(string[] args)
    {
#if RUNNING_ON_4
        Console.WriteLine("RUNNING_ON_4 was set");
#endif
#if NOT_RUNNING_ON_4
        Console.WriteLine("NOT_RUNNING_ON_4 was set");
#endif
    }
}

Тогда я мог бы переключаться между таргетингом 3.5 и 4.0, и он поступил бы правильно.

Ответ 2

Группы свойств переписываются только так, что это выбивает ваши настройки для DEBUG, TRACE или любых других. - См. Оценка свойств MSBuild

Также, если свойство DefineConstants задано из командной строки, все, что вы делаете с ним внутри файла проекта, не имеет значения, поскольку этот параметр становится глобальным. Это означает, что ваши изменения в этом значении терпят неудачу.

Пример сохранения существующих определенных констант:

    <CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">V2</CustomConstants>
    <CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">V4</CustomConstants>
    <DefineConstants Condition=" '$(DefineConstants)' != '' And '$(CustomConstants)' != '' ">$(DefineConstants);</DefineConstants>
    <DefineConstants>$(DefineConstants)$(CustomConstants)</DefineConstants>

Этот раздел ДОЛЖЕН прибыть после любых других определенных констант, поскольку они вряд ли будут настроены аддитивным образом.

Я только определил эти 2, потому что это в основном то, что меня интересует в моем проекте, ymmv.

См. также: Общие свойства проекта MsBuild

Ответ 3

У меня есть несколько предложений по улучшению ответов, которые были даны до сих пор:

  • Используйте Version.CompareTo(). Тестирование на равенство не будет работать для более поздних версий фреймворка, но для их имени. Например.

    <CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
    

    не будет соответствовать v4.5 или v4.5.1, которые обычно вам нужны.

  • Используйте файл импорта, чтобы эти дополнительные свойства нужно было определить только один раз. Я рекомендую хранить файл импорта под контролем источника, чтобы изменения распространялись вместе с файлами проекта без дополнительных усилий.

  • Добавьте элемент импорта в конец файла проекта, чтобы он не зависел от каких-либо групп свойств, специфичных для конфигурации. Это также имеет преимущество в том, что в файле проекта требуется одна дополнительная строка.

Вот файл импорта (VersionSpecificSymbols.Common.prop)

<!--
******************************************************************
Defines the Compile time symbols Microsoft forgot
Modelled from https://msdn.microsoft.com/en-us/library/ms171464.aspx
*********************************************************************
-->

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5.1')))) &gt;= 0">$(DefineConstants);NETFX_451</DefineConstants>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5'))))   &gt;= 0">$(DefineConstants);NETFX_45</DefineConstants>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.0'))))   &gt;= 0">$(DefineConstants);NETFX_40</DefineConstants>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.5'))))   &gt;= 0">$(DefineConstants);NETFX_35</DefineConstants>
        <DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.0'))))   &gt;= 0">$(DefineConstants);NETFX_30</DefineConstants>
    </PropertyGroup>
</Project>

Добавить элемент импорта в файл проекта

Ссылка на него из вашего .csproj файла, добавив в конце, перед тегом.

…
    <Import Project="VersionSpecificSymbols.Common.prop" />
</Project>

Вам нужно будет исправить путь, чтобы указать на общую/общую папку, в которую вы помещаете этот файл.

Использовать символы времени компиляции

namespace VersionSpecificCodeHowTo
{
    using System;

    internal class Program
    {
        private static void Main(string[] args)
        {
#if NETFX_451
            Console.WriteLine("NET_451 was set");
#endif

#if NETFX_45
            Console.WriteLine("NET_45 was set");
#endif

#if NETFX_40
            Console.WriteLine("NET_40 was set");
#endif

#if NETFX_35
            Console.WriteLine("NETFX_35 was set");
#endif

#if NETFX_30
            Console.WriteLine("NETFX_30 was set");
#endif

#if NETFX_20
             Console.WriteLine("NETFX_20 was set");
#else
           The Version specific symbols were not set correctly!
#endif

#if DEBUG
            Console.WriteLine("DEBUG was set");
#endif

#if MySymbol
            Console.WriteLine("MySymbol was set");
#endif
            Console.ReadKey();
        }
    }
}

Пример "реальной жизни"

Реализация Join (разделитель строк, строки с IEnumerable) До .NET 4.0

// string Join(this IEnumerable<string> strings, string delimiter)
// was not introduced until 4.0. So provide our own.
#if ! NETFX_40 && NETFX_35
public static string Join( string delimiter, IEnumerable<string> strings)
{
    return string.Join(delimiter, strings.ToArray());
}
#endif

Ссылки

Функции свойств

Оценка свойств MSBuild

Могу ли я создать директиву препроцессора в зависимости от версии платформы .NET?

Условная компиляция в зависимости от версии фреймворка в С#

Ответ 4

Я хотел бы внести свой вклад с обновленным ответом, который решает некоторые проблемы.

Если вы установите DefineConstants вместо CustomConstants, вы закончите в командной строке условных компиляций Debug после некоторого переключателя версии оболочки с дублированными условными константами (то есть: NETFX_451; NETFX_45; NETFX_40; NETFX_35; NETFX_30; NETFX_20; NETFX_35; NETFX_30; NETFX_20;). Это версия VersionSpecificSymbols.Common.prop, которая решает любую проблему.

<!--
*********************************************************************
Defines the Compile time symbols Microsoft forgot
Modelled from https://msdn.microsoft.com/en-us/library/ms171464.aspx
*********************************************************************
Author: Lorenzo Ruggeri ([email protected])
-->

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Choose>
    <When Condition=" $(TargetFrameworkVersion) == 'v2.0' ">
      <PropertyGroup>
        <CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
      </PropertyGroup>
    </When>
    <When Condition=" $(TargetFrameworkVersion) == 'v3.0' ">
      <PropertyGroup>
        <CustomConstants >$(CustomConstants);NETFX_30</CustomConstants>
        <CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
      </PropertyGroup>
    </When>
    <When Condition=" $(TargetFrameworkVersion) == 'v3.5' ">
      <PropertyGroup>
        <CustomConstants >$(CustomConstants);NETFX_35</CustomConstants>
        <CustomConstants >$(CustomConstants);NETFX_30</CustomConstants>
        <CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
      </PropertyGroup>
    </When>
    <Otherwise>
      <PropertyGroup>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5.1')))) &gt;= 0">$(CustomConstants);NETFX_451</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5')))) &gt;= 0">$(CustomConstants);NETFX_45</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.0')))) &gt;= 0">$(CustomConstants);NETFX_40</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.5')))) &gt;= 0">$(CustomConstants);NETFX_35</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.0')))) &gt;= 0">$(CustomConstants);NETFX_30</CustomConstants>
        <CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('2.0')))) &gt;= 0">$(CustomConstants);NETFX_20</CustomConstants>
      </PropertyGroup>
    </Otherwise>
  </Choose>
  <PropertyGroup>
    <DefineConstants>$(DefineConstants);$(CustomConstants)</DefineConstants>
  </PropertyGroup>
</Project>

Ответ 5

Используйте отражение, чтобы определить, существует ли класс. Если это так, то динамически создайте и используйте его, в противном случае используйте класс обходного пути .Net2, который можно определить, но не использовать для всех других версий .net.

Вот код, который я использовал для AggregateException, который является .Net 4 и выше:

var aggregatException = Type.GetType("System.AggregateException");

if (aggregatException != null) // .Net 4 or greater
{
    throw ((Exception)Activator.CreateInstance(aggregatException, ps.Streams.Error.Select(err => err.Exception)));
}

// Else all other non .Net 4 or less versions
throw ps.Streams.Error.FirstOrDefault()?.Exception 
      ?? new Exception("Powershell Exception Encountered."); // Sanity check operation, should not hit.