Skip to main content

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.

ℹ️ What You'll Learn
  • static members 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

MemberTypeInstance NeededSharedUse CaseExample
static methodMethod? No? YesPure calculation, helperMath.Max(a, b)
static fieldField? No? YesCounter, configuration_totalCount
static propertyProperty? No? YesReadonly shared stateDateTime.Now
static constructorConstructor? No? YesInitialize shared state onceLoad config at startup
static classClass? No? All membersUtility/helper classMath, File
instance methodMethod? Yes? NoBehavior per objectstudent.GetGrade()
instance fieldField? Yes? NoPer-object datastudent.Name
instance propertyProperty? Yes? NoPer-object statestudent.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

💻 Try It — Console App
💡 Paste into Program.cs and press F5⌥ GitHub
public class FeeService
{
// ? static - pure calculation, no state needed
public static decimal CalculateLateFee(decimal principal, int daysLate)
=> principal * 0.01m * daysLate;

public static bool IsOverdue(DateTime dueDate)
=> DateTime.Now > dueDate;

// ? instance - needs injected dependencies, has state
private readonly IStudentRepository _repo;

public FeeService(IStudentRepository repo) => _repo = repo;

public async Task<decimal> GetBalanceAsync(int studentId)
{
var student = await _repo.GetByIdAsync(studentId);
return student?.OutstandingFees ?? 0;
}
}

// Static - call directly
decimal late = FeeService.CalculateLateFee(50000m, 30);

// Instance - needs repo injected
var service = new FeeService(repo);
decimal balance = await service.GetBalanceAsync(1);
Use static whenUse instance when
No state needed (pure calculation)Needs dependencies (DB, services)
Utility/helper methodsNeeds its own data per object
Shared counters/configBehaviour varies per instance
Extension methodsFollows 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

  1. Create static AppConfig class with school settings
  2. Build static validation methods for email/phone
  3. Add static counter to track total students
  4. Use static helper instead of instance methods
  5. Create static class (not instantiable) for utilities

ℹ️ Video Tutorial

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

  1. Use static for utility methods only - Math.Max, helper calculations
  2. Use static constructor for one-time initialization - Load config, singletons
  3. Make static fields readonly - Immutable = safe
  4. Use static class for extension methods - Standard practice
  5. Don't store mutable state in static - Use instance with DI instead
  6. Lock static fields if modified - Thread safety required
  7. Document static members clearly - Shared state is surprising
  8. Prefer dependency injection over static - More testable, flexible
  9. Avoid static if needs to change per application - Use instance instead
  10. Use static for constants and config - Math.PI, database connection string
  11. Test static methods thoroughly - Shared state interactions complex
  12. Consider static vs Singleton pattern - Static simpler, Singleton more flexible

🎯 Q1: What's the difference between static and instance members?

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.

🎯 Q2: When should I make a method static vs instance?

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.

🎯 Q3: What is a static constructor and when does it run?

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.

🎯 Q4: What problems come from overusing static?
  1. Hard to test: Static is global state, hard to mock in unit tests.
  2. Not OOP: Loses flexibility, inheritance, polymorphism.
  3. Thread-unsafe: Shared state accessed by multiple threads = race conditions.
  4. Hidden dependencies: Code silently depends on static state.
  5. 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.

🎯 Q5: Static field - is it thread-safe?

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 lock for complex operations
  • Use Interlocked for simple increment
  • Use readonly if 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.

🎯 Q6: Static class vs Singleton - which to use?

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 AI to Learn Faster

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.

Next Article

Method Hiding ->

nexcoding.in