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.