Gå til indholdet

Klasser

En klasse i C# kan skabes med class-kodeordet, og med definitionen angives samtidig klassens synlighed:

[synlighed] class [navn]
{
  // medlemmer
}

Synligheden kan enten være internal, som fortæller, at klassen kun er synlig i det projekt, den er defineret, eller public som fortæller, at andre projekter med en reference til projektet, hvor klassen er defineret, kan se og benytte klassen. Hvis du ikke angiver noget, er den som standard internal. Men til at starte med bør du angive synligheden, så den er tydelig for dig selv og andre, der arbejder med koden.

Information til undervisere

Det vigtigste er, at kursisterne forstår, at en klasse er en type de selv kan skabe og eftefølgende bruge. Her introduceres (offentlige) felter, brug af this, metoder, konstruktører og brugerdefinerede konstruktører. Det er vigtigt, at kursisterne forstår, at en klasse er en type, og at de kan skabe objekter af klassen. Det er også vigtigt, at de forstår, at en klasse er en referencebaseret type, og at de derfor skal skabe objekter med new.

De fleste udviklere benytter Microsofts navngivningsstandard der blandt andet fortæller, at klassenavnet skal starte med stort og derefter benytter camelcasing (hvert ord starter med stort bortset fra det første), men det er helt op til dig.

Her er et par eksempler:

internal class Faktura 
{
}

internal class Person
{
}

internal class Terning 
{
}

internal class PointTavle
{
}

Klassen bør defineres på namespace-niveau, så hvis du tilføjer klasser til en konsol-applikation, bør den placeres på linje med Program-klassen:

using System;

namespace Demo
{
    internal class Program
    {
        public static void Main()
        {
        }       
    }

    internal class Terning { 

    }

}

Bemærk, at klassen er placeret i Demo-namespacet på samme niveau som Program-klassen.

De fleste vælger dog at placere en klasse (eller andre typer) i en fil for sig selv med samme navn som typen. Det kan du nemt gøre i Visual Studio ved at højreklikke på projektet i Solution Explorer-vinduet, vælge Add… og herefter Class. I Visual Studio Code skal du blot tilføje en cs-fil eller benytte en extension, der kan hjælpe. Om du placerer alle typer i en og samme fil eller i hver sin fil er under-ordnet – kompileren vil sørge for at kompilere alle filer sammen.

Info

I VS og VSC kan du altid flytte en type til en ny fil ved at klikke på typenavnet, finde det lille lyspære-ikon og vælge ”move type to …”

Oprettelse af objekter

Oprettelse af objekter sker ved hjælp af new-kodeordet, hvilket vil oprette et objekt (kaldes også en instans) i hukommelsen. Da en klasse er en referencebaseret type, kan referencen gemmes i en variabel. Du kan enten benytte en eksisterende variabel eller erklære en ny. I grundlæggende C# kan du blot benytte en variabel af den konkrete type, men i mere avanceret kode kan du begynde at udnytte objektorienterede muligheder som eksempelvis arv eller interface – mere om det senere i bogen.

Lige nu skal du blot gemme et objekt i en variabel af samme type.

Her er et par eksempler, der tager udgangspunkt i en klasse Terning:

// Erklær en variabel der kan holde referencen 
// til et objekt af typen Terning
Terning a;

// Opret et nyt objekt og gem referencen i a
a = new Terning();

// Erklær en variabel der kan holde referencen 
// til et objekt af typen Terning, og opret
// objekt med det samme
Terning b = new Terning();

// Erklær en variabel der kan holde referencen
// til et objekt af typen Terning, og tildel
// variablen referencen fra b
Terning c = b;
// Nu er reference i c og b ens - de peger
// på samme objekt i hukommelsen

I et senere kapitel kommer vi meget mere i dybden omkring hukommelsesteori, men det er vigtigt, at du ser oprettelse af et objekt som tre separate operationer:

Terning a = new Terning();

