Static members belong to the type itself rather than to any instance. Math.PI, Convert.ToInt32(), and Console.WriteLine() are all static — you call them on the class name, not on an object. Static is powerful for utilities and pure functions, but must be used carefully in ASP.NET Core because static state is shared across all requests, all threads, and all users — a leaked per-user value in a static field is a security vulnerability and a data-corruption bug waiting to happen.
Static Fields, Properties, and Methods
public class SlugGenerator
{
// ── Static field — shared across all instances and all threads ─────────
private static int _generatedCount = 0;
// ── Static read-only property ─────────────────────────────────────────
public static int TotalGenerated => _generatedCount;
// ── Static method — called on the type, not an instance ───────────────
public static string Generate(string title)
{
Interlocked.Increment(ref _generatedCount); // thread-safe increment
return title
.ToLowerInvariant()
.Trim()
.Replace(" ", "-")
.Replace("--", "-");
}
}
// Call static members on the type name — not on an instance
string slug = SlugGenerator.Generate("Hello World"); // "hello-world"
Console.WriteLine(SlugGenerator.TotalGenerated); // 1
Note: Static fields are shared across ALL threads simultaneously. In a multi-threaded ASP.NET Core application handling hundreds of concurrent requests, two threads could read and write the same static field at exactly the same moment — causing a race condition. Use
Interlocked.Increment() for counters, lock statements for more complex operations, or better yet avoid mutable static state altogether. Read-only static data (constants, compiled regular expressions, configuration) is safe because it is never written after initialisation.Tip: A common legitimate use of static members in ASP.NET Core is pre-compiled regular expressions:
private static readonly Regex EmailRegex = new(@"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.Compiled);. Compiling a Regex is expensive — doing it on every request wastes CPU. A static readonly field compiles it once at class load time and reuses it across all requests safely (Regex match is thread-safe). Similarly, static readonly instances of JsonSerializerOptions and HttpClient follow this pattern.Warning: Do not store per-request or per-user data in static fields in ASP.NET Core. Unlike a console application where only one user exists, an ASP.NET Core application serves thousands of simultaneous users. A static field holding the “current user’s ID” would be overwritten by every incoming request, causing one user to see another’s data. Use dependency injection with Scoped lifetime for per-request state, or pass data as method parameters.
Static Classes — Pure Utility Containers
// A static class can only contain static members — cannot be instantiated
public static class StringExtensions
{
// Extension method — adds methods to existing types
public static string ToSlug(this string value)
=> value.ToLowerInvariant().Trim().Replace(" ", "-");
public static bool IsValidEmail(this string value)
=> !string.IsNullOrWhiteSpace(value) && value.Contains('@') && value.Contains('.');
public static string Truncate(this string value, int maxLength)
=> value.Length <= maxLength ? value : value[..maxLength] + "…";
}
// Extension methods are called as if they are instance methods
string title = "Hello World";
string slug = title.ToSlug(); // "hello-world"
string email = "alice@example.com";
bool isValid = email.IsValidEmail(); // true
string truncated = title.Truncate(5); // "Hello…"
// Static class for application constants
public static class AppConstants
{
public const int MaxPageSize = 100;
public const int DefaultPageSize = 10;
public const string DefaultRole = "User";
public static readonly string[] AllowedImageTypes = { "image/jpeg", "image/png", "image/webp" };
}
Static Constructor — One-Time Type Initialisation
// Static constructor runs once, automatically, before the type is first used
public class CurrencyConverter
{
private static readonly Dictionary<string, decimal> _rates;
static CurrencyConverter() // no access modifier, no parameters
{
// Expensive one-time setup — runs once per AppDomain
_rates = new Dictionary<string, decimal>
{
["USD"] = 1.00m,
["EUR"] = 0.92m,
["GBP"] = 0.79m,
};
Console.WriteLine("CurrencyConverter static constructor ran.");
}
public static decimal Convert(decimal amount, string from, string to)
{
decimal usd = amount / _rates[from];
return usd * _rates[to];
}
}
Common Mistakes
Mistake 1 — Mutable static state in ASP.NET Core (shared across requests)
❌ Wrong — all requests share and overwrite this:
public class AuthController : ControllerBase
{
private static User? _currentUser; // DANGEROUS — shared across ALL users!
✅ Correct — use injected, scoped services for per-request state.
Mistake 2 — Instantiating a static class
❌ Wrong — compile error: cannot create an instance of a static class:
var ext = new StringExtensions(); // compile error!
✅ Correct — call static class members directly on the type name.