Enum None vs. Nullable<TEnum>   zodpovězená otázka

C#, .NET

Předpokládejme, že u nějaké nepovinné položky potřebujeme evidovat volbu Alfa nebo Beta (a "nezadáno"). Pro hodnotu "nezadáno" ale máme na úrovni jazyka C# dvě možnosti reprezentace (v db to může být buď null,1,2 nebo 0,1,2 to je vcelku jedno):

1) Hodnota 0 resp. None

public enum Options
{
    None = 0,
    Alfa = 1,
    Beta
}

public Options Options { get; set; }

2) Hodnota null

public enum Options
{
   Alfa = 1,
   Beta
}

public Options? Options { get; set; }

Já osobně preferuju první řešení s None (přijde mi pak testování prázdné hodnoty jednodušší), ale někde jsem i našel, že by se mělo použít Nullable.

Dá se použití druhé varianty nějak opodstatnit?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

2) Nullable se hodí v případech, kdy je potřeba zjistit, že hodnota ještě vůbec nebyla nastavena.

1) None je spíš nějaká výchozí hodnota, rozhodně bych tím nenahrazoval 2).

nahlásit spamnahlásit spam -1 / 3 odpovědětodpovědět

To moc jako smysluplné odůvodnění použití 2 nevidím.

Nenastavená proměnná typu Options bude mít hodnotu 0 tedy None v tom oproti null u Options? není rozdíl. Oboje má ve výchozím stavu hodnotu, která sémanticky odpovídá hodnotě "nezadáno".

None znamená "žádná/žádný" tedy ani Alfa ani Beta což lze právě sémanticky chápat jako tu hodnotu pro "nezadáno".

None je sice zároveň i výchozí hodnotou, ale jen z hlediska .NET runtime, protože to čistě náhodou odpovídá int hodnotě 0. Se skutečnou výchozí hodnotou to nutně souviset nemusí (rozhodně bych None významově jako "výchozí" nechápal). To, že některá (klidně jiná) hodnota enumu je výchozí, bych buď uvedl pouze v komentářích nebo pokud bych chtěl explicitně nadefinovat, např. že výchozí hodnota je Alfa, šlo by do enum přidat Default = Alfa (takže Options.Default by vrátilo stejnou hodnotu jako Options.Alfa tj. 1 v tomto případě).

Nicméně v tomto případě asi i chceme, aby hodnota "nezadáno" byla zároveň i výchozí, což je splněno automaticky opět ve variantě 1 i 2.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Nechápete to. Jakákoliv přiřazená hodnota z výčtového typu je platná hodnota, když je v tom Nothing (null), není tam žádná hodnota. Proto je taky u Nullable typů vlastnost HasValue, která to určuje. Přidáním nějaké další "None" hodnoty do výčtového typu tím akorát projekt zaprasíte věcmi, které vůbec nejsou potřeba. Udělali to proto, že do hodnotových (value) narozdíl od referenčních (reference) typů nešlo přiřazovat Nothing (null).

nahlásit spamnahlásit spam -2 / 4 odpovědětodpovědět

Ale ne, nebojte, chápu to. A velmi dobře. Jen to domýšlím mnohem dál než vy.

