Hoppa till innehåll

Static, const och readonly

↑

Exemplen är oberoende C#-utdrag för moderna .NET-konsolprojekt, om inget annat anges. Klassdeklarationer och toppnivåkod kan placeras i samma Program.cs-fil, med toppnivåkoden först.

static knyter en medlem till typen i stället för ett visst objekt. Det handlar om ägande och livslängd, inte automatiskt om prestanda eller oföränderlighet.

I public static int TotalPlayers styr public åtkomsten, static innebär att fältet hör till typen, int är datatypen och TotalPlayers är namnet. Alla Player-instanser delar det statiska fältet, men har egna instansmedlemmar.

Example.cs
Player first = new() { Score = 100 };
Player second = new() { Score = 50 };
Console.WriteLine(Player.TotalPlayers); // 2
Console.WriteLine(first.Score); // 100
public class Player
{
public static int TotalPlayers;
public int Score;
public Player() => TotalPlayers++;
}

En statisk metod anropas genom typen utan att skapa ett objekt. Den kan inte direkt använda instansfält eftersom den inte har något this. Statiska klasser kan inte instansieras. Använd statiska hjälpklasser när inget objekttillstånd behövs.

Example.cs
Console.WriteLine(ScoreMath.Double(8)); // 16
public static class ScoreMath
{
public static int Double(int score) => score * 2;
}

const är en kompileringstidskonstant och är implicit statisk. readonly på instansfält tillåter tilldelning vid deklaration och i en konstruktor. static readonly kan initieras vid körning för hela typen. För referenstyper hindras fältets omtilldelning, inte att objektet förändras.

Example.cs
public class Settings
{
public const int MaxRetries = 3;
public readonly Guid InstanceId = Guid.NewGuid();
public static readonly DateTime StartedAt = DateTime.UtcNow;
}

Fördjupning: förstå ägandet bakom static

Section titled “Fördjupning: förstå ägandet bakom static”

Ett fält per objekt eller ett fält per typ?

Section titled “Ett fält per objekt eller ett fält per typ?”

Föreställ dig en lobby med tre Player-objekt. Varje spelare har ett eget namn och poängantal, medan spelet kanske också vill räkna hur många spelarobjekt som har skapats. Ett instansfält hör till ett enskilt objekt. Ett statiskt fält hör i stället till själva typen Player.

Deklaration Vem äger värdet? Åtkomst Om en spelare ändrar det
public int Score; Varje instans player.Score Bara den instansens poäng ändras
public static int Count; Typen Player Player.Count Alla ser samma nya värde
public const int MaxScore = 100; Kompileringstidskonstant Player.MaxScore Kan inte ändras
public static readonly Guid Id = Guid.NewGuid(); Typen Player.Id Fältet kan inte tilldelas om efter initiering

Tänk på en klass som en ritning och objekten som saker som byggs utifrån ritningen. static hör till ritningens gemensamma del. Du behöver inte skapa ett objekt för att läsa Player.Count. Däremot fungerar inte Player.Score: kompilatorn vet inte vilken spelares poäng du menar.

Program.cs
var first = new Player("Alex");
var second = new Player("Sam");
first.AddScore(10);
second.AddScore(25);
Console.WriteLine(first.Score); // 10
Console.WriteLine(second.Score); // 25
Console.WriteLine(Player.CreatedCount); // 2
public sealed class Player
{
public static int CreatedCount { get; private set; }
public string Name { get; }
public int Score { get; private set; }
public Player(string name)
{
Name = name;
CreatedCount++;
}
public void AddScore(int amount) => Score += amount;
}

Konstruktorn körs för varje skapat objekt: CreatedCount blir först 1 och sedan 2. Varje Score börjar på standardvärdet noll och ändras på rätt instans. Den privata set-accessorn gör att annan kod inte fritt kan skriva över räknaren. Observera: detta räknar skapade objekt, inte antalet objekt som fortfarande lever. För att hålla reda på aktiva spelare måste du också registrera att de lämnar.

Varför kan en statisk metod inte läsa instansfält direkt?

Section titled “Varför kan en statisk metod inte läsa instansfält direkt?”

this betyder “det här objektet som metoden körs på”. När du skriver first.AddScore(10) finns ett mottagande objekt, och metoden kan använda dess Score. Vid anropet ScoreMath.WithBonus(...) finns däremot inget implicit spelarobjekt. Därför kan en statisk metod inte direkt läsa Score eller Name från en viss instans. Du kan däremot skicka in ett objekt som parameter.

Program.cs
var player = new Player("Alex", 80);
Console.WriteLine(ScoreMath.WithBonus(player, 20)); // 100
public sealed record Player(string Name, int Score);
public static class ScoreMath
{
public static int WithBonus(Player player, int bonus)
=> player.Score + bonus;
}