Denne linje skal du læse som

  • Erklæring af en variabel
  • Oprettelse af et nyt objekt med new
  • Tildeling af reference fra det nye objekt til variablen.

Hvis du læser det på den måde, er det lidt nemmere at forstå.

I nyere C# kan du vælge at bruge en lidt nemmere syntaks:

Terning a = new();

Info

Bemærk, at brugen af new() er en C# 9 feature!

Kompileren er smart til at udlede, at a tildeles referencen til et nyt objekt af typen Terning, men som begynder kan det være en fordel at skrive, hvilken type du ønsker en ny instans af. Det er lidt mere logisk at skrive og læse.

Medlemmer fra System.Objekt

Selv om du skaber objekter af helt tomme klasser, vil du måske undre dig over, at der alligevel findes nogle metoder på objektet, hvis du kigger på det i Visual Studio.

Metoder fra System.Object

Det skyldes, at alle typer automatisk har fire metoder fra den øverste klasse i .NET kaldet Object. Det er sjældent, du i grundlæggende C# vil bruge andre metoder end metoden ToString, som har til formål at skabe en streng, der beskriver objektet bedst muligt. Senere i bogen vil du lære at skrive din egen implementering af denne metode, men indtil da vil metoden blot returnere typenavnet.

Felter

Du kan opfatte felter i typer (klasser og strukturer) som muligheden for at opbevare data. Du kan også kalde felter for typens variabler.

De er det eneste, der adskiller objekter af samme type fra hinanden idet alle andre medlemstyper (metoder, egenskaber og hændelser) er nøjagtig ens og i virkeligheden kun eksisterer et sted i hukommelsen. Objekterne indeholder blot en reference til dem.

Info

Felter er objekternes data.

Felter kan blandt andet erklæres som private (private) eller offentlige (public). Offentlige felter kan tilgås både udefra og inde fra selve objektet, medens private blot kan tilgås inde fra objektets andre medlemmer. Her er eksempler på forskellige klasser med offentlige felter:

internal class Terning {
    public int Værdi;
}

internal class TerningeBæger {         
    public Terning[] Terninger;
    public DateTime SidstRystet;
}

internal class Person {
    public string Navn;
    public int Alder;
    public bool ErDansk;        
}

Når der oprettes objekter af klasserne, bliver felterne automatisk tildelt typernes default værdier:

Person person1 = new Person();
// nu har 
// person1.Alder værdien 0
// person1.Navn værdien null
// person1.ErDansk værdien False

Men du kan naturligvis tildele felterne andre værdier:
Terning terning = new Terning();
terning.Værdi = 5;

TerningeBæger bæger = new TerningeBæger();
bæger.SidstRystet = DateTime.Now;
bæger.Terninger = new Terning[5];

Person person1 = new Person();
person1.Alder = 14;
person1.Navn = "Mathias";
person1.ErDansk = true;

I nyere versioner af C# kan man både erklære, oprette og tildele i en og samme instruktion:

Person person2 = new Person() 
{ 
    Navn = "Mikkel", 
    Alder = 16, 
    ErDansk = true 
};

Det kan naturligvis også gøres på én lang linje:

Person person2 = new Person() { Navn = "Mikkel", Alder = 16, ErDansk = true };

Det er helt op til din kodestandard – kompileren er ligeglad.

Offentlige data

Du vil i virkeligheden aldrig erklære felter som offentlige, fordi du dermed ikke kan afvikle instruktioner, når der tildeles og aflæses værdier. Du er nødt til at kunne indkapsle data og styre tilgangen, og som du skal se senere, kan dette ske ved at gøre felter private, og tilføje offentlige medlemmer, som blot skaber adgang til felterne.

Som eksempel kan du tænke på din Terning:

internal class Terning {
    public int Værdi;
}

Hvis der skabes en instans af terningen, kan værdi tildeles hvad som helst, så længe der er tale om lovlige værdier for en Int32:

Terning t = new Terning();
t.Værdi = -1;

