Определить целевую версию фреймворка во время компиляции
У меня есть некоторый код, который использует методы расширения, но компилируется в.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?
6 ответов
Связанный вопрос SO с "создать 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, и это было бы правильно.
У меня есть несколько предложений по улучшению ответов, данных до сих пор:
Используйте Version.CompareTo(). Тестирование на равенство не будет работать для более поздних версий фреймворка, которые пока не названы. Например
<CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
не будет соответствовать v4.5 или v4.5.1, что, как правило, вы хотите.
Используйте файл импорта, чтобы эти дополнительные свойства были определены только один раз. Я рекомендую держать файл импорта под контролем исходного кода, чтобы изменения распространялись вместе с файлами проекта без лишних усилий.
Добавьте элемент import в конец файла вашего проекта, чтобы он не зависел от каких-либо специфических для конфигурации групп свойств. Это также имеет то преимущество, что в файле проекта требуется дополнительная строка.
Вот файл импорта (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')))) >= 0">$(DefineConstants);NETFX_451</DefineConstants>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5')))) >= 0">$(DefineConstants);NETFX_45</DefineConstants>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.0')))) >= 0">$(DefineConstants);NETFX_40</DefineConstants>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.5')))) >= 0">$(DefineConstants);NETFX_35</DefineConstants>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.0')))) >= 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();
}
}
}
Типичный пример "реальной жизни"
Реализация соединения (разделитель строк, строки 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
Рекомендации
Можно ли сделать директиву препроцессора зависимой от версии платформы.NET?
Группы свойств перезаписываются только так, что это выбило бы ваши настройки для 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
Предопределенные символы для целевых структур теперь встроены в версию MSBuild, которая используется dotnet
инструмент и VS 2017 года. См. https://docs.microsoft.com/en-us/dotnet/standard/frameworks для получения полного списка.
#if NET47
Console.WriteLine("Running on .Net 4.7");
#elif NETCOREAPP2_0
Console.WriteLine("Running on .Net Core 2.0");
#endif
Я хотел бы внести свой вклад с обновленным ответом, который решает некоторые проблемы.
Если вы установите DefineConstants вместо CustomConstants, то в конечном итоге вы увидите командную строку "Отладка условных символов компиляции" после некоторого переключения версии фреймворка с дублированными условными константами (т.е.: NETFX_451;NETFX_45;NETFX_40;NETFX_35;NETFX_30;NETFX_20;NETFX_30; NETFX_35; NETFX;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 (lrnz.ruggeri@gmail.com)
-->
<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')))) >= 0">$(CustomConstants);NETFX_451</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5')))) >= 0">$(CustomConstants);NETFX_45</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.0')))) >= 0">$(CustomConstants);NETFX_40</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.5')))) >= 0">$(CustomConstants);NETFX_35</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.0')))) >= 0">$(CustomConstants);NETFX_30</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('2.0')))) >= 0">$(CustomConstants);NETFX_20</CustomConstants>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup>
<DefineConstants>$(DefineConstants);$(CustomConstants)</DefineConstants>
</PropertyGroup>
</Project>
Используйте отражение, чтобы определить, существует ли класс. Если это так, то динамически создайте и используйте его, в противном случае используйте класс обхода.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.