C# Static Members
A static member belongs to a type itself, not to one particular object created from that type. Static members matter when data or behavior is shared by all instances, such as a counter, a conversion method, a shared application setting, or a utility function. Used well, static makes intent clear; used carelessly, it can create hidden shared state that is hard to test and debug.
Overview: How Static Members Work
In C#, normal instance members live with an object. If you create three BankAccount objects, each object has its own instance fields such as owner and balance. A static member is different: there is one member associated with the class, regardless of how many objects exist. You access it through the type name, such as Math.Round(2.5) or BankAccount.TotalAccounts, instead of through a variable that holds an object.
The CLR keeps static fields in type-level storage, separate from individual object data. The first time a type is used in a way that requires initialization, the runtime ensures its static fields are initialized and its static constructor, if present, has run. This happens once per type per application domain or load context. After that, every caller sees the same static field values until the process changes them or ends.
Static methods do not receive a hidden this reference. That is why a static method can use other static members directly, but cannot directly read instance fields or call instance methods. If static code needs object-specific data, you must pass an object or value into it. This rule is central: static means type-level; instance means object-level.
C# supports static fields, static properties, static methods, static constructors, static events, static operators in suitable types, and static classes. A static class is a class that cannot be instantiated or inherited, and all of its members must be static. It is commonly used for focused utility functions, such as formatting, validation, conversion, or calculation helpers.
Static members are useful, but they are global-ish within the running process. A mutable static field can be changed from anywhere that can access it. In a web app, desktop app, or test suite, that shared state can survive longer than expected and affect unrelated code. Prefer immutable static data, stateless static methods, or carefully encapsulated static state.
Syntax
using System;
class TypeName
{
public static int Count { get; private set; }
public static readonly string Category = "Example";
static TypeName()
{
Count = 0;
}
public static void PrintCategory()
{
Console.WriteLine(Category);
}
public TypeName()
{
Count++;
}
}
class Program
{
static void Main()
{
}
}
| Part | Meaning |
|---|---|
static int Count |
One field or property shared by the entire type. |
static TypeName() |
A static constructor that runs once before the type is first used. |
public static void PrintCategory() |
A method called on the class name, not on an object. |
public TypeName() |
An instance constructor. It can update static members, but it runs for each new object. |
The static keyword appears before the member type. Static constructors have no access modifier and no parameters. Static members are normally called with the type name, for example TypeName.PrintCategory(). Calling a static member through an instance is not allowed in modern C#.
Examples
A stateless static utility class
using System;
static class TemperatureConverter
{
public static double CelsiusToFahrenheit(double celsius)
{
return celsius * 9 / 5 + 32;
}
public static double FahrenheitToCelsius(double fahrenheit)
{
return (fahrenheit - 32) * 5 / 9;
}
}
class Program
{
static void Main()
{
Console.WriteLine(TemperatureConverter.CelsiusToFahrenheit(20));
Console.WriteLine(TemperatureConverter.FahrenheitToCelsius(68));
}
}
Output:
68
20
TemperatureConverter has no per-object data, so making it a static class fits. You do not write new TemperatureConverter(). The methods depend only on their parameters and return calculated values, which makes them easy to understand and test.
Shared counters with static fields and properties
using System;
class BankAccount
{
private static int nextNumber = 1000;
public static int CreatedCount { get; private set; }
public string AccountNumber { get; }
public string Owner { get; }
public BankAccount(string owner)
{
Owner = owner;
AccountNumber = $"A-{nextNumber}";
nextNumber++;
CreatedCount++;
}
public void Print()
{
Console.WriteLine($"{AccountNumber}: {Owner}");
}
}
class Program
{
static void Main()
{
BankAccount first = new BankAccount("Maya");
BankAccount second = new BankAccount("Noah");
first.Print();
second.Print();
Console.WriteLine($"Created: {BankAccount.CreatedCount}");
}
}
Output:
A-1000: Maya
A-1001: Noah
Created: 2
Each account has its own Owner and AccountNumber, but all accounts share nextNumber and CreatedCount. The constructor runs once per object and updates the static counter each time. Notice that CreatedCount is read through BankAccount.CreatedCount, because it belongs to the class.
Static constructor for one-time setup
using System;
class ReportSettings
{
public static readonly string DefaultTitle;
public static readonly int MaxRows;
static ReportSettings()
{
DefaultTitle = "Monthly Report";
MaxRows = 500;
Console.WriteLine("ReportSettings initialized");
}
public static void PrintDefaults()
{
Console.WriteLine(DefaultTitle);
Console.WriteLine(MaxRows);
}
}
class Program
{
static void Main()
{
ReportSettings.PrintDefaults();
ReportSettings.PrintDefaults();
}
}
Output:
ReportSettings initialized
Monthly Report
500
Monthly Report
500
The static constructor runs before the first call to ReportSettings.PrintDefaults(). It does not run again for the second call. Static constructors are useful for one-time setup of static data, especially when initialization needs more than a simple field initializer.
Combining static and instance members
using System;
class Employee
{
public static string CompanyName { get; set; } = "Northwind";
public string Name { get; }
public Employee(string name)
{
Name = name;
}
public void PrintBadge()
{
Console.WriteLine($"{Name} - {CompanyName}");
}
}
class Program
{
static void Main()
{
Employee ana = new Employee("Ana");
Employee ben = new Employee("Ben");
ana.PrintBadge();
Employee.CompanyName = "Contoso";
ben.PrintBadge();
ana.PrintBadge();
}
}
Output:
Ana - Northwind
Ben - Contoso
Ana - Contoso
Name is instance data, so Ana and Ben keep different names. CompanyName is static data, so changing it through the Employee type affects what every employee sees afterward. This can be useful for a shared setting, but it can also surprise you if you expected each object to have its own company name.
How It Works Step by Step
- The compiler records whether each member is static or instance in the type metadata.
- When an object is created with
new, memory is allocated for instance fields only. Static fields are not copied into each object. - Before a type’s static member is first used, the CLR initializes static fields and runs the static constructor if one exists.
- A static method call is dispatched using the type, not a target object. There is no
thisparameter. - An instance method call includes a target object. That method can read instance members through
thisand can also read accessible static members through the type. - Static field values remain available as long as the type remains loaded in the running process.
This explains both the strength and danger of static state. It is efficient and easy to reach, but it is shared. If one part of your program changes a mutable static field, another part may observe the changed value later.
Common Mistakes
Trying to use instance data directly from a static method
class ScoreBoard
{
private int score;
public static void Reset()
{
score = 0;
}
}
This does not compile because score belongs to an object, but Reset belongs to the class. A static method needs an object reference if it wants to change object-specific data.
using System;
class ScoreBoard
{
private int score;
public ScoreBoard(int startingScore)
{
score = startingScore;
}
public static void Reset(ScoreBoard board)
{
board.score = 0;
}
public void Print()
{
Console.WriteLine(score);
}
}
class Program
{
static void Main()
{
ScoreBoard board = new ScoreBoard(12);
ScoreBoard.Reset(board);
board.Print();
}
}
Output:
0
Using static fields for per-object state
using System;
class Player
{
private static int score;
public Player(int startingScore)
{
score = startingScore;
}
public void AddPoint()
{
score++;
}
public void Print()
{
Console.WriteLine(score);
}
}
class Program
{
static void Main()
{
Player red = new Player(10);
Player blue = new Player(20);
red.AddPoint();
red.Print();
blue.Print();
}
}
Output:
21
21
This code compiles, but both players share the same score. The second constructor call overwrites the score for everyone, and red.AddPoint() increments the shared value. The corrected version uses an instance field:
using System;
class Player
{
private int score;
public Player(int startingScore)
{
score = startingScore;
}
public void AddPoint()
{
score++;
}
public void Print()
{
Console.WriteLine(score);
}
}
class Program
{
static void Main()
{
Player red = new Player(10);
Player blue = new Player(20);
red.AddPoint();
red.Print();
blue.Print();
}
}
Output:
11
20
Trying to create an instance of a static class
static class TextTools
{
public static bool IsShort(string text)
{
return text.Length <= 10;
}
}
class Program
{
static void Main()
{
TextTools tools = new TextTools();
}
}
A static class cannot be instantiated. Call its members through the class name, such as TextTools.IsShort("hello"). If you need objects with separate state, use a normal class instead.
Best Practices
- Use static methods for behavior that does not need object state, such as pure calculations, conversions, formatting, and validation helpers.
- Use static fields sparingly. Prefer
const,static readonly, or read-only static properties when the value should not change after initialization. - Access static members through the type name. It communicates that the member is shared.
- Do not store per-user, per-request, or per-object data in static fields. That data belongs in instances or in explicitly scoped services.
- Keep static constructors small and predictable. Expensive work, I/O, and failure-prone setup can make a type hard to use and test.
- Encapsulate mutable static state behind methods or properties so validation and thread-safety rules can be enforced.
- Be careful with mutable static fields in multithreaded programs. Multiple threads can read and write the same field at the same time unless you design synchronization.
- Use a static class only when the entire type is a utility holder. If some members require object state, use a normal class.
Practice Exercises
- Create a
CurrencyConverterstatic class with methods that convert dollars to euros and euros to dollars using a fixed exchange rate. - Create a
Documentclass that assigns each new document a unique number using a private static counter. Print three document numbers. - Create a
GameSettingsclass with a static property namedDifficulty. Create two player objects and show that changing the difficulty affects both.
Summary
- Static members belong to the type itself, not to a particular object.
- There is one copy of a static field for the type, while each object has its own copy of instance fields.
- Static methods do not have
thisand cannot directly access instance members. - Static constructors run once before a type is first used and are useful for one-time type initialization.
- Static classes are good for stateless utility code, but mutable static state should be used carefully.