Som udvikler af klassen Terning er det ikke så smart, at du ikke ved, hvornår værdi tildeles og aflæses, og i eksemplet er terningens værdi blevet tildelt -1, hvilket jo ikke giver nogen mening. Men heldigvis kan vi sikre felterne på anden vis, som du skal se senere.

Info

Jeg plejer at sige til mine kursister, at offentlige felter er den direkte vej til en medarbejdersamtale. Det er naturligvis ment i spøg, men lige nu skal du bare forstå begrebet felter, og må (indtil du har læst om egenskaber og indkapsling) gerne gøre brug af offentlige felter. Men det bør være private felter som ikke kan tilgås udefra, der er normen.

Brug af this

Når du fra medlemmer i en klasse tilgår andre medlemmer, kan du vælge, om du vil benytte this-kodeordet:

internal class Terning
{
    public int Værdi;

    public void Ryst()
    {
        System.Random rnd = new Random();
        // Tilfældig værdi fra 1-6 
        this.Værdi = rnd.Next(1, 7);
    }
}

Koden bliver lidt mere logisk at læse, og Visual Studio kan hjælpe lidt mere ved at give dig forslag. Den ved jo, at der er tale om objektet selv, så brug af this giver en fin dropdown-liste med muligheder. Men kompileren er ligeglad, så denne kode er lige så god:

internal class Terning
{
    public int Værdi;

    public void Ryst()
    {
        System.Random rnd = new Random();
        // Tilfældig værdi fra 1-6 
        Værdi = rnd.Next(1, 7);
    }
}

Det er op til den kodestandard, du benytter, men jeg vil anbefale brugen af this, hvis du er ny i C#.

Metoder

Instans metoder i klasser er som nævnt typisk relateret til klassens felter i en eller anden form, og syntaksen på metoderne er, som du tidligere har set. Du kan vælge om en metode skal returnere en konkret værdi eller blot er en samling instruktioner til afvikling (void-metode).

Her er et par eksempler på metoder i klassen Terning:

internal class Terning
{

    public int Værdi;

    public void Ryst()
    {
        Random rnd = new Random();
        this.Værdi = rnd.Next(1, 7);
    }

    public string VærdiTilPrint()
    {
        return $"[ {this.Værdi} ]";
    }

    public void SkrivTilConsole(ConsoleColor farve)
    {
        ConsoleColor gammelFarve = Console.ForegroundColor;
        Console.ForegroundColor = farve;
        Console.WriteLine(this.VærdiTilPrint());
        Console.ForegroundColor = gammelFarve;
    }

    public bool ErSekser()
    {
        return this.Værdi == 6;
    }
}

Bemærk, at de alle i en eller anden form har med feltet Værdi at gøre, men det behøver ikke være tilfældet. Her er eksempler på brug af metoderne:

Terning t = new Terning();
t.Ryst();
Console.WriteLine(t.VærdiTilPrint());
t.SkrivTilConsole(ConsoleColor.Red);
Console.WriteLine(t.ErSekser());

Der er ikke syntaksmæssigt nogen forskel på disse metoder og de metoder, der er gennemgået tidligere. Du kan således bruge navngivne argumenter, argumenter med en standardværdi, overloads med videre. Konceptuelt er der dog den forskel, at kapitlet om metoder benyttede statiske metoder, og de metoder, der nævnes her, er instans metoder. Forskellen er, om en metode skal have adgang til instansens data, eller om den skal tilgås fra typen.

Her er klassen Terning med en instans metode Ryst (den skal have adgang til feltet Værdi) og en statisk metode FindTilfældigTerningVærdi, som blot er en hjælpemetode, som måske kunne være praktisk. Ved at kalde den, kan du finde en tilfældig værdi uden at skabe en instans:

internal class Terning
{

    public int Værdi;

    public void Ryst()
    {
        Random rnd = new Random();
        this.Værdi = rnd.Next(1, 7);
    }

