Søg på DotNyt:
Denne blog er flyttet til www.nielsbrinch.com


lørdag den 27. september 2008

Alt om brug af enums

skrevet af Niels Brinch

Jeg har efterhånden arbejdet med mange projekter hvor der indgår enums på den ene eller den anden måde og jeg synes godt jeg kan komme med en kvalificeret anbefaling af hvordan de anvendes.

Men først det basale.

Hvad er en enum?

Man kan tænke på det som en afgrænset mængde valgmuligheder. I eksemplet herunder repræsenterer min enum de forskellige måder brugeren kan vælge at få sorteret søgeresultater.

public enum SortOptionType

{

    MostRecent = 0,

    Popular = 1,

    TalkedAbout = 2,

    HotRightNow = 3

}

Navnet slutter på "Type" i ovenstående eksempel. Det er der ingen konvention der siger den skal, men det plejer at give god mening at kalde den det - for det er typisk det den er. Men fald derimod ikke for fristelsen og giv den et flertalsnavn. Det giver kun mening når du lige sidder og skriver din enum, for så skriver du jo flere forskellige typer - men det giver bedre mening med ental senere hen når din enum anvendes.

Man behøver i øvrigt ikke eksplicit at skrive tal ud for hver enum-værdi, men det er god praksis at gøre hvis man vil konvertere mellem enums og tal, hvilket ofte kan være relevant - mere om det senere.

Hvordan anvendes den?

Enum-værdier er meget lette at sammenligne med hinanden og det giver let læselig kode. Hver enum-værdi repræsenterer et tal (oftest) - man kunne vælge bare at sammenligne talværdierne i stedet, men dagen efter har programmøren glemt hvad tallene betød.

For at gøre denne guide komplet, starter jeg fra bunden og viser hvordan man laver en property på en klasse til at gemme sin enum - men det gøres ligesom alle andre datatyper.

public class Navigation

{

    private SortOptionType sortOptionType;

 

    public SortOptionType SortOptionType

    {

        get { return sortOptionType; }

        set { sortOptionType = value; }

    }

}

Jeg anbefaler at navngive propertyen nøjagtig det samme som enum'en hedder - som i eksemplet herover, med mindre der er en god grund til at lade være.

Sådan assignes den valgte enum til propertyen.

navigation.SortOptionType = SortOptionType.MostRecent;

Her er et eksempel hvor jeg har anvendt ovenstående enum.

// Make the headline text to explain what is viewed

StringBuilder headline = new StringBuilder();

 

switch (navigation.SortOptionType)

{

    case SortOptionType.MostRecent:

        headline.Append("Most recent");

        break;

    case SortOptionType.Popular:

        headline.Append("Most popular");

        break;

    case SortOptionType.TalkedAbout:

        headline.Append("Most talked about");

        break;

    case SortOptionType.HotRightNow:

        headline.Append("Most hot right now");

        break;

}

Hvordan konverteres den?

En af de store styrker ved en enum er at den er en simpel datatype som let kan konverteres til tal og tilbage igen. Det er relevant i to situationer:

  • Hvis enum-værdien skal gemmes i databasen, er det fornuftigt at gemme tal-værdien af din enum - både fordi tallet er kortere end teksten, men bestemt også fordi databasen er langt bedre til at sortere og søge på tal end på tekst.
  • Hvis enum-værdien skal vælges af brugeren i brugergrænsefladen, skal den konverteres til enten tekst eller et tal - jeg anbefaler tal fordi det er lettere at arbejde med - og hverken du, som programmør, eller brugeren behøver nogensinde at blive præsenteret for tallet. Det kan være valg-værdierne i en rullemenu.

Så her er lidt konverteringer:

SortOptionType sortOptionType = SortOptionType.MostRecent;

 

// En enum konverteres til int ganske enkelt med et cast.

int i = (int)sortOptionType;

 

// Og det gælder også den anden vej.

sortOptionType = (SortOptionType)i;

 

// En enum kan let konverteres til streng, med ToString().

string str = sortOptionType.ToString();

 

// Men fra tekst til enum er knap så elegant