Nemusíte sem psát co a k čemu jsou Nullable typy. Že když máme třeba int, u kterého jsou si všechny hodnoty jeho rozsahu (-1, 0, cokoliv jiného) navzájem rovny, tak není příliš vhodné (i když principiálně možné) jednu z nich volit pro speciální hodnotu označující "neznámá hodnota"/"neurčeno"/"nezadáno" (prostě null) (ve VB.NET tomu říkají Nothing, protože na to stejně jako u C# použili stejné klíčové slovo, které u referenčních typů označuje referenci, která neukazuje na žádný objekt, ale s null/Nothing referencí to nijak nesouvisí), a proto použijeme monad Nullable<T>, který k hodnotám hodnotového typu T přidá hodnotu null. To ví každý.

My tu ale řešíme Enum. A navíc konkrétní modelový případ, kdy nás u něho zajímají pouze hodnoty 1 (Alfa) a 2 (Beta) a dále potřebujeme hodnotu s významem "nezadáno". Vy na to mechanicky a slepě aplikujete to co jsem výše uvedl pro int (výjde vám varianta 2) a máte hotovo. Je to ale pro tento případ jediné správné řešení? A pokud ne, není jiné řešení dokonce lepší?

Enum tak jak je v jazyce C# a v .NETu interně implementován je hodnota nějakého "underlying" celočíselného numerického hodnotového typu (pokud ho neuvedeme je jím int, ale může to být i byte, short nebo long), pouze s tím rozdílem, že jeho některé námi zvolené hodnoty pojmenujeme a můžeme se na ně pak tím zvoleným jménem odkazovat. Nikdo nám ale nikdy nezaručí, že v proměnné, která je typu Enum, nebudou jiné hodnoty jeho "underlying" typu.

Navíc "underlying" typ Enumu je hodnotový typ, což znamená, že i Enum je hodnotový typ. A u každého hodnotového typu nutně existuje "prázdná" hodnota, která odpovídá tomu, když obsah jeho paměti obsahuje pouze nuly (u Enumu tedy 0).

A mimochodem, aby jsme uměli jednoduše testovat, zda nemá emum prázdnou hodnotu, říká guideline, že máme hodnotu 0 u každého Enumu rovnou pojmenovat např. právě None. A pak můžeme psát:

Options[] array = new Options[10];

Debug.Print(array[0] == Options.None);

místo

Debug.Print((int)array[0] == 0);

Nyní zpět k našemu modelovému případu.

Když každý Enum má stejně hodnotu 0/None, kterou my konkrétně pro Alfa ani Beta nevyužíváme, nebylo by správnější rovnou tuto hodnotu použít pro reprezentaci hodnoty "nezadáno", místo zavádění hodnoty null? Navíc potřebujeme zde umět 0 a null odlišit?

Ne, naopak, pokud budeme "nezadáno" reprezentovat v DB jako 0 budeme muset nějak zajistit, že se 0 bezpodmínečně vždy převede na null (pokud zase bude v DB null, budeme muset CHECK constraintem zajistit, že do db nepůjde uložit 0).

Takže použití 0/None je zajisté jednodušší a přitom to vypadá, že nic oproti řešení s null neztrácí.

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

To se to snažíte vysvětlovat sám sobě, nebo co vás vede k psaní takových zcela zřejmých věcí?

Problém je jasný. Pokud je potřeba zjišťovat nezadáno/zadáno cokoliv, použít Nullable Enum. Pokud je potřeba zjišťovat konkrétní hodnotu, stačí pouze Enum. Je to tak jednoduché, že není potřeba to ničím dalším obhajovat.

Příliš se soustředíte na bezvýznamné hovadiny, které nijak neovlivňují aplikaci. Když jsem začínal s programováním, nad podobnými nesmysly jsem také přemýšlel dlouho, takže vás chápu.

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Děkuji za váš názor, nicméně:

Sám programuju už hodně dlouho, takže jsem to za tu dobu v praxi v různých stejných nebo mírně odlišných případech použil minimálně 50x ve variantě 1 i 50x ve variantě 2.

Tento dotaz byl směřován na to jak to dělat z hlediska posledních guidelinů, coding rules a psaní čistého kódu obecně. To jsou záležitosti, kde asi nikomu moc nepomůžete.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Není zač. Pravidly se řídím také, nicméně když si něčím nejsem jist, použiju raději vlastní hlavu než se slepě držet nějakého obecně doporučovaného postupu. Leccos dokáže posoudit i FxCop, jehož funkčnost je integrována v lepších verzích Visual Studia.

nahlásit spamnahlásit spam 0 / 2 odpovědětodpovědět

Já myslím, že je to jedno. Nepodařilo se mi najít nějaké exaktní pravidlo, podle kterého určovat variantu použití.

Obvykle to rozhoduji podle toho, aby to tak nějak dávalo větší smysl a bylo jednodušší na používání v té dané situaci.

To může znamenat i to, že například nějaký framework neumí dobře pracovat s null hodnotou a tak volím Enum hodnotu "None" nebo naopak chci dát možnost s hodnotama automatizovaně zacházet ve smyslu "je to null, není to vyplněno", tak použiju Nullable<>.

Jednou jsem viděl použití v případu, kdy nějaký serializér chtěl mít výchozí hodnotu "Unknown", protože to mělo v tom daném případě prostě smysl.

Nebo často vídám výčtovou návratovou hodnotu nějaké funkce - z pohledu použití je příjemnější výstup { Nothing, Something, SomethingElse }, než nějaká null hodnota a { Somethnig, SomethingElse }. Z pohledu programátora je stav Nothing plnohodnotný stav, který říká, že se operace vykonala, ale nic se například nemuselo ukládat. Dávat místo ní null by mi nepřišlo správné a komplikovalo by to zápis.

Těch možností je celá řada a dost pochybuju, že to někdo dokáže nějak rozumně popsat.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Osobně používám obojí a ne vždycky je to úplně jedno - každý má trošku jiný význam

None je pro mě něco, k čemu se chovám myšlenkově podobně jako k Alfa a Beta. Tedy např. si to hodím do switche a pro každou možnost mám +- stejně odlišný kód.

Null používám, když se potřebuju k té hodnotě chovat o dost jinak než k ostatním dvoum. Prostě je nějak speciální. Příklad je třeba filtr - Alfa a Beta jsou hodnoty, pomocí kterých filtruji, null je pak ve významu "cokoliv". Druhý příklad je kdy null pro mě znamená "nevím", což pro mě v konkrétním případě znamená diametrálně odlišné chování. Zatřetí null může znamenat defaultní hodnotu (tím nemyslim syntakticky default, ale logicky, tj. např nějakou hodnotu z nastavení aplikace, která se může průběžně měnit), nebo třeba že se mám podívat do nadřazeného prvku a vzít hodnotu odsud. Což je o dost odlišné od ostatníh možností.

Takže pro mě někdy dává smysl mít jak None tak null najednou. A někdy ne. Generalizovat se asi moc nedá, beru to "na citovku" :)

Případ options je třeba to, kdy si dokážu představit mít obě: null znamená "uživatel nic nenastavil a použiju default/inherited". None je, že si nastavil, že nic nechce, což nemusí být default. Pak vedle toho můžu mít OptionsEffective { get { return Options ?? GetFromParent(); }} a ta už nebude nullable.

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • 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ř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