    public static int FindTilfældigTerningVærdi() {
        Random rnd = new Random();
        return rnd.Next(1, 7);
    }
}

Her er begge metoder i brug:

Terning t = new Terning();
//Instans metode
t.Ryst();

// Statisk metode
int v = Terning.FindTilfældigTerningVærdi();

Bemærk at Ryst-metoden kan findes på instansen, men FindTilfældig-TerningVærdi-metoden kan findes på klassen Terning.

Standard konstruktør

Som tidligere nævnt er en konstruktør (constructor) kode, der afvikles, når der oprettes et objekt med new operatoren. Det bruges blandt andet til initialisering, log, sikkerhed og logik. En konstruktør ligner en metode, men der er ikke angivet nogen returværdi, og navnet på en konstruktør er det samme som navnet på klassen. Den mest simple konstruktør har ingen argumenter og kaldes for standard-konstruktøren (default constructor). Den kunne bruges i vores terning for at sikre, at feltet ikke har værdien 0, når der oprettes et objekt:

internal class Terning
{
    public int Værdi;

    // Konstruktør
    public Terning()
    {
        this.Værdi = 1;
    }
}

Når der oprettes et objekt, vil værdi automatisk blive tildelt 1, og dermed overskrive standardværdien på 0:

Terning t = new Terning();
Console.WriteLine(t.Værdi); // 1

Måske kunne det give mening, at terningens værdi blev tildelt et tilfældigt tal mellem 1 og 6, når den bliver oprettet:

internal class Terning
{
    public int Værdi;

    public Terning()
    {
        this.Ryst();
    }

    public void Ryst()
    {
        System.Random rnd = new Random();
        // Tilfældig værdi fra 1-6 
        // det sidste argument - 7 - er rigtig nok
        this.Værdi = rnd.Next(1, 7);
    }
}

Bemærk, at konstruktøren vises med et navn svarende til klassen selv – ligesom i koden.

Nu er værdien tilfældig ved oprettelse:

Terning t = new Terning();
Console.WriteLine(t.Værdi); // tilfældigt tal mellem 1-6

Her er et andet eksempel:

internal class TerningeBæger
{
    public Terning[] Terninger;

    public TerningeBæger()
    {
        this.terninger = new Terning[5];
        for (int i = 0; i < 5; i++)
          this.Terninger[i] = new Terning();
    }
}

Når der skabes en instans af klassen, bliver der oprettet et array som tildeles nye terninger, og hvis terningerne automatisk får en tilfældig værdi i konstruktøren i klassen Terning, vil de jo automatisk få en tilfældig værdi:

TerningeBæger b = new TerningeBæger();
// Fem terninger med tilfældige værdier
foreach (Terning terning in b.Terninger)
    Console.WriteLine(terning.Værdi);   

En konstruktør består typisk af initialiseringskode, men det er helt op til dig. Du må gerne tilføje eksempelvis sikkerhed, som sikrer, at man kun kan oprette et objekt, hvis man har rettigheder til det.

Tip

Du kan nemt tilføje en konstruktør i Visual Studio ved at bruge en snippet. Placer cursoren i klassen og skriv ctor og tryk to gange tabulering.

Brugerdefineret konstruktør

En brugerdefineret konstruktør kan du opfatte som en konstruktør med argumenter, og kan således bruges til at oprette objekter på flere måder. Du bestemmer selv, hvor mange konstruktører, du ønsker, og om du udelukkende vil have brugerdefinerede konstruktører.

I eksemplet med terningen kunne det være smart at kunne oprette en terning både med og uden argument svarende til værdien på terningen:

internal class Terning
{
    public int Værdi;

    // Afvikles hvis terning oprettes uden
    // argumenter
    public Terning()
    {
        this.Ryst();
    }

    // Afvikles hvis terning oprettes med 
    // en int som argument
    public Terning(int startVærdi)
    {
        this.Værdi = startVærdi;
    }

    public void Ryst()
    {
        System.Random rnd = new Random();
        this.Værdi = rnd.Next(1, 7);
    }
}

Nu kan terningen oprettes på to måder:

Terning t1 = new Terning();
Console.WriteLine(t1.Værdi);    // Tilfældig værdi

Terning t2 = new Terning(2);
Console.WriteLine(t2.Værdi);    // 2

Terningen kunne også udvides med et par ekstra konstruktører, hvor man kan angive en snydefaktor, som betyder at Ryst-metoden altid giver en 6’er:

internal class Terning
{
    public int Værdi;
    public bool SnydeFaktor;

    public Terning()
    {
        this.Ryst();
    }

    public Terning(int startVærdi)
    {
        this.Værdi = startVærdi;
    }

    public Terning(bool snydeFaktor)
    {
        this.Værdi = 6;
        this.SnydeFaktor = snydeFaktor;

    }

    public void Ryst()
    {
        if (this.SnydeFaktor == false)
        {
            System.Random rnd = new Random();
            this.Værdi = rnd.Next(1, 7);
        }
        else
        {
            this.Værdi = 6;
        }
    }
}

Nu kan du skabe en terning på tre måder:

Terning t1 = new Terning();
Console.WriteLine(t1.Værdi);    // Tilfældigt tal
t1.Ryst();
Console.WriteLine(t1.Værdi);    // Tilfældigt tal

Terning t2 = new Terning(3);
Console.WriteLine(t2.Værdi);    // 3
t2.Ryst();
Console.WriteLine(t2.Værdi);    // Tilfældigt tal

Terning t3 = new Terning(true);
Console.WriteLine(t3.Værdi);    // 6
t3.Ryst();
Console.WriteLine(t3.Værdi);    // 6

Læg mærke til at klassen nu har et ekstra felt kaldet SnydeFaktor. Det skal gemme den værdi, der angives i den ene konstruktør og bruges i øvrigt i Ryst-metoden.

Info

Hvis en klasse har en eller flere brugerdefinerede konstruktører, behøver du ikke også have en standard konstruktør. På den måde kan du tvinge brugeren af en klasse til at oprette objekter på en konkret måde.

Genbrug af konstruktører

Gentagelse af kode er roden til alt ondt, og du skal så vidt muligt forsøge at centralisere kode, så du kun skal rette ét sted. I relation til konstruktører kan der eksempelvis være log- eller sikkerhedskode, som altid skal køres. Du kan vælge at lade konstruktører kalde en fælles metode som eksempelvis:

internal class Terning
{
    public int Værdi;

    public Terning()
    {
        this.Værdi = 1;
        this.Check();
    }

    public Terning(int startVærdi)
    {
        this.Værdi = startVærdi;
        this.Check();
    }

    private void Check()
    {
        if (DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
            throw new 
               Exception("Du må ikke bruge terningen på en søndag");
    }
}

Her kalder begge konstruktører metoden Check, som sikrer, at terningen ikke kan benyttes på en søndag. Alternativt kan du få konstruktører til at kalde hinanden ved hjælp af this-kodeordet:

internal class Terning
{
    public int Værdi;

    // Kalder først Terning(int)
    // inden resten af konstruktøren 
    // afvikles
    public Terning() : this(1)
    {
        // Kode der evt skal afvikles
    }

    public Terning(int startVærdi)
    {
        if (DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
            throw new Exception(
               "Du må ikke bruge terningen på en søndag");

        this.Værdi = startVærdi;
    }
}

Terningen kan stadig oprettes på to måder, men hvis du benytter standardkonstruktøren, bliver den brugerdefinerede konstruktør kaldt først, og herefter afvikles eventuelle instruktioner i standardkonstruktøren.

Opgaver

readonly felter

Et readonly felt kan kun intialiseres i constructor eller ved intialisering og kan derefter ikke tilrettes:

using System;

namespace MinTest
{
    public class Person
    {
        public readonly string Navn = "";

