Kód pro více verzí .NET Frameworku

Ondřej Janáček       30.07.2014       C#, Visual Studio, .NET       10100 zobrazení

Víceméně pořád pracuji na nějakém projektu, který je určen pro .NET Framework 4.0 a současně na projektu, který může používat vlastnosti z nejnovější verze. Také mám nasbíráno spoustu užitečných nástrojů a metod a všechno to mám schované v knihovně, kterou ke každému svému projektu připojím. Idea je, že projekty na novější verzi používají kód napsaný přímo v té verzi a projekty na starších verzích používají stejný nebo podobný kód používající vlastnosti ze starší verze. Nějakou dobu jsem neohrabaně vždycky kus kódu zakomentoval, zkompiloval pro jednu verzi a pak jsem zase zakomentoval něco jiného a zkompiloval pro druhou verzi. To je ale velmi pomalé a otravné řešení a navíc pro tento problém existuje v .NET Frameworku a Visual Studiu řešení.

CallerMemberName atribut

Jako příklad uvedu třídu ViewModelBase, kterou používám ve WPF projektech jako base třídu pro všechny moje view modely (zatím jsem si vystačil bez MVVM frameworku). Přestože tématem článku není WPF, popíši následující ukázku podrobněji, než by bylo nutné, aby ji pochopili všichni.

.NET 4.0

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

Metoda SetField<T>(T, T, string) je pomocná metoda, která nejdříve ověří, jestli se hodnota opravdu změnila, abych nevolal OnPropertyChanged(string) zbytečně. Tuto metodu pak volám přímo v setteru property mého view modelu.

private string title;
public string Title
{
    get { return title; }
    set { SetField(ref title, value, "Title"); }
}

.NET 4.5

Od .NET 4.5 je k dispozici nový atribut CallerMemberName, který automaticky dodá jméno volající property nebo metody jako argument.
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
    // tady se nic nemění
}

Potom není nutné specifikovat toto jméno manuálně a riskovat tak překlep.

set { SetField(ref title, value); }

Toto samozřejmě chci mít ve své knihovně a chci mít možnost nespecifikovat jméno property, pokud dělám v projektu cíleném na verzi 4.5.1.

Build configuration a preprocessor directives

Projekt, ve kterém se kód nachází, nechám nastavený normálně na starší verzi frameworku. Ve Visual Studiu si nejdříve vytvořím novou build konfiguraci v Configuration Manageru, který najdete buď v menu Build a nebo na panelu nástrojů jak je ukázáno na obrázku.

config manager

Kromě odkazu na něj jsou tam právě všechny build konfigurace spojené s projektem. Debug a Release jsou tam vždy, NET45 je můj, který teď vytvoříme. V Configuration Manageru rozbalte první combobox a vyberte volbu <New…> . Do řádku zajdete jméno, doporučuje se bez mezer, ideálně kombinace malých písmenek, velkých písmenek a číslic. Dále pak v comboboxu vyberte konfiguraci, která bude základem pro vytvoření této. Já jsem použil Release, protože neobsahuje některé prvky určené pro debugging. Potvrďte a nastavte Vašemu projektu tuto konfiguraci. Protože jsem nenašel, kde se dá pro build konfiguraci nastavit cílená verze .NET Frameworku, tak jsem projekt zavřel a editoval jeho .csproj soubor, kde jsou tyto informace uloženy. Ten si otevřete klidně v notepadu a vyhledejte následující kousek konfigurace.

<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'NET45|AnyCPU'">
    <OutputPath>bin\NET45\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
    <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>

Tady je potřeba udělat dvě věci. Nejdříve definovat konstantu, kterou později budeme používat, v elementu DefineConstants. Pokud jste profil vytvořili na základě Release konfigurace, pak je tam stejně jako u mě TRACE. Tato konstanta povoluje používání metod ve třídě System.Diagnostics.Trace, které lze používat například pro logování. V zdrojovém kódu této třídy můžete u některých metod vidět aplikovaný následující atribut

[System.Diagnostics.Conditional("TRACE")]

který definuje, že daná metoda půjde zavolat, jen pokud je nastavená konstanta TRACE. Pokud metody této třídy nepoužíváte, lze ji smazat. V opačném případě ji oddělíme středníkem a přidáme vlastní konstantu, jejíž jméno by se mělo držet konvence – použití velkých písmen, číslic a jako oddělovač podtržítko. U mě je to RUNNING_NET_4_5. Dále je potřeba přidat element pro cílenou verzi frameworku.

<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>

Při používání profile verzí (např. 4.0 Client Profile) je potřeba uvést i následující element.

<TargetFrameworkProfile>Client</TargetFrameworkProfile>

Soubor uložíme a otevřeme projekt ve VS. Nyní aplikujeme námi definovanou konstantu v kódu. Protože potřebuji změnit pouze signaturu metody a to jen poslední parametr, udělám následující

protected bool SetField<T>(ref T field, T value,
#if RUNNING_NET_4_5
  [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
#else
  string propertyName)
#endif
{ ... }

Používám zde preprocessor direktivu #if, která funguje na stejném principu jako if, který používáte v kódu. Je důležité, aby na řádku s direktivou nebylo nic víc kromě direktivy a případně konstanty. Nejdříve tedy specifikuji, že pokud je nastavená moje konstanta, pak poslední parametr bude dekorován atributem CallerMemberName a v opačném případě tam nebude. Specifikuji celý název typu (tj. včetně namespace), abych tento namespace nemusel v souboru uvádět mezi ostatními usingy.

To je vše. Teď už stačí jen projekt buildnout a ve složce bin se objeví nová složky pro danou konfiguraci (toto lze také nastavit) a tam assembly buildnutá pro verzi 4.5.1 s metodou zvýhodněnou vyšší verzí frameworku. Pokud ve VS konfiguraci změním zpět na Release a buildnu projekt, pak se mi ve složce bin/Release objeví assembly pro verzi 4.0 s metodou, která používá dostupnou funkcionalitu.

Build konfigurace i samotné preprocessor direktivy jsou oboje mocné nástroje, které lze používat nezávisle na sobě. Direktivami lze například nastavovat hodnoty kontakt v kódu a měnit tak cesty k souborům podle konfigurace (např. TEST, CUSTOMER, ale klidně i Debug a Release) a pomocí build konfigurace mohu nastavit mnohem více věcí, z nichž některé jsou dostupné ve vlastnostech projektu, na záložce Build a jiné můžete dodefinovat ručně. Lze tak třeba měnit reference na projekty nebo odebrat celý soubor z projektu. Možností je spousta.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback