CodeOath
← All posts
C#65 min total · 17 parts

C# Fundamentals: Value Types, Reference Types, Boxing, and the Type System

Contents — Part 13 of 17: static Members: Shared Per Type, Not Per Instance
Part 13 of 17 · ~1 min

static Members: Shared Per Type, Not Per Instance

class Counter
{
    public static int Count = 0;
    public Counter() { Count++; }
}

new Counter();
new Counter();
new Counter();
Console.WriteLine(Counter.Count); // 3

static fields belong to the type itself — there's exactly one Count, regardless of how many Counter instances exist. This is a different axis from value-vs-reference (it's about who owns the storage, not how assignment behaves), but the two interact constantly in real code, especially around thread-safety: a mutable static field is shared state across every caller, everywhere, all the time, for the entire lifetime of the process (or app domain).

// A real bug pattern: a mutable static field used as scratch space, silently shared
// across concurrent requests in a web server
class ReportGenerator
{
    private static StringBuilder _buffer = new StringBuilder(); // shared across every call, every thread

    public string Generate(string data)
    {
        _buffer.Clear();
        _buffer.Append(data);
        return _buffer.ToString(); // two concurrent calls can interleave and corrupt each other's output
    }
}

The fix in cases like this is almost always to make the state either genuinely immutable, instance-level instead of static, or explicitly synchronized (a lock, or a thread-safe collection) — treating a mutable static field as harmless shared convenience is one of the most common sources of hard-to-reproduce concurrency bugs in real ASP.NET Core applications, precisely because a Singleton-registered service (see ASP.NET Core Web API for dependency injection lifetimes) has effectively the same sharing problem as a static field, just wrapped in DI.