        public Person(string navn)
        {
            this.Navn = navn;
        }

        public void Test() {
            // this.Navn = ""; fejl - må kun tildeles i constructor eller ved initialisering
        }
    }
}

Det giver mulighed for at skabe immutable felter.

Primære konstruktører

I C# 12 er der kommet en ny måde at skabe klasser på, som gør det nemmere at skabe konstruktører med argumenter. Det er en såkaldt primær konstruktør, og formålet er at gøre det nemt at oprette private felter (altså - felter som ikke kan tilgås udefra). Eksempelvis er følgende kode standard indtil C# 12:

class Person
{
    private string navn;
    private int alder;

    public Person(string navn, int alder)
    {
        this.navn = navn;
        this.alder = alder;
    }

    public void Print(){
        Console.WriteLine($"{navn} er {alder} år gammel");    
    }
}

Klassen kan bruges på følgende måde:

Person p = new Person("a", 1);
p.Print();

og gemmes værdierne i private felter. Det er dog lidt besværligt at skabe en klasse med mange felter, og derfor er der kommet en ny syntaks, som gør det nemmere at skabe konstruktører med argumenter.

Med en primær konstruktør kan du skrive følgende:

class Person(string navn, int alder)
{
    public void Print()
    {
        Console.WriteLine($"{navn} er {alder} år gammel");    
    }
}

og bruge den på samme måde:

Person p = new Person("a", 1);
p.Print();

Det er altså ikke længere nødvendigt at skabe private felter, og du kan skabe en klasse med mange felter på en enkelt linje.

Du må gerne tilføje flere konstruktører hvis du ønsker:

class Person(string navn, int alder)
{
    public Person() : this("", 0)
    {
    }

    public Person(string navn) : this(navn, 0)
    {
    }

    public Person(int alder) : this("", alder)
    {
    }