sortOptionType = (SortOptionType)Enum.Parse(typeof(SortOptionType), "MostRecent");

Her er i øvrigt en hyggelig lille metode til at vise alle enum-valgmuligheder i en rullemenu:

drdSortOptions.DataSource = Enum.GetNames(SortOptionType);

drdSortOptions.DataBind();

Placering i projektet

Følgende er de tre steder som udviklere typisk lægger sin enums - jeg anbefaler to af dem og forklarer hvorfor:

  1. "Den skal da ligge i den klasse hvor den hører til!":

    public class Navigation

    {

        // Klassens øvrige indhold

     

        public enum SortOptionType

        {

            MostRecent = 0,

            Popular = 1,

            TalkedAbout = 2,

            HotRightNow = 3

        }

    }


    Problemet med dette valg er, at en enum viser sig at være relevant i forhold til flere klasser og så ved programmøren pludselig ikke hvor den er. Jeg vil da ikke helt udelukke det KAN være fornuftigt, men jeg er aldrig stødt på et eksempel hvor det var en god idé.

  2. "Nej ok, men så placerer vi da bare alle vores enums i samme klasse!":

    public class Enums

    {

        public enum SortOptionType

        {

            MostRecent = 0,

            Popular = 1,

            TalkedAbout = 2,

            HotRightNow = 3

        }

    }


    Ikke dumt. Så kan man altid finde sin enum. Man skriver bare Enums. og får så en liste af alle de enums som eksisterer. Og har man rigtig mange enums i et meget stort projekt, kan man lave flere samlingsklasser og kategorisere dem på den måde.

    ...men hvordan bliver syntaksen så når man anvender sin enum?

    if (sortOptionType == Enums.SortOptionType.MostRecent)


    Der skal stå "Enums." frygteligt mange steder i koden, hvilket ikke gør det mere læsevenligt, hvilket ellers var idéen med en enum. Stadig, det er fint at placere sine enums i en klasse til det formål, men koden kan altså godt blive mere læsevenlig med metode 3.

  3. Man behøver ikke skrive noget foran sin enum når man anvender den hvis man lægger den uden for klasse. Jo, opret en klasse, men fjern klasse-definitionen og skriv bare enum-definitioner direkte deri.

    using System;

     

    namespace PopBusiness

    {

        public enum SortOptionType

        {

            MostRecent = 0,

            Popular = 1,

            TalkedAbout = 2,

            HotRightNow = 3

        }

     

        public enum ImageQuality

        {

            NotUsable = 0,

            TooSmall = 1,

            SeemsBad = 2,

            SeemsOk = 3,

            SeemsGood = 4

        }

    }

Men en lille advarsel! Metode 3 anbefaler jeg for læsevenlighedens skyld, men kommer du til at give en enum samme navn som noget der f.eks. eksisterer i .NET frameworket, så beder du om ballade. Der vil konstant komme misforståelser om hvor vidt du mente det ene eller det andet. Hvis du helt vil undgå den tvivl, så anvend metode 2.

Jeg vælger metode 3 fordi jeg går op i læsevenligheden af min kode, hvilket alle der anvender enums gør.

2 kommentarer

torsdag den 25. september 2008

Fil-baseret lagring på Windows Mobile

skrevet af Niels Brinch

Eftersom jeg er måneden blogger i september må jeg hellere få skrevet mindst ét indlæg i september :)

Jeg skrev for en måneds tid siden om at programmere til min mobiltelefon. Jeg skitserede en database-baseret løsning, men noterede i en bisætning, at jeg endte med ikke at anvende det. Grunden er, at det umiddelbart krævede .NET 3.5. Det er svært at vurdere, men jeg vil gætte på man i skal lave telefon-applikationer til .NET 2.0 i hvert fald et år endnu - lidt afhængigt af målgruppen, selvfølgelig. Derfor er det kun fair jeg præsenterer den noget mindre glamourøse filbaserede måde at gemme filer på telefonen.

Der var to måder jeg kunne vælge at lave fil-baseret håndtering af gemte indstillinger:

  1. Tekstfiler
  2. XML-filer

