C# Static Members: Static Fields, Methods and Constructors
Static members belong to the type itself, not to any instance.
Imagine you're building a system that tracks how many users are currently logged in. You need a single counter that every part of your application can access and update — but you don't want to create an object just to hold that counter.
That's where static members come in. They belong to the class itself, not to any particular object. Static fields hold data that is shared across all instances, and static methods provide utility functions that don't depend on object state.
In this lesson, you'll learn about static fields, properties, methods, constructors, and classes — and when to use them in your applications.
What Is It?
The Simple Explanation
A static member is a field, property, method, or constructor that belongs to the class itself, not to any specific object. You access it using the class name, not an instance variable.
Instance members — each object has its own copy (e.g., person.Name).
Static members — shared across all objects (e.g., Person.TotalCount).
Static members exist even if no objects of the class have been created.
The Technical Definition
A static member is declared with the static keyword. It is associated with the type, not with instances. Static members are stored in a special area of memory that is shared across all instances and accessed via the type name.
- Static fields — share data across all objects.
- Static properties — provide controlled access to static fields.
- Static methods — perform operations not specific to an instance.
- Static constructors — initialise static members (run once per type).
- Static classes — contain only static members and cannot be instantiated.
| Member Type | Keyword | Access | Example |
|---|---|---|---|
| Instance field | — | obj.Name |
Each object has its own |
| Static field | static |
ClassName.Value |
Shared across all objects |
| Static method | static |
ClassName.Method() |
Utility / helper functions |
| Static constructor | static |
— | Runs once per type |
| Static class | static class |
ClassName.Method() |
Contains only static members |
Why Does It Exist?
The Problem
Sometimes you need data or functionality that isn't tied to a specific object. For example:
- A counter that tracks how many objects have been created.
- A utility method that converts strings to numbers (doesn't need any object state).
- A configuration value that should be shared across all parts of your application.
- A factory method that creates objects of a class.
Without static members, you'd have to create a dummy object just to hold such data or methods — which is awkward and inefficient.
The Solution
Static members provide a clean way to:
- Share data — static fields maintain a single value across all instances.
- Provide global utility — static methods like
Math.Sqrtare available anywhere. - Encapsulate type-level logic — e.g., factory methods that create instances.
- One-time initialisation — static constructors run once to set up the type.
- Prevent instantiation — static classes ensure only static members exist.
Big Picture
Here's how static members relate to the type and its instances:
How It Works
Let's explore different types of static members with examples.
public class Counter
{
public static int TotalInstances = 0; // static field
public int InstanceNumber { get; }
public Counter()
{
TotalInstances++;
InstanceNumber = TotalInstances;
}
}
var c1 = new Counter();
var c2 = new Counter();
var c3 = new Counter();
Console.WriteLine(Counter.TotalInstances); // 3
Console.WriteLine(c1.InstanceNumber); // 1
Console.WriteLine(c2.InstanceNumber); // 2
Console.WriteLine(c3.InstanceNumber); // 3TotalInstances is shared across all objects. Each constructor increments it.
public class Configuration
{
private static string _environment = "Development";
public static string Environment
{
get => _environment;
set
{
if (value == "Production" || value == "Development" || value == "Staging")
_environment = value;
else
throw new ArgumentException("Invalid environment.");
}
}
}
Console.WriteLine(Configuration.Environment); // Development
Configuration.Environment = "Production";
Console.WriteLine(Configuration.Environment); // ProductionStatic properties allow validation and logic when accessing static data.
public class MathHelper
{
// Static utility method
public static int Add(int a, int b) => a + b;
// Static factory method
public static Counter CreateCounter()
{
return new Counter();
}
}
int sum = MathHelper.Add(5, 3); // 8
var counter = MathHelper.CreateCounter();Static methods are called on the class itself. They don't have a this reference.
public class Database
{
private static string ConnectionString;
static Database()
{
// Runs once when the type is first accessed
ConnectionString = LoadFromConfig();
Console.WriteLine("Static constructor ran.");
}
private static string LoadFromConfig() => "Server=localhost;Db=MyDb;";
public static void Connect()
{
Console.WriteLine($"Connecting with {ConnectionString}");
}
}
Database.Connect(); // Static constructor runs firstStatic constructors are perfect for loading configuration, initialising static fields, etc.
public static class StringExtensions
{
public static string Reverse(this string input) // extension method
{
return new string(input.Reverse().ToArray());
}
public static bool IsNullOrWhitespace(string input)
{
return string.IsNullOrWhiteSpace(input);
}
}
string reversed = "hello".Reverse(); // "olleh" (using extension method)
bool empty = StringExtensions.IsNullOrWhitespace(" "); // trueStatic classes cannot be instantiated or inherited. They're ideal for utility libraries.
Simple Example
Let's build a Library system that tracks total books and provides utility methods.
public class Book
{
// ─── Static members ───
private static int _nextId = 100;
private static int _totalBooks = 0;
public static int TotalBooks => _totalBooks;
// Static constructor: runs once
static Book()
{
Console.WriteLine("Book static constructor: Initialising.");
}
// Static factory method
public static Book CreateBook(string title, string author)
{
return new Book(title, author);
}
// ─── Instance members ───
public int Id { get; }
public string Title { get; }
public string Author { get; }
private Book(string title, string author)
{
if (string.IsNullOrWhiteSpace(title)) throw new ArgumentException("Title required.");
if (string.IsNullOrWhiteSpace(author)) throw new ArgumentException("Author required.");
Id = Interlocked.Increment(ref _nextId);
Title = title;
Author = author;
_totalBooks++;
}
public void Display()
{
Console.WriteLine($"#{Id}: {Title} by {Author}");
}
}
// ─── Static class for utilities ───
public static class BookUtilities
{
public static string FormatTitle(string title)
{
return title.ToUpperInvariant();
}
public static bool IsValidIsbn(string isbn)
{
return isbn?.Length == 13 && isbn.All(char.IsDigit);
}
}
// ─── Usage ───
var book1 = Book.CreateBook("1984", "George Orwell");
var book2 = Book.CreateBook("Brave New World", "Aldous Huxley");
book1.Display(); // #101: 1984 by George Orwell
book2.Display(); // #102: Brave New World by Aldous Huxley
Console.WriteLine($"Total books: {Book.TotalBooks}"); // 2
Console.WriteLine(BookUtilities.FormatTitle("hello")); // HELLO
Console.WriteLine(BookUtilities.IsValidIsbn("9781234567890")); // TrueKey takeaways from this example:
- Static field
_totalBookstracks the total number of books created. - Static factory method
CreateBookprovides a controlled way to create instances. - Static constructor initialises static data.
- Static class
BookUtilitiesholds pure utility functions.
Real-World Example
In a typical application, static members are used for logging, configuration, and the singleton pattern.
// ─── Singleton using static members ───
public class AppConfig
{
private static AppConfig? _instance;
private static readonly object _lock = new();
private AppConfig() { } // Private constructor prevents external instantiation
public static AppConfig Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
_instance ??= new AppConfig();
}
}
return _instance;
}
}
public string Environment { get; set; } = "Development";
public int MaxRetries { get; set; } = 3;
}
// ─── Static logger ───
public static class Logger
{
private static readonly string _logFilePath;
static Logger()
{
_logFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "app.log");
Console.WriteLine($"Logger initialised. Log file: {_logFilePath}");
}
public static void LogInfo(string message)
{
var entry = $"[INFO] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}";
Console.WriteLine(entry);
// In a real app, you'd write to a file.
}
public static void LogError(string message, Exception? ex = null)
{
var entry = $"[ERROR] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}" + (ex != null ? $"\n{ex}" : "");
Console.WriteLine(entry);
}
}
// ─── Usage in a real application ───
var config = AppConfig.Instance;
Logger.LogInfo($"Application starting in {config.Environment} mode.");
Logger.LogInfo($"Max retries: {config.MaxRetries}");
try
{
// Simulate some work
throw new InvalidOperationException("Something went wrong.");
}
catch (Exception ex)
{
Logger.LogError("An error occurred.", ex);
}This example shows:
- Singleton — static property
Instanceensures only one configuration object exists. - Static logger —
Loggeris a static class with methods available everywhere. - Lazy initialisation — singleton is created only when first accessed.
- Thread safety — lock ensures safe singleton creation in multithreaded environments.
Analogy
Think of a class as a company. The company itself has attributes that are shared by all employees: the company name, the address, the number of employees.
Each employee (object) has their own attributes: name, salary, employee ID.
- Static field = total number of employees (shared).
- Static method = HR department that can hire an employee (doesn't depend on a specific employee).
- Static constructor = company founding (runs once).
- Static class = a department that only provides utility services (like IT support).
Static members are like the company's central resources; instance members are like each employee's personal belongings.
Under the Hood
How does the .NET runtime handle static members?
- Static fields are stored in a special area of memory called the high-frequency heap or type-specific static storage.
- There is exactly one copy of each static field per
AppDomain. - Static fields are initialised to their default values before the static constructor runs.
- The static constructor (if any) is called automatically before any static members are accessed or any instances are created.
- The runtime ensures thread-safe initialisation — only one thread executes the static constructor.
- If the static constructor throws an exception, the type becomes unusable (TypeInitializationException).
- Static methods are called directly via the type's metadata — no instance reference needed.
- They don't receive a
thispointer, so they can't access instance members directly. - The JIT can sometimes inline static methods more aggressively.
- The compiler marks the class as
abstract sealedin IL — it cannot be instantiated or inherited. - The compiler enforces that all members are static.
Common Confusion
1. Static vs Instance — When to Use Which
Use instance members when the data or behaviour depends on the specific object's state. Use static members when the data or behaviour is common to all objects or doesn't require any object state.
2. Static Methods Can't Access Instance Members
A static method cannot directly access instance fields or methods because it doesn't have a this reference. If you need to access instance data, you must pass an instance reference as a parameter.
public class Example
{
private int _value;
public static void StaticMethod(Example obj) // pass instance
{
Console.WriteLine(obj._value);
}
}3. Static Constructor vs Instance Constructor
An instance constructor runs each time an object is created. A static constructor runs once, before any static member is accessed or any instance is created. Static constructors cannot be called explicitly and have no parameters.
Common Mistakes
Mistake 1 — Using static for state that should be per-instance
Wrong:
public class User
{
public static string Name { get; set; } // All users share the same name — bad!
}Correct:
public class User
{
public string Name { get; set; } // Each user has their own name
}Mistake 2 — Static methods that depend on instance data
Wrong:
public static void PrintName()
{
Console.WriteLine(Name); // Error: Can't access instance field Name
}Correct (pass an instance):
public static void PrintName(User user)
{
Console.WriteLine(user.Name);
}Mistake 3 — Not handling thread safety for static fields
If you have static fields accessed from multiple threads, you must synchronise access (e.g., using locks).
private static int _counter;
public static void Increment()
{
Interlocked.Increment(ref _counter); // Thread-safe increment
}Mistake 4 — Overusing static classes instead of dependency injection
Static classes make code harder to test and can create hidden dependencies. In modern applications, prefer dependency injection over static utilities for shared services.
When Should I Use It?
- You need data shared across all instances (e.g., counters).
- You have utility methods that don't depend on state (e.g.,
Math.Sqrt). - You need a factory method to create instances.
- You want to implement a singleton pattern.
- You need one-time setup for a type (static constructor).
- The data should be per-instance (e.g., user names, order totals).
- You need polymorphism (static methods can't be virtual).
- You want to test the code easily (static members create hard dependencies).
- You're building a system with dependency injection (prefer instance services).
Mental Model
Instance = belongs to each object, separate, accessed via object
Static field = one copy for the whole type
Static method = utility or factory, no
thisStatic constructor = one-time type initialisation
Static class = pure utilities, cannot be instantiated
Remember:
· Access static members via
ClassName.Member· Static methods cannot access instance members directly
· Static constructors run automatically and cannot be called directly
· Use static sparingly; prefer instance-based design with dependency injection for testability
Key Takeaway
- Static members belong to the type, not to instances.
- They are shared across all objects and exist even if no objects are created.
- Static fields store global or shared state (use cautiously).
- Static methods provide utility functions that don't depend on object state.
- Static constructors run once per type for one-time initialisation.
- Static classes are containers for static members and cannot be instantiated.
- Prefer instance-based design for testability and flexibility; use static members judiciously.
Check Your Understanding
You've seen how static members work. Let's test your knowledge.
1. Which of the following statements about static members is true?
Show answer
Correct: C
Why C is correct: Static members belong to the class itself, not to any instance. They are shared across all objects and exist even when no objects are created.
Why A is incorrect: Static members are accessed using the class name, not an instance.
Why B is incorrect: Static members are not per-object; they are per-type.
Why D is incorrect: Static members exist even without any instances. They can be accessed at any time.
Reinforcement: Static = type-level, shared; instance = object-level, separate.
2. Can a static method access an instance field directly?
Show answer
Correct: C
Why C is correct: Static methods do not have a this reference, so they cannot access instance members directly. They would need an instance reference passed as a parameter.
Why A is incorrect: Static methods cannot access instance fields directly.
Why B is incorrect: Even if the field is public, the static method still lacks a specific instance to access it.
Why D is incorrect: Being in the same class doesn't give the static method a this reference.
Reinforcement: Static methods operate at the type level; instance members require an object.
3. When does a static constructor run?
Show answer
Correct: B
Why B is correct: A static constructor runs automatically once, before any static member is accessed or any instance of the type is created. It is called by the runtime when the type is first referenced.
Why A is incorrect: Static constructors run once, not per instance.
Why C is incorrect: They run at initialisation, not at termination.
Why D is incorrect: You cannot call a static constructor explicitly; it's invoked by the runtime.
Reinforcement: Static constructors are perfect for one-time setup, like loading configuration.
4. Which of the following correctly declares a static class?
Show answer
Correct: A
Why A is correct: A static class is declared with the static keyword. It cannot be instantiated and can only contain static members.
Why B is incorrect: This is a regular class with a static method, not a static class.
Why C is incorrect: sealed prevents inheritance but doesn't make it static.
Why D is incorrect: abstract classes can have instance members and can be inherited.
Reinforcement: Static classes are containers for utility methods and constants.
5. What is the output of the following code?
public class Test
{
public static int Count = 0;
public int InstanceNumber;
public Test()
{
Count++;
InstanceNumber = Count;
}
public static void ResetCount() => Count = 0;
}
var t1 = new Test();
var t2 = new Test();
Test.ResetCount();
var t3 = new Test();
Console.WriteLine($"{t1.InstanceNumber} {t2.InstanceNumber} {t3.InstanceNumber}");Show answer
Correct: A — 1 2 1
Why A is correct: Count is static and shared. After two objects, Count = 2. ResetCount() sets Count = 0. Then t3 is created, incrementing Count to 1, so t3.InstanceNumber = 1. t1 and t2 have their numbers already set (1 and 2). So output is "1 2 1".
Why B is incorrect: This would happen if ResetCount wasn't called.
Why C is incorrect: This would be if the reset didn't happen and counter started at a different base.
Why D is incorrect: This would be if instance numbers were not set correctly.
Reinforcement: Static fields are shared; changes affect all objects. Instance fields capture the value at creation time.
You now understand static members — the type-level features that provide shared state and utility in C#!