I en online session med undervisere på datamatikeruddannelsen omkring tips og tricks relateret til C#-undervisning blev jeg spurgt hvordan jeg bedst ville forklare forskellen på mutable og immutable data. Det er jeg blevet spurgt om før så jeg tænkte det kunne blive til et kort blogindlæg.
Første skridt må være at forsøge at oversætte mutable og immutable til noget der giver lidt mere mening på dansk. Den bedste oversættelse må vel være foranderlige og uforanderlige data, hvor foranderlige data er data i hukommelsen som frit kan ændres, og uforanderlige data er data som ikke kan ændres efter der er tildelt en værdi.
Et simpelt eksempel er en int og en const int:
int a = 10;
a = a + 1; // Ændring (kunne også skrives som a++)
const int b = 10;
// b = b + 1; // Fejl - b kan ikke rettes
I koden kan a frit ændres medens b aldrig kan få en anden værdi fordi det er en konstant.
Et andet oplagt eksempel er DateTime-typen som er uforanderlig (immutable) af natur:
DateTime c = new DateTime(2020, 11, 1);
Console.WriteLine(c.ToShortDateString()); // 01-11-2020
c.AddDays(1);
Console.WriteLine(c.ToShortDateString()); // 01-11-2020
DateTime d = c.AddDays(1);
Console.WriteLine(d.ToShortDateString()); // 02-11-2020
Bemærk, at metoden AddDays ikke lægger en dag til den konkrete instans men derimod returnerer en ny instans med den nye værdi.
Når man skaber egne typer kan man også vælge om man ønsker at skabe en uforanderlig (immutable) eller foranderlig (mutable) type. Her er en “mutable” Terning:
class Terning
{
public int Værdi { get; set; }
public Terning()
{
this.Ryst();
}
public void Ryst()
{
this.Værdi = new Random().Next(1, 7);
}
}
Egenskaben værdi er åben til både at aflæse OG tildele en værdi, og metoden Ryst ændrer ligeledes værdien.
Terning t1 = new Terning();
Console.WriteLine(t1.Værdi); // Tilfældig værdi
t1.Ryst();
Console.WriteLine(t1.Værdi); // Tilfældig værdi
t1.Værdi = 6;
Console.WriteLine(t1.Værdi); // 6
Her er den samme terning i “immutable” version:
class Terning
{
public int Værdi { get; private set; }
public Terning()
{
this.Værdi = new Random().Next(1, 7);
}
private Terning(int værdi)
{
this.Værdi = værdi;
}
public Terning Ryst()
{
return new Terning(new Random().Next(1, 7));
}
}
Bemærk, at egenskaben nu er låst for yderligere tildeling (private set) og at Ryst-metoden returnerer en ny terning med en tilfældig værdi i stedet for at tilrette den eksisterende.
Terning t2 = new Terning();
Console.WriteLine(t2.Værdi); // Tilfældig værdi
//t2.Værdi = 6; // Fejl
Terning t3 = t2.Ryst();
Console.WriteLine(t3.Værdi); // Tilfældig værdi
Se det lidt om at den “foranderlige” terning blot kan ændres ved at ryste den eller tildele den en ny værdi, mens den “uforanderlige” terning kun kan få en værdi en gang.