Можно ли условно скомпилировать в версию.NET Framework?

Я могу вспомнить, что при работе с MFC вы могли поддерживать несколько версий инфраструктуры MFC, проверив _MFC_VER макро.

Сейчас я делаю кое-что с.NET 4 и хотел бы использовать Tuple в нескольких местах, но все равно оставляю все остальное 3.5 совместимым.

Я хочу сделать что-то вроде:

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

4 ответа

Решение

Нет встроенных констант прекомпилятора, которые вы можете использовать. Но достаточно легко создать свои собственные конфигурации сборки в VS, каждая из которых имеет свой собственный набор определенных констант и, конечно, целевую версию фреймворка. Многие люди делают это для условной компиляции на основе 32- или 64-битных различий.

Если вы используете систему сборки.NET Core, вы можете использовать ее предопределенные символы:

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

Список предопределенных символов задокументирован в разделе Разработка библиотек с помощью кроссплатформенных инструментов и #if (Справочник по C#):

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

Стандарт.NET: NETSTANDARD, NETSTANDARD1_0, NETSTANDARD1_1, NETSTANDARD1_2, NETSTANDARD1_3, NETSTANDARD1_4, NETSTANDARD1_5, NETSTANDARD1_6, NETSTANDARD2_0, NETSTANDARD2_1

.NET Core: NETCOREAPP, NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2, NETCOREAPP3_0

При определении пользовательских символов компиляции в вашем.csproj (или.vbproj, теоретически) необходимо учитывать одно важное предостережение: они перезаписывают все ранее определенные символы компиляции. Например, рассмотрим фрагмент MSBuild:

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

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

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

Кроме того, вы захотите поместить это внутри PropertyGroup, определенной после всех других групп PropertyGroups, так как Visual Studio 2010 в настоящее время добавляет в настраиваемые символы компиляции такой способ, что он будет загромождать любые другие настраиваемые символы компиляции, которые вы определяете, если они помещаются перед Visual Студия снимает свое определение. Я подал эту проблему в Microsoft. Вы можете отслеживать его прогресс в Microsoft Connect.

Кстати, ваш код условной компиляции разочарует программистов, которые с ним сталкиваются.

Отредактировано на основе комментариев

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

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
}

Как всегда, хорошо взвешивать, используя встроенный материал, а не разворачивать свой собственный код. Обычно это означает, что вы спрашиваете себя: "Я в порядке, поддерживаю и поддерживаю этот код?" vs. "Код делает то, что мне нужно, прямо из коробки?"

В этом случае, поскольку вы не гарантированно иметь Tuple<T1, T2>Я бы просто написал свой простой, чтобы другие разработчики могли дышать легко:)

Поскольку у вас должны быть разные проекты, вы можете иметь частичные классы и ссылаться только на те, которые вам нужны для каждого проекта, со специальной логикой для них:

classname.cs публичное частичное имя класса {...}

classname.40.cs публичное частичное имя класса {public Tuple SomeMethod () {...}}

classname.35.cs публичное частичное имя класса { public KeyValuePair SomeMethod(){...} }

Другие вопросы по тегам