Klassen ScoreMath är statisk eftersom den inte behöver hålla reda på ett eget objekttillstånd. Att skapa new ScoreMath() skulle inte tillföra något. Men en statisk metod är inte automatiskt ren, snabbare eller trådsäker. Den kan fortfarande ändra filer, delade fält eller objekt som skickas in.

Innan initiering får ett statiskt fält sitt standardvärde (0 för int, null för referenstyper). Fältinitierare eller en statisk konstruktor kan sedan skapa det gemensamma startvärdet. En statisk konstruktor har inga parametrar och ingen åtkomstmodifierare och körs automatiskt i samband med typens initiering.

Program.cs
Console.WriteLine(GameRules.SessionId);
Console.WriteLine(GameRules.SessionId); // Samma värde
public static class GameRules
{
public static readonly Guid SessionId;
static GameRules()
{
SessionId = Guid.NewGuid();
}
}

Värdet finns i regel kvar så länge typen är laddad i sin process eller laddningskontext. Det sparas inte automatiskt på disk, delas inte mellan olika servrar och synkroniseras inte över nätverket. För generiska typer har Cache<int> och Cache<string> dessutom egna statiska fält eftersom de är olika konstruerade typer.

När flera trådar ändrar samma fält är count++ inte en atomisk operation: två trådar kan läsa samma gamla värde och en uppdatering försvinner. För en enkel säker räknare kan du använda Interlocked.Increment. Mer komplicerat tillstånd behöver ofta låsning eller en tydligare ansvarsfördelning.

Program.cs
using System.Threading;
Console.WriteLine(RequestCounter.RecordRequest()); // 1
public static class RequestCounter
{
private static int _count;
public static int RecordRequest()
=> Interlocked.Increment(ref _count);
}

Trådsäkerhet är inte samma sak som static. Lägg inte användarspecifika värden i ändringsbara statiska fält i en serverapp: andra förfrågningar kan läsa eller skriva över dem.

const, readonly, static readonly och egenskaper

Section titled “const, readonly, static readonly och egenskaper”
Val När får värdet tilldelas? Typiskt exempel Viktig skillnad
const Vid deklaration; känt vid kompilering const int MaxLives = 3; Värdet byggs in i anropande kod; ett publikt konstantvärde kan kräva omkompilering hos anroparen
Instansfält med readonly Vid deklaration eller i instanskonstruktor readonly Guid _id = Guid.NewGuid(); Olika objekt kan få olika värden
static readonly Vid deklaration eller i statisk konstruktor static readonly Guid SessionId = Guid.NewGuid(); Ett runtime-initierat fält per typ
Statisk egenskap med private set Där koden har rätt att sätta egenskapen public static int Count { get; private set; } Kontrollerar hur delad data ändras

En readonly List<int> tillåter fortfarande list.Add(42): modifieraren skyddar referensen i fältet, inte själva listans innehåll. Vill du förhindra förändringar behöver du i stället ett lämpligt skrivskyddat gränssnitt eller en oföränderlig samling.

[SerializeField] gör inte att statiska fält serialiseras av Unitys vanliga Inspector-serialisering. Referenser till MonoBehaviour-instanser ska normalt tilldelas som instansfält. Om Enter Play Mode Options används utan domain reload kan statiska värden och eventprenumerationer överleva mellan Play Mode-sessioner. Det kan ge gamla referenser eller dubbla callbacks. Återställ tillståndet vid behov med Unitys initieringsmekanismer.

ScoreRegistry.cs
using UnityEngine;
public static class ScoreRegistry
{
public static int TotalScore { get; private set; }
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetState() => TotalScore = 0;
public static void Award(int points) => TotalScore += points;
}

Här förutsätts att anropen sker på Unitys huvudtråd. I ett större spel är en uttrycklig ägare, exempelvis en sessionshanterare eller injicerad tjänst, ofta bättre när data måste kunna testas, återställas eller höra till en viss sparfil. static är ett beslut om delat ansvar, inte en ersättning för arkitektur.

Behöver metoden läsa ett specifikt objekts tillstånd? Välj en instansmetod. Beräknar den bara resultat från parametrar? En statisk hjälpmetod kan vara lämplig. Är värdet känt vid kompilering? Använd const. Måste det beräknas en gång under körning? Överväg static readonly. Ändras det under spelet? Föredra en kontrollerad egenskap eller tjänst framför ett publikt ändringsbart fält. Behöver olika tester, spelare eller sessioner egna värden? Undvik globalt delat tillstånd.

Använd static för verkligt delad data eller tillståndslösa metoder. Använd instanser när ägande, testbarhet och isolering spelar roll.

Publikt ändringsbart statiskt tillstånd skapar dolda beroenden. Unity serialiserar normalt inte statiska fält. Vid avstängd domain reload kan statiska värden och händelser finnas kvar mellan Play Mode-sessioner; återställ dem vid behov.

Skapa två spelarobjekt med olika poäng och en delad räknare. Förklara varför ändring av den ena poängen inte påverkar den andra.

Visa ett lösningsförslag

Deklarera Score som instansfält och TotalPlayers med static.