Avatar billede dennism Nybegynder
03. december 2008 - 23:33 Der er 23 kommentarer og
2 løsninger

C#: Accessor og Mutator

Jeg er lidt usikker på, hvordan man smartest kan lave Accessor og Mutator metoder i C# (.NET 3.5).

Her kommer lige lidt kode:

    interface Person {
        bool Name {
            get;
            set;
        }
    }

    class Student : Person {
        bool Name {
            get;
            set;
        }
    }

Er det måden man skal bygge det op på?
Og så sætte navnet sådan (mutator):
    student.Name = "Per";

Og så hente navnet sådan (accessor):
    label.Text = student.Name;

Er det den rigtige måde at gøre det på? Som det kører der, er Name en feltvaribel, men kan man lave det samme, hvor det er en metode? Så man f.eks. kan hente sådan student.Name();

Jeg kommer fra en Java-verden, så er meget vant til getName() og setName(). Men kan lidt fornemme, at det ikke er måde man gør det på i C#?

Håber der er nogen der kan hjælpe mig lidt her :)
Alle kommentarere er velkomne!
Avatar billede arne_v Ekspert
04. december 2008 - 01:09 #1
Det er måden at gøre det på.

Og det er ikke felter men properties.

Properties svarer til Java get og set.
Avatar billede wster Nybegynder
04. december 2008 - 01:11 #2
Properties er en af de store forskelle fra java og C# og så er det ikke ret meget mere end et compiler trick.
Tag udgangspunkt i denne kode

    public class Kunde
    {
        string _navn;
        int _alder;

        public Kunde()
        {

        }

        public string Navn
        {
            get { return _navn; }
            set { _navn = value; }
        }

        public int Alder
        {
            get { return _alder; }
            set { _alder = value; }
        }

    }
Der er her en klasse, kunde, som indeholder et navn og en alder. I Java ville man typisk lave en set-metode og en get-metode for at kunne tilgå disse variabler udefra. I c# er der mulighed for at lave properties. Det giver reelt denne forskel, som du også selv beskriver:
[i JAVA]
//sætter alderen til kundens alder
int alderen = kunde.getAlder();
//sætter kundens navn til søren
kunde.setNavn("søren");
//inkrementerer kundens alder (det er hans fødselsdag)
kunde.SetAlder(kunde.getAlder() + 1);
[i C#]
//sætter alderen til kundens alder
int alderen = kunde.Alder;
//sætter kundens navn til søren
kunde.Navn = "søren";
//inkrementerer kundens alder (det er hans fødselsdag)
kunde.Alder++;

Det er oftest nemmere at skrive properties end det er at lave get og set metoder og så kan man argumentere for at det giver kønnere kode (specielt i sidste eksempel hvor kundens alder inkrementeres).
Men hov - formålet med at man laver get og set metoder er jo muligheden for at kontrollere variabler og f.eks forhindre at alderen sættes til -9. I java giver det sig selv at man så laver setmetoden således:
public void setAlder(int nyAlder)
{
    if(nyAlder >= 0)
        alder = nyalder
    else
        throw new InvalidAgeException();
}

Det kan man ikke med properties, eller hvad?

Det kan man godt, for man kan betragte det der står mellem tuborg efter set, som setmetodens kode. Lige nu sætter den bare _alder lig med hvad end der står i value (value er et reserveret ord og svarer til den værdi man forsøger at 'sætte' til, her er value f.eks 9: kunde.Alder = 9;), men skal der validering til kan det også gøres meget enkelt:

public int Alder
{
  get { return _alder; }
  set
  {
      if (value >= 0)
        _alder = value;
      else
        throw new InvalidAgeException();
  }
}

hvis jeg nu skriver:
kunde.Alder = -67;
vil jeg få en exception kastet i hovedet.

Microsoft introducerede noget nyt i deres compiler - en nemmere måde at skrive properties på. I mange tilfælde har man brug for at lave en property uden validering, som det er tilfældet i det første kode. Derfor har man lavet en nemmere måde at gøre det på og jeg kunne i stedet for det første kode have skrevet

    public class Kunde
    {

        public Kunde()
        {

        }

        public string Navn
        {
            get;
            set;
        }

        public int Alder
        {
            get;
            set;
        }

    }

Det der sker her er at programmet opretter nogle variabler til mig, en string og et navn - hver gang en af disse properties anvendes, sættes eler gettes den variabel. Når du skal tilgå disse variabler inden i klassen, bruger du property navnet dvs. Navn = "lars";
Det er det samme du gør i dit eget eksempel og det er som sagt bare en nemmere måde at skrive disse properties på.

Du kan ikke skrive student.Name(), hvis Name er en property - men der er ingen som forhindrer dig i at lave en metode som hedder Name, hvorefter det vil være muligt. Det er bare den måde man normalt gør det i C#.

Jeg kommer selv fra en Java verden inden jeg prøvede kræfter med C# og jeg håber du kommer til at holde ligeså meget af properties, som jeg er kommet til.

God fornøjelse :)
Avatar billede arne_v Ekspert
04. december 2008 - 01:18 #3
public class Person
{
    public string Name
    {
        get;
        set;
    }
}