XML er nemt, men i dette tilfælde er tekstfiler nemmere. Jeg valgte den nemmeste løsning, hvilket jeg generelt går ind for at gøre. Det 'rigtigste' ville måske være at gemme en xml-fil ala app.config og anvende den - også så man kan gemme lidt kontekst med indstillingerne - men jeg valgte altså det noget nemmere, nemlig at anvende en tekst-fil for hver indstilling, hvor indstillingens nøgle ganske enkelt er filens filnavn. Enkelt, hurtigt og det virker bare.

Min generelle klasse til at håndtere indstillinger, indeholder tre metoder:

GetFilePath, som henter applikationens installationssti dynamisk ved at anvende lokationen af programmets exe-fil.

public static string GetFilePath(string fileName)
{
string filePath = Assembly.GetExecutingAssembly().GetName().CodeBase;
filePath = filePath.Replace("Timer.exe", fileName);

return filePath;
}

Den metode skal jeg bruge hver gang jeg gemmer eller henter en applikation. Herefter skal jeg bruge to metoder til henholdsvis at gemme indstillinger og hente indstillinger. Jeg har lavet dem så de hver gang tjekker om filen eksisterer og håndterer det pænt. På den måde behøver der ikke være nogen procedure til initialisering af indstillingerne eller lignende - man kan bare begynde at anvende dem.


SetSetting, sørger for at oprette filen hvis den ikke allerede findes og gemmer ganske enkelt den streng som den modtager. Ja, det er en streng, ikke et object. Jeg kunne have valgt at modtage et object som så skulle serialiseres, men jeg havde ikke brug for det og man kommer altså langt med en streng. Som sagt, jeg vælger tit den nemmeste løsning.


private static void SetSetting(string key, string value)
{
string filePath = GetFilePath(key + ".txt");

StreamWriter writer = File.CreateText(filePath);
writer.Write(value);
writer.Close();
}

Det er heldigvis så smart at man bare har skriverettigheder til applikationens eget bibliotek. Jeg er webudvikler og er vant til at webapplikationen skal have rettighed til at skrive i mappen hvis man skal anvende filsystemet. Det er som sagt ikke tilfældet på en telefon. Heldigvis.


GetSetting, sørger for at hente tekststrengen ud fra filen, men returnerer null hvis filen ikke eksisterer. På den måde kan programmøren kende forskel på om indstillingen er "tom" eller "ikke-eksisterende", hvilket er rart at vide, hvis man f.eks. vil have brugeren til at tage stilling til indstillingen.


private static string GetSetting(string key)
{
string filePath = GetFilePath(key + ".txt");

if (!File.Exists(filePath))
{
return null;
}
else
{
StreamReader reader = File.OpenText(filePath);
string value = reader.ReadToEnd();
reader.Close();

return value;
}
}

Næste skridt ville som sagt være serialisering og deserialisering af objekter og selvfølgelig at gemme i en XML-fil i stedet - men slap af. Der er allerede databaseunderstøttelse i .NET 3.5 og det bliver udbredt i løbet af 1 til 2 år. Indtil da fungerer mit filbaserede lagringssystem fint.


Her er hele klassen for en god ordens skyld, så andre let kan anvende den.


using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Data;
using System.IO;

namespace Timer
{
public static class Settings
{
public static string GetFilePath(string fileName)
{
string filePath = Assembly.GetExecutingAssembly().GetName().CodeBase;
filePath = filePath.Replace("Timer.exe", fileName);

return filePath;
}

private static string GetSetting(string key)
{
string filePath = GetFilePath(key + ".txt");

if (!File.Exists(filePath))
{
return null;
}
else
{
StreamReader reader = File.OpenText(filePath);
string value = reader.ReadToEnd();
reader.Close();

return value;
}
}

private static void SetSetting(string key, string value)
{
string filePath = GetFilePath(key + ".txt");

StreamWriter writer = File.CreateText(filePath);
writer.Write(value);
writer.Close();
}
}
}

0 kommentarer


 
Til forsiden

Niels Brinch

- Seneste indlæg