    public void Print()
    {
        Console.WriteLine($"{navn} er {alder} år gammel");
    }
}
Se den autogenerede C# kode

Hvis du vil se, hvordan C# kompileren oversætter den primære konstruktør, kan du bruge en decompiler som eksempelvis ILSpy eller SharpLab.

I sidstnævnte kan du indtaste følgende kode i venstre vindue:

class Person(string navn, int alder)
{
    public void Print()
    {
        Console.WriteLine($"{navn} er {alder} år gammel");    
    }
}

og se den autogenerede kode i højre vindue (vælg C# i dropdown-listen):

...

internal class Person
{
    [CompilerGenerated]
    private string <navn>PC__BackingField;

    [CompilerGenerated]
    private int <alder>PC__BackingField;

    [System.Runtime.CompilerServices.NullableContext(1)]
    public Person(string navn, int alder)
    {
        <navn>PC__BackingField = navn;
        <alder>PC__BackingField = alder;
        base..ctor();
    }

    public void Print()
    {
        DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(14, 2);
        defaultInterpolatedStringHandler.AppendFormatted(<navn>PC__BackingField);
        defaultInterpolatedStringHandler.AppendLiteral(" er ");
        defaultInterpolatedStringHandler.AppendFormatted(<alder>PC__BackingField);
        defaultInterpolatedStringHandler.AppendLiteral(" år gammel");
        Console.WriteLine(defaultInterpolatedStringHandler.ToStringAndClear());
    }
}

...

Bemærk, at argumenterne i konstruktøren er blevet til felter med backing fields, og at de er blevet initialiseret i konstruktøren.

Statiske felter og metoder

Statiske medlemmer i en klasse er elementer, der er knyttet til selve klassen og ikke til dens instanser. Statiske medlemmer inkluderer både metoder og felter.

En statisk metode eller felt erklæres ved at bruge nøgleordet static før typen og navnet på medlemmet. Statiske metoder og felter kan tilgås direkte ved hjælp af klassens navn, uden at der først skal oprettes en instans af klassen.

Et almindeligt eksempel på brug af statiske medlemmer er matematikbiblioteket i C#. Math-klassen indeholder mange statiske metoder og felter, såsom Math.PI og Math.Sqrt(double).

Fordelen ved at bruge statiske medlemmer er, at de kan reducere hukommelsesforbruget, da der kun oprettes én instans af det statiske medlem for hele klassen. Derudover kan de være nemmere at bruge, fordi de ikke kræver instansiering af objekter.

Dog er der også nogle ulemper ved at bruge statiske medlemmer. De kan gøre det vanskeligere at skrive testbar og fleksibel kode, da de skaber en tæt kobling mellem klasser. Derudover kan de føre til racebetingelser og synkroniseringsproblemer i flertrådede miljøer.

Her er et eksempel på en Person-klasse med en statisk metode:

Person p = new Person() { Navn = "mikkel" };
Console.WriteLine(p.StortNavn());
Console.WriteLine(Person.KonverterTilStortNavn("mikkel"));

public class Person
{
    // instans medlemmer
    public string Navn { get; set; }
    public string StortNavn() {
        return this.Navn.ToUpper();
    }

    public static string KonverterTilStortNavn(string navn) {
        return navn.ToUpper();
    }

}

og her et eksempel på et statisk felt og tilhørende statisk konstruktør:

 public class Vare
{
    private static double momsPct;

    static Vare() {
        momsPct = .25;  // Kan komme fra en database
    }
}

Opgaver

File Adgangsmodifikatoren i C#

I C# 11 blev en ny adgangsmodifikator, file, introduceret. Denne modifikator begrænser adgangen til en klasse, struktur eller anden type til den specifikke kildefil, hvor de er defineret. Dette er særligt nyttigt i store projekter, hvor du ønsker at holde visse typer interne og begrænsede til en enkelt fil, hvilket hjælper med at reducere kompleksiteten og forbedre kodeorganisationen:

// This class is only accessible within this file
file class InternalHelper {
    public void Assist() {
        // Method implementation
    }
}

// Another class in the same file can use 'InternalHelper'
public class ExternalClass {
    public void DoSomething() {
        InternalHelper helper = new InternalHelper();
        helper.Assist();
    }
}

I dette eksempel er klassen InternalHelper kun tilgængelig indenfor den fil, den er defineret i. Det betyder, at andre klasser i samme projekt, men i forskellige filer, ikke vil kunne tilgå InternalHelper-klassen. Denne begrænsning er nyttig for at holde interne hjælperklasser eller hjælpefunktioner organiseret og skjult fra resten af programmet.

Adgangsmodifikatoren file tilføjer et yderligere lag af adgangskontrol i C#, hvilket giver udviklere større fleksibilitet og kontrol over deres kodebases synlighed og tilgængelighed.

Attributter

For at give yderligere information til eksempelvis kompiler eller frameworks som benytter en klasse kan den beriges med [attributter]. Det vil tilføjes ekstra metadata som kan aflæses af anden kode eller framework hvis det ønskes.

Eksempelvis kan en klasse markeres med en SerializableAttribute for at indikere, at klassen kan benyttes i serialisering (mest brugt i ældre kode):

[Serializable()]
public class Person
{
    // ...
}

Der må gerne tilføjes mange attributter og man kan skabe dem selv, men lige nu skal du bare vide at det er en måde at tilføje ekstra information til klasser.

Klassediagram

I Visual Studio har du mulighed for at indsætte et klasse diagram der viser hvad en klasse består af og eventuelt sammenhæng med andre klasser.

Klassediagram

Du kan indsætte et klassediagram ved at højreklikke på projektet, vælge “Add” og “New item…” - her skal du lede efter “Class Diagram”.

Hvis du ikke kan finde klassediagrammet er det muligvis din installation mangler et kryds i en checkboks. Åbn Visual Studio Installer, vælg Modify, og under “Individual components” skal du finde “Class designer” og sætte kryds i checkboksen.