Gå til indholdet

Brug af ref, in og out

I C# er der tre nøgleord, der bruges til at kontrollere, hvordan argumenter overføres til metoder: in, out og ref. Disse nøgleord ændrer metodekaldets semantik og giver forskellige grader af adgang til variabler i metoder.

Information til undervisere

Man skal have noget erfaring for at forstå nødvendigheden af ref, in og out. Men det er en god idé at introducere disse nøgleord tidligt, så eleverne kan blive fortrolige med dem, inden de støder på dem i kode fra andre. Understreg, at det er en god idé at bruge disse nøgleord med forsigtighed, da de kan gøre koden mere kompleks og svær at læse, og at det typisk kun handler om mikro optimering.

ref

I C# har du mulighed for at bruge kodeordet ref til at arbejde med referencer til variabler. Dette kan være nyttigt, når du vil sende en reference til en variabel til en metode, så metoden kan ændre værdien af variablen. Det komplicerer koden noget og kan gøre den sværere at læse og vedligeholde, men til gengæld kan det giver nogle performance forbedringer. Derfor bør du kun bruge ref, når det er nødvendigt.

Der er flere måder at bruge ref på, her er et par eksempler.

Brug af ref i metodekald

void Increment1(int number)
{
    number++;
}


void Increment2(ref int number)
{
    number++;
}

int value = 5;
Increment1(value);
Console.WriteLine(value); // Output: 5
Increment2(ref value);
Console.WriteLine(value); // Output: 6

I dette eksempel bruges ref til at sende en reference til en variabel til metoden Increment. Metoden øger værdien af variablen med 1, og ændringerne er synlige uden for metoden. Hvis du fjerner ref-nøgleordet, vil metoden modtage en kopi af variablen, og ændringerne vil ikke blive synlige uden for metoden. Bemærk, at du skal bruge ref både i metodekaldet og i metode signaturen.

Brug af ref i returnerede værdier

SomeClass someClass = new SomeClass();
someClass.PrintArray(); // 1 2 3
ref int element = ref someClass.GetArrayElement(1);
element = 42;
someClass.PrintArray(); // 1 42 3
ref int saveRef = ref element;
saveRef = 99;
someClass.PrintArray(); // 1 99 3

class SomeClass
{

    private int[] array = { 1, 2, 3 };

    public ref int GetArrayElement(int index)
    {
        return ref array[index];
    }

    public void PrintArray()
    {
        Console.WriteLine(string.Join(" ", array));
    }

}

Her bruges ref til at returnere en reference til et arrayelement fra en metode. Dette gør det muligt at ændre værdien af arrayelementet direkte fra metodekaldet. Det minder lidt om brugen af Span<T> og Memory<T>, som også giver mulighed for at arbejde med hukommelsesblokke på en mere effektiv måde.

Brug af ref til reference typer

Ved reference baserede argumenter kan man ikke ikke rette referencen (eksempelvis pege på et nyt objekt), men ref giver mulighed for helt at ændre referencen:

class Person
{
    public string Name { get; set; }
}

void Test1(Person p)
{
    // der er overført en kopi af p
    // så hvis man tildeler p en ny
    // reference har det ikke nogen
    // effekt i den kaldende metode
    p = new Person();   // ingen betydning
}

void Test2(ref Person p)
{
    // der er overført den originale reference
    // så hvis man tildeler p en ny reference har
    // det betydning i den kaldende metode
    p = new Person();   // betydning
}

I dette eksempel er der to metoder, Test1 og Test2, der tager en Person som argument. I Test1 er der ikke brugt ref, så metoden modtager en kopi af Person-objektet. Hvis du tildeler p en ny reference, vil det ikke have nogen effekt uden for metoden. I Test2 er der brugt ref, så metoden modtager den originale reference til Person-objektet. Hvis du tildeler p en ny reference, vil det have betydning uden for metoden.

out

Brugen er out er meget lig ref bortset fra, at out ikke kræver at variablen er initialiseret før metoden kaldes, og at metoden skal tildele en værdi til variablen før metoden returnerer.

static void GetMinMax(int[] numbers, out int min, out int max)
{
    min = numbers[0];
    max = numbers[0];
    foreach (int number in numbers)
    {
        if (number < min) min = number;
        if (number > max) max = number;
    }
}

Metoden kan kaldes med

int min, max;   // bemærk - behøver ikke initialiseres
int[] a = { 1, 2, 3 };
GetMinMax(a, out min, out max);

in

Når et argument mærkes med in nøgleordet, betyder det, at værdier ikke kopieres og at metoden kun kan læse værdien af argumentet og ikke ændre den. Dette er særligt nyttigt for at optimere ydeevnen, når der arbejdes med store værdityper, da det forhindrer unødvendige kopiering af data.

public struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

public static double CalculateDistance(in Point p1, in Point p2)
{
    int deltaX = p1.X - p2.X;
    int deltaY = p1.Y - p2.Y;
    return Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
}

Info

Bemærk at in-kodeordet er såkaldt micro optimering, men det kan give en fordel ved store værdibaserede typer i et i forvejen belastet system.

Det virker også med reference baserede variabler med giver begrænset effekt:

static void PrintLength(in string text)
{
    Console.WriteLine(text.Length);
    // text = "..."; error
}

I dette eksempel er in nøgleordet ikke nødvendigt, fordi det ikke giver ydeevnegevinster for reference-typer. Men det giver stadig en beskyttelse mod utilsigtet ændring af parameteren, selvom det er mere symbolsk i dette tilfælde.