er en ny feature i C# 3.0 som fungerer lignende følgende kode i ældre C# versioner:

public class Person
{
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

og kiggre man på den genererede MSIL finder man:

  .field private string '<Name>k__BackingField'
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGenera
tedAttribute::.ctor() = ( 01 00 00 00 )
  .method public hidebysig specialname instance string
          get_Name() cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGene
ratedAttribute::.ctor() = ( 01 00 00 00 )
    // Code size      11 (0xb)
    .maxstack  1
    .locals init (string V_0)
    IL_0000:  ldarg.0
    IL_0001:  ldfld      string Person::'<Name>k__BackingField'
    IL_0006:  stloc.0
    IL_0007:  br.s      IL_0009

    IL_0009:  ldloc.0
    IL_000a:  ret
  } // end of method Person::get_Name

  .method public hidebysig specialname instance void
          set_Name(string 'value') cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGene
ratedAttribute::.ctor() = ( 01 00 00 00 )
    // Code size      8 (0x8)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldarg.1
    IL_0002:  stfld      string Person::'<Name>k__BackingField'
    IL_0007:  ret
  } // end of method Person::set_Name

og:

  .field private string name
  .method public hidebysig specialname instance string
          get_Name() cil managed
  {
    // Code size      12 (0xc)
    .maxstack  1
    .locals init (string V_0)
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldfld      string Person::name
    IL_0007:  stloc.0
    IL_0008:  br.s      IL_000a

    IL_000a:  ldloc.0
    IL_000b:  ret
  } // end of method Person::get_Name

  .method public hidebysig specialname instance void
          set_Name(string 'value') cil managed
  {
    // Code size      9 (0x9)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldarg.1
    IL_0003:  stfld      string Person::name
    IL_0008:  ret
  } // end of method Person::set_Name

så det ender altså op med at være metoder alligevel.
Avatar billede dennism Nybegynder
04. december 2008 - 09:16 #4
Tak for dit meget lange svar!

Jeg har kun et enkelt spørgsmål tilbage:
Er det den rigtige måde, hvorpå jeg i mit interface angiver at der skal implementeres en property - eller kan det gøres smartere? Det er jo faktisk den samme kode som der står i klasse som implementerer interfacet.

Skal også lige vænne mig til, at man skal bruge : for både implements og extends. Men synes egentligt ikke så godt om det, da man ikke umiddelbart kan se, om en klasse implementerer noget eller extender noget - så er man nødt til at skulle over og kigge i den klasse som der står. Er det ikke korrekt?
Avatar billede dennism Nybegynder
04. december 2008 - 10:45 #5
Der er dog en ting jeg ikke helt kan gennemskue. Lad os antage noget lignende dette:

    class Kunde {

        public Kunde() {
            this.IsPartner = false;           
        }

        public bool IsPartner {
            get;
            set;
        }
    }

Da jeg gerne vil have, at IsPartner er false, med mindre jeg har sat den til andet, har jeg smidt en accessor i konstruktøren. Men er der ikke en smartere måde man kan gøre det på? Hvis jeg f.eks. har en klasse med 5 konstruktører, er det lidt træls at skulle sætte den i alle 5. Jeg har prøvet med noget lignende dette:

        public bool IsPartner = false {
            get;
            set;
        }
Avatar billede arne_v Ekspert
04. december 2008 - 15:10 #6
Hvis du bruger den gamle form kan du initialisere feltet uden om constructor.

Med den nye form tror jeg at du skal bruge constructor, men husk at du kan lade en
constructor kalde en anden constructor.
Avatar billede arne_v Ekspert
04. december 2008 - 15:11 #7
Jeg mener at dit interface er korrekt.

Jeg kan vaere meget enig i extend implements - i praksis lader man i .NET altid
interfaces navne starte med I, saaledes at man ved at IPerson er et interface og
ikke en klasse.
Avatar billede dennism Nybegynder
04. december 2008 - 15:26 #8
Tak for det tip med at et interface starter med I. Gør man så noget specielt med abstrakte klasser?
Avatar billede arne_v Ekspert
04. december 2008 - 15:33 #9
Nej.
Avatar billede dennism Nybegynder
04. december 2008 - 15:48 #10
Tak. Du må gerne smide et svar:)

