38. Static Keyword in C#
Level: Intermediate
Goal: Create methods and fields that belong to the type, not to individual objects.
Normal (instance):
Student Sahasra = new Student("Sahasra"); // Create object
Sahasra.GetAge(); // Call method on object
Static (no object needed):
Math.Min(5, 10); // Call without creating Math object
DateTime.Now; // Get current time directly
int.Parse("123"); // Parse without creating int
Static is for shared, utility functionality.
staticmembers belong to type, not instances- Static methods - call without creating objects
- Static fields - shared data across all instances
- Static constructors - initialize shared state once
- Static classes - only static members allowed
- When to use static vs instance
- Common pitfalls and thread safety
- Real-world patterns (Singleton, Utility classes)
static Keyword Reference
| Member | Type | Instance Needed | Shared | Use Case | Example |
|---|---|---|---|---|---|
| static method | Method | ? No | ? Yes | Pure calculation, helper | Math.Max(a, b) |
| static field | Field | ? No | ? Yes | Counter, configuration | _totalCount |
| static property | Property | ? No | ? Yes | Readonly shared state | DateTime.Now |
| static constructor | Constructor | ? No | ? Yes | Initialize shared state once | Load config at startup |
| static class | Class | ? No | ? All members | Utility/helper class | Math, File |
| instance method | Method | ? Yes | ? No | Behavior per object | student.GetGrade() |
| instance field | Field | ? Yes | ? No | Per-object data | student.Name |
| instance property | Property | ? Yes | ? No | Per-object state | student.Percentage |
Quick Definitions
- static - Belongs to class, not instance
- static method - Call without creating object (e.g.,
Math.Max()) - static field - Shared across all instances
- static property - Readonly shared state
- static constructor - Initialize shared state once at startup
- static class - Only static members (e.g.,
Math,File) - Instance member - Belongs to each object, not class
- Shared state - One value for all objects
- Thread-safe - Safe when multiple threads access simultaneously
- Utility class - Collection of static helper methods
static Methods
public class GradeCalculator
{
// static - call without creating GradeCalculator object
public static string GetGrade(double percentage)
{
if (percentage >= 90)
{
return "A+";
}
else if (percentage >= 80)
{
return "A";
}
else if (percentage >= 70)
{
return "B";
}
else if (percentage >= 60)
{
return "C";
}
else if (percentage >= 35)
{
return "D";
}
else
{
return "F";
}
}
public static double GetPercentage(int[] marks)
{
if (marks.Length == 0)
{
return 0;
}
else
{
return marks.Average();
}
}
public static bool HasPassed(double percentage)
{
return percentage >= 35;
}
}
// Call directly - no new GradeCalculator()
string grade = GradeCalculator.GetGrade(87.5);
double pct = GradeCalculator.GetPercentage(new[] { 82, 91, 78 });
bool passed = GradeCalculator.HasPassed(pct);
static Fields - Shared Across All Instances
public class Student
{
// static - ONE copy shared by all Student objects
private static int _totalEnrolled = 0;
private static int _nextId = 1;
// instance - EACH Student has its own copy
public int Id { get; }
public string Name { get; set; } = "";
public string ClassName { get; set; } = "";
public Student(string name, string className)
{
Id = _nextId++; // use shared counter
Name = name;
ClassName = className;
_totalEnrolled++; // increment shared total
}
// static property - access shared data
public static int TotalEnrolled
{
get { return _totalEnrolled; }
}
// instance method - uses this object's data
public void DisplayInfo()
{
Console.WriteLine($"[{Id}] {Name} - {ClassName}");
}
}
var s1 = new Student("Sahasra Kumar", "10th");
var s2 = new Student("Priya Sharma", "10th");
var s3 = new Student("Arjun Reddy", "11th");
Console.WriteLine($"Total enrolled: {Student.TotalEnrolled}"); // 3
// Student.TotalEnrolled accessed on TYPE - not on instance
static Constructor - Runs Once
public class SchoolSettings
{
public static string SchoolName { get; private set; }
public static string AcademicYear { get; private set; }
public static decimal AnnualFees { get; private set; }
// static constructor - runs ONCE before first use
// no access modifier, no parameters
static SchoolSettings()
{
// Load from config, DB, environment - runs once at startup
SchoolName = "NexCoding Academy";
AcademicYear = $"{DateTime.Now.Year}-{DateTime.Now.Year + 1}";
AnnualFees = 60000m;
Console.WriteLine("School settings initialized.");
}
}
// First access triggers static constructor
Console.WriteLine(SchoolSettings.SchoolName); // triggers constructor
Console.WriteLine(SchoolSettings.AcademicYear); // already initialized
static Class - Cannot Instantiate
// static class - only static members, cannot be instantiated
public static class AttendanceHelper
{
public static double CalculateRate(int present, int total)
{
if (total == 0) return 0;
return (double)present / total * 100;
}
public static bool MeetsMinimum(int present, int total, double minimum = 75.0)
=> CalculateRate(present, total) >= minimum;
public static string GetStatus(double rate) => rate switch
{
>= 90 => "Excellent",
>= 75 => "Good",
>= 60 => "Warning",
_ => "Shortage - Detained"
};
}
// new AttendanceHelper() <- error - cannot instantiate static class
double rate = AttendanceHelper.CalculateRate(22, 26);
bool ok = AttendanceHelper.MeetsMinimum(22, 26);
string status = AttendanceHelper.GetStatus(rate);
Console.WriteLine($"Attendance: {rate:F1}% - {status}");
static vs Instance - When to Use
Use static when | Use instance when |
|---|---|
| No state needed (pure calculation) | Needs dependencies (DB, services) |
| Utility/helper methods | Needs its own data per object |
| Shared counters/config | Behaviour varies per instance |
| Extension methods | Follows OOP design |
When You'll Use This in SMS
SMS uses static members for configuration and utilities:
// Static configuration (shared app-wide)
public static class AppConfig
{
public static string DatabaseConnection => "Server=localhost;Database=SMS";
public static string SchoolName => "NexCoding Academy";
public static int MaxStudentsPerClass => 50;
}
// Static helper methods (no instance needed)
public static class ValidationHelper
{
public static bool IsValidEmail(string email)
=> email.Contains("@") && email.Contains(".");
public static bool IsValidPhone(string phone)
=> phone.Length == 10 && phone.All(char.IsDigit);
}
// Static counter (shared across all enrollments)
public class EnrollmentService
{
private static int _totalEnrolled = 0; // Shared
public static void Enroll(Student student)
{
_totalEnrolled++;
Console.WriteLine($"Total enrolled: {_totalEnrolled}");
}
}
Real impact: Without static = duplicate code, repeated calculations. With static = DRY helpers, shared configuration, efficient.
Try This Now
- Create static
AppConfigclass with school settings - Build static validation methods for email/phone
- Add static counter to track total students
- Use static helper instead of instance methods
- Create static class (not instantiable) for utilities
Static keyword explained: static methods/fields/properties, when NOT to use static, static constructors, Singleton pattern. Video coming soon. Subscribe to NexCoding YouTube for updates.
Common Mistakes
? Making everything static (not OOP):
public static class StudentManager
{
public static List<Student> students = new(); // Mutable shared state!
public static void AddStudent(Student s) => students.Add(s);
public static Student GetStudent(int id) => students[id];
}
// Problems: Not testable, thread-unsafe, hard to manage multiple schools
? Use instance with dependency injection:
public class StudentManager
{
private readonly IStudentRepository _repo;
public StudentManager(IStudentRepository repo) => _repo = repo;
public async Task<Student> GetStudentAsync(int id)
=> await _repo.GetByIdAsync(id);
}
? Static field without thread safety (race condition):
public class Counter
{
public static int Count = 0; // Not thread-safe!
public static void Increment() => Count++; // Multiple threads corrupt value
}
? Use lock or Interlocked:
public class Counter
{
private static int _count = 0;
private static readonly object _lock = new();
public static void Increment()
{
lock (_lock)
{
_count++; // Thread-safe
}
}
}
? Calling static method on instance (confusing):
var student = new Student();
string grade = student.GetGrade(85); // Looks like instance method
? Call static on type:
string grade = GradeCalculator.GetGrade(85); // Clear it's static
? Static field modified from multiple places (hidden dependencies):
public static decimal LateFeeRate = 0.01m; // Changed anywhere in app!
// Hard to track who changes it
LateFeeRate = 0.015m; // Where did this come from?
? Use readonly property:
public static decimal LateFeeRate { get; } = 0.01m; // Immutable after init
? Storing mutable objects in static (shared and modified):
public static List<string> ErrorLog = new(); // Shared mutable list
ErrorLog.Add("Error 1"); // From thread A
ErrorLog.Add("Error 2"); // From thread B - race condition
? Use thread-safe collection or readonly:
public static readonly ConcurrentBag<string> ErrorLog = new(); // Thread-safe
ErrorLog.Add("Error 1"); // Safe from multiple threads
Best Practices
- Use static for utility methods only - Math.Max, helper calculations
- Use static constructor for one-time initialization - Load config, singletons
- Make static fields readonly - Immutable = safe
- Use static class for extension methods - Standard practice
- Don't store mutable state in static - Use instance with DI instead
- Lock static fields if modified - Thread safety required
- Document static members clearly - Shared state is surprising
- Prefer dependency injection over static - More testable, flexible
- Avoid static if needs to change per application - Use instance instead
- Use static for constants and config -
Math.PI, database connection string - Test static methods thoroughly - Shared state interactions complex
- Consider static vs Singleton pattern - Static simpler, Singleton more flexible
Static: Belongs to type. One copy shared by all instances. Access on type: ClassName.Member.
Instance: Belongs to object. Each instance has own copy. Access on object: obj.Member.
public class Student
{
public static int TotalEnrolled = 0; // Shared - one copy
public string Name; // Per instance - one per object
}
var s1 = new Student { Name = "Sahasra" };
var s2 = new Student { Name = "Priya" };
Student.TotalEnrolled = 2; // Access on TYPE
Console.WriteLine(s1.Name); // Access on instance
Static shared, instance unique per object.
Static: Pure calculation, no state. Math.Sqrt(16), int.TryParse(...).
Instance: Needs dependencies or per-object state. student.GetGrade(), repo.SaveAsync().
// Static - pure math, no state
public static double CalculatePercentage(int[] marks)
=> marks.Average();
// Instance - needs injected repo
public class StudentService
{
private readonly IStudentRepository _repo;
public StudentService(IStudentRepository repo) => _repo = repo;
public async Task<Student> GetStudentAsync(int id)
=> await _repo.GetByIdAsync(id);
}
Rule: Static for utility, instance for business logic with dependencies.
Static constructor runs once before first access to type. No parameters, no access modifier.
public class SchoolConfig
{
public static string SchoolName { get; private set; }
static SchoolConfig() // Runs ONCE at first access
{
SchoolName = "NexCoding Academy";
Console.WriteLine("Config initialized"); // Print once
}
}
Console.WriteLine(SchoolConfig.SchoolName); // Triggers static constructor
Console.WriteLine(SchoolConfig.SchoolName); // Already initialized, doesn't run again
Use for: Initialize shared state, load config, singleton setup.
- Hard to test: Static is global state, hard to mock in unit tests.
- Not OOP: Loses flexibility, inheritance, polymorphism.
- Thread-unsafe: Shared state accessed by multiple threads = race conditions.
- Hidden dependencies: Code silently depends on static state.
- Not mockable: Can't inject test doubles.
// Bad - static database
public static class UserRepository
{
public static User GetUser(int id) { } // Hard to test - always hits DB
}
// Good - instance with DI
public class UserRepository : IUserRepository
{
private readonly IDatabase _db;
public UserRepository(IDatabase db) => _db = db;
public User GetUser(int id) { } // Testable - inject mock DB
}
Use static sparingly. Default to instance with dependency injection.
No. Static field accessed by multiple threads = race condition.
public static int Count = 0;
// Two threads both increment
Task.Run(() => Count++); // Thread A reads 0, increments to 1
Task.Run(() => Count++); // Thread B reads 0, increments to 1
// Expected 2, got 1 - race condition!
Solutions:
- Use
lockfor complex operations - Use
Interlockedfor simple increment - Use
readonlyif value never changes
// Interlocked - atomic increment
public static int Count = 0;
Interlocked.Increment(ref Count); // Thread-safe
// Lock - general synchronization
private static readonly object _lock = new();
lock (_lock)
{
Count++; // Thread-safe
}
Static without synchronization = dangerous with concurrency.
Static class: Utility methods, no state. Simple, no instantiation.
Singleton: Need single instance, state management, interface implementation.
// Static - pure utility
public static class MathHelper
{
public static double CalculateGPA(double[] marks) => marks.Average();
}
// Singleton - needs state, dependency injection
public class DatabaseConnection : IConnection // Implements interface
{
private static readonly Lazy<DatabaseConnection> _instance
= new(() => new DatabaseConnection());
public static DatabaseConnection Instance => _instance.Value;
public string ConnectionString { get; set; } // State
}
// Singleton injectable
services.AddSingleton<DatabaseConnection>();
var conn = serviceProvider.GetRequiredService<DatabaseConnection>();
Static for utilities, Singleton for managed shared objects.
Use ChatGPT, Claude, or Copilot to go deeper on C# static keyword. Try these prompts:
"What is the difference between static and instance members in C#?""When should I make a method static vs instance in C#?""What is a static constructor in C# and when does it run?""What are the problems with overusing static in C# code?"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.