How to use readonly vs const vs static in C#

In C#, readonly, const, and static are keywords used to define variables with different behaviors in terms of mutability, memory allocation, and scope.

Understanding their differences is crucial for writing efficient and maintainable code. In this article we'll take a look at each and see how they are used.

1. const (Constant Values)

A const variable is a compile-time constant, meaning its value must be assigned at declaration and cannot be changed later.

Key Characteristics:

  • Must be assigned at declaration.
  • Stored in the assembly metadata (not allocated memory at runtime).
  • Can only be assigned primitive types, string, or enum values.
  • Cannot be modified after compilation.

Example:

public class MathConstants
{
    public const double Pi = 3.14159;
}

// Usage:
Console.WriteLine(MathConstants.Pi); // Output: 3.14159

Limitations:

  • Since const values are replaced at compile-time, updating a const in a library requires recompiling all dependent projects.
  • Cannot use non-primitive types (e.g., objects, lists).

2. readonly (Runtime Immutable Fields)

A readonly field allows initialization either at declaration or in the constructor but cannot be modified afterward.

Key Characteristics:

  • Can be assigned at declaration or inside a constructor.
  • Its value can change during runtime (but only in the constructor).
  • Works with all data types, including objects.
  • More flexible than const since values are resolved at runtime.

Example:

public class Circle
{
    public readonly double Radius;
    public readonly double Pi = 3.14159;

    public Circle(double radius)
    {
        Radius = radius; // Allowed because it's inside the constructor.
    }
}

// Usage:
Circle c = new Circle(5);
Console.WriteLine(c.Radius); // Output: 5

Best for: Values that should remain constant per instance but need to be assigned dynamically at runtime.


3. static (Shared Across All Instances)

A static variable belongs to the type itself rather than to any instance of the class.

Key Characteristics:

  • Shared across all instances of a class.
  • Cannot be used with instance constructors.
  • Initialized once and persists for the application’s lifetime.
  • Can be combined with readonly or const.

Example:

public class GlobalConfig
{
    public static string ApplicationName = "MyApp";
    public static readonly DateTime StartTime = DateTime.Now;
}

// Usage:
Console.WriteLine(GlobalConfig.ApplicationName); // Output: MyApp

Best for: Global state, caching, configuration values, and utility methods.


Key Differences Summary

Feature const readonly static
Mutability Immutable Immutable (after construction) Mutable
When Set Compile-time Runtime (constructor) Runtime
Memory Usage Stored in metadata Instance-based Type-based (shared)
Can Use Objects? ❌ No ✅ Yes ✅ Yes
Can Change After Initialization? ❌ No ❌ No (after constructor) ✅ Yes

Choosing the Right One:

  • Use const for fixed, compile-time values that will never change.
  • Use readonly for immutable values that need runtime initialization.
  • Use static for class-level data shared across all instances.

Understanding these differences helps you write cleaner, more efficient C# code. Happy coding! 🚀

0
80

Related

Removing duplicates from a list in C# is a common task, especially when working with large datasets. C# provides multiple ways to achieve this efficiently, leveraging built-in collections and LINQ.

Using HashSet (Fastest for Unique Elements)

A HashSet<T> automatically removes duplicates since it only stores unique values. This is one of the fastest methods:

List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
numbers = new HashSet<int>(numbers).ToList();
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 2, 3, 4, 5

Using LINQ Distinct (Concise and Readable)

LINQ’s Distinct() method provides an elegant way to remove duplicates:

List<int> numbers = new List<int> { 1, 2, 2, 3, 4, 4, 5 };
numbers = numbers.Distinct().ToList();
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 2, 3, 4, 5

Removing Duplicates by Custom Property (For Complex Objects)

When working with objects, DistinctBy() from .NET 6+ simplifies duplicate removal based on a property:

using System.Linq;
using System.Collections.Generic;

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

List<Person> people = new List<Person>
{
    new Person { Name = "Alice", Age = 30 },
    new Person { Name = "Bob", Age = 25 },
    new Person { Name = "Alice", Age = 30 }
};

people = people.DistinctBy(p => p.Name).ToList();
Console.WriteLine(string.Join(", ", people.Select(p => p.Name))); // Output: Alice, Bob

For earlier .NET versions, use GroupBy():

people = people.GroupBy(p => p.Name).Select(g => g.First()).ToList();

Performance Considerations

  • HashSet<T> is the fastest but only works for simple types.
  • Distinct() is easy to use but slower than HashSet<T> for large lists.
  • DistinctBy() (or GroupBy()) is useful for complex objects but may have performance trade-offs.

Conclusion

Choosing the best approach depends on the data type and use case. HashSet<T> is ideal for primitive types, Distinct() is simple and readable, and DistinctBy() (or GroupBy()) is effective for objects.

0
256

Reading a file line by line is useful when handling large files without loading everything into memory at once.

✅ Best Practice: Use File.ReadLines() which is more memory efficient.

Example

foreach (string line in File.ReadLines("file.txt"))
{
    Console.WriteLine(line);
}

Why use ReadLines()?

Reads one line at a time, reducing overall memory usage. Ideal for large files (e.g., logs, CSVs).

Alternative: Use StreamReader (More Control)

For scenarios where you need custom processing while reading the contents of the file:

using (StreamReader reader = new StreamReader("file.txt"))
{
    string? line;
    while ((line = reader.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}

Why use StreamReader?

Lets you handle exceptions, encoding, and buffering. Supports custom processing (e.g., search for a keyword while reading).

When to Use ReadAllLines()? If you need all lines at once, use:

string[] lines = File.ReadAllLines("file.txt");

Caution: Loads the entire file into memory—avoid for large files!

3
280

In C#, you can format an integer with commas (thousands separator) using ToString with a format specifier.

int number = 1234567;
string formattedNumber = number.ToString("N0"); // "1,234,567"
Console.WriteLine(formattedNumber);

Explanation:

"N0": The "N" format specifier stands for Number, and "0" means no decimal places. The output depends on the culture settings, so in regions where , is the decimal separator, you might get 1.234.567.

Alternative:

You can also specify culture explicitly if you need a specific format:

using System.Globalization;

int number = 1234567;
string formattedNumber = number.ToString("N0", CultureInfo.InvariantCulture);
Console.WriteLine(formattedNumber); // "1,234,567"
3
386