Static Members and Classes
Static members belong to the type, not to any object: one shared copy, reached through the type name rather than an instance.
static class Helpers { public static int Twice(int n) { return n * 2; } }Static means belonging to the type rather than to any object built from it. An instance field gets one copy per object. A static field gets exactly one copy for the whole program, shared by every object of that type and by code that never builds one. You reach a static member through the type name: Ticket.Next(), not ticket.Next().
A static method is one that needs no particular object. It works only from its arguments and from static state. Math.Clamp and Console.WriteLine are both static, which is why you never build a Math.
Static constructors and static classes
A static constructor is written static TypeName(). It takes no parameters and no access modifier, and you cannot call it. The runtime runs it at most once, before the first instance is created or any static member of that type is used. A static class cannot be built with new and may only contain static members, which makes it the right shape for a set of pure helper functions and nothing else.
What trips people up
A static method cannot touch instance fields, because there is no object for it to read them from. More seriously, static state is shared by everything, including requests being handled at the same moment in a web application. Prefer const or static readonly for values fixed at startup, and keep changeable state in objects you pass around.
Example
// Issued is read through the type name. No Ticket exists yet.
Console.WriteLine($"issued: {Ticket.Issued}");
Ticket first = Ticket.Next();
Ticket second = Ticket.Next();
Console.WriteLine($"first: {first.Number}");
Console.WriteLine($"second: {second.Number}");
Console.WriteLine($"issued: {Ticket.Issued}");
class Ticket
{
// One _issued for the whole type, not one per Ticket.
private static int _issued;
private Ticket(int number)
{
Number = number;
}
// One Number per Ticket object.
public int Number { get; }
public static int Issued => _issued;
// A static method. Called as Ticket.Next(), with no object involved.
public static Ticket Next()
{
_issued = _issued + 1;
return new Ticket(_issued);
}
}
// Output:
// issued: 0
// first: 1
// second: 2
// issued: 2When to use it
- Write pure helper functions, such as unit conversion or formatting, that need no object.
- Hold genuinely fixed configuration like a retry limit or a default page size.
- Offer a factory method on the type itself while keeping the constructor private.
More examples
A static class of helpers
This is the right use of static: functions whose answer depends only on their arguments. Marking the class static documents that there is nothing to construct and stops anyone adding an instance field later by accident. Resist the pull to grow a static class into a general bucket of unrelated utilities, because a class that can be reached from everywhere is depended on by everything.
Console.WriteLine(Temperature.ToCelsius(212));
Console.WriteLine(Temperature.ToCelsius(32));
// A static class may only contain static members. The compiler refuses
// new Temperature(), so nobody can mistake it for a thing with state.
static class Temperature
{
private const double FreezingF = 32.0;
public static double ToCelsius(double fahrenheit)
{
return (fahrenheit - FreezingF) * 5.0 / 9.0;
}
}
// Output:
// 100
// 0A static constructor
The output shows the timing: nothing happens until the first touch of Codes, and it happens once. Use a static constructor only when the setup is too involved for a single initialiser expression. If it would fit at the declaration, put it there instead: having a static constructor blocks a runtime optimisation, and an exception thrown inside one leaves the type permanently unusable, surfacing as a confusing TypeInitializationException.
Console.WriteLine("before");
Console.WriteLine(Codes.Lookup(2));
Console.WriteLine(Codes.Lookup(404));
static class Codes
{
private static readonly Dictionary<int, string> _names;
// No parameters, no access modifier, never called by your code.
// The runtime runs it once, just before the first use of Codes.
static Codes()
{
Console.WriteLine("static constructor ran");
_names = new Dictionary<int, string>
{
[2] = "accepted",
[404] = "not found"
};
}
public static string Lookup(int code)
{
return _names.GetValueOrDefault(code, "unknown");
}
}
// Output:
// before
// static constructor ran
// accepted
// not foundconst, static readonly and the difference
The distinction matters when your code ships as a library. Change a public const and every project that was compiled against the old value keeps the old value until it is rebuilt, because the number was baked in at their compile time. static readonly has no such trap. Keep const for things that are true by definition, and static readonly for everything else.
Console.WriteLine($"max retries: {Limits.MaxRetries}");
Console.WriteLine($"page size: {Limits.PageSize}");
Console.WriteLine($"timeout: {Limits.Timeout}");
static class Limits
{
// const is fixed when the code is compiled, and its value is copied
// into every assembly that reads it.
public const int MaxRetries = 3;
// static readonly is set once when the type is first used, and is read
// from here every time, so a rebuild of this project alone changes it.
public static readonly int PageSize = 50;
// Only static readonly can hold this: const is limited to numbers,
// strings, bools, chars, enums and null.
public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
}
// Output:
// max retries: 3
// page size: 50
// timeout: 00:00:30
Discussion