Har du arbejdet med Test i Visual Studio 2008? Da jeg ikke har tid til at lave de test-cases som tester de kritiske punkter for en funktion i min kode, har jeg lavet én TestMethod som har en for-løkke som kører denne funktion 1000 gange. Jeg ved godt, at det ikke er ideen med test-cases at de skal indeholder løkker m.m.
Jeg tænkte, om det er muligt at fortælle den, at den skal køre f.eks. 1000 gange? Det vil jo også være smart, at der stod 1000 test-cases under "Test Results" vindue i stedet for den ene som der står nu.

Kan man lave det på en smart måde?
Avatar billede dennism Nybegynder
04. december 2008 - 15:48 #11
Jeg har i øvrigt lige smidt nogle flere point på.
Avatar billede arne_v Ekspert
04. december 2008 - 16:40 #12
Jeg har ikke brugt MS's egne test vaerktoejer. Jeg har leget en lille bitte smule
med NUnit.

Og svar.

Jeg regner med at wster faar en slat af poin ogsaa.
Avatar billede arne_v Ekspert
04. december 2008 - 16:41 #13
Unit tests kan ioevrigt godt indeholde loekker hvis der er grund til det.
Avatar billede dennism Nybegynder
04. december 2008 - 16:45 #14
Ja tak, afventer lige svar på ham også :)
Avatar billede wster Nybegynder
04. december 2008 - 17:51 #15
Det kommer så her :)
Avatar billede dennism Nybegynder
04. december 2008 - 21:11 #16
Jeg er meget usikker på, hvornår man skal bruge Properties og hvornår man skal bruge en alm. feltvariabel. Håber I kan fortælle mig lidt om det?
Avatar billede arne_v Ekspert
04. december 2008 - 21:28 #17
Du skal altid bruge private fields og public (eller ihvertfald ikke-private) properties.

Properties vil normalt daekke over fields (omend det med C# 3 syntaxen ikke er synligt).

Men nogle fields vil ikke have properties fordi de ikke skal exposes.
Avatar billede dennism Nybegynder
04. december 2008 - 21:37 #18
Så det vil sige, alt der skal bruges af andre klasser skal ligge i properties og interne ting skal ligge i fields?
Avatar billede arne_v Ekspert
04. december 2008 - 21:41 #19
Nej.

Det der kun skal bruges internt ligger som private field.

Det der skal bruges eksternt ligger som public property og private field.

Ganske som i Java bortset fra at properties hedder get og set metoder.

I C# 3.0 kan du saa bruge den forkortede syntax for properties hvor C# selv opretter
et field bag properties.

Det faar det til at se ud som oder kun er en public property og ikke noget field.
Avatar billede dennism Nybegynder
04. december 2008 - 22:18 #20
Ok, tak :) Metoder og Properties skrives med stort begyndelsesbogstav, korrekt? og private field er med småt?
Avatar billede arne_v Ekspert
04. december 2008 - 22:44 #21
Jeps.

Og metodenavne starter ogsaa med stort.
Avatar billede dennism Nybegynder
08. december 2008 - 23:09 #22
Hvordan ser så selve strukturen i selve klassen? Først private feltvariabler, konstruktør, properties og metoder? ..eller?
Avatar billede arne_v Ekspert
08. december 2008 - 23:20 #23
Jeg kender ikke nogen endegyldig guide til dette.

Med C# 2.0 syntax vill jeg nok lave det i den rækkefølge du nævner.

Med C# 3.0 syntaxen ville jeg rykke properties op over constructor, hvis nu constructor
skal sætte nogle værdier.
Avatar billede kastermester Nybegynder
10. december 2008 - 18:45 #24
Ville da lige knytte en kommentar ang. abstrakte klasser - har aldrig hørt om en officiel navngivning af dem, men jeg synes at have lagt mærke til at de ofte fra Microsoft's side (I hvert fald I den nyere del af deres kode, som f.eks. MVC frameworket til ASP.Net) lader abstrakte klasser ende på "Base", altså f.eks. PersonBase - hvilket vel også giver meget god mening? Dog er det ikke noget der på nogen måde er lige så gennemført som stort I ved interfaces, jeg tror de fleste fra .Net verdenen ville kigge mærkeligt efter en hvis man ikke benyttede sig af den "regel".
Avatar billede dennism Nybegynder
11. december 2008 - 08:27 #25
Tak for jeres hjælp her!

I må undskylde at jeg spammer her, men sidder med et akut problem, som jeg meget gerne vil have en løsning på. Håber I kan hjælpe mig her:
http://www.eksperten.dk/spm/856076
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester