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! 🚀

1
86

Related

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!

4
293

When working with URLs in C#, encoding is essential to ensure that special characters (like spaces, ?, &, and =) don’t break the URL structure. The recommended way to encode a string for a URL is by using Uri.EscapeDataString(), which converts unsafe characters into their percent-encoded equivalents.

string rawText = "hello world!";
string encodedText = Uri.EscapeDataString(rawText);

Console.WriteLine(encodedText); // Output: hello%20world%21

This method encodes spaces as %20, making it ideal for query parameters.

For ASP.NET applications, you can also use HttpUtility.UrlEncode() (from System.Web), which encodes spaces as +:

using System.Web;

string encodedText = HttpUtility.UrlEncode("hello world!");
Console.WriteLine(encodedText); // Output: hello+world%21

For .NET Core and later, Uri.EscapeDataString() is the preferred choice.

28
1179

Closing a SqlDataReader correctly prevents memory leaks, connection issues, and unclosed resources. Here’s the best way to do it.

Use 'using' to Auto-Close

Using using statements ensures SqlDataReader and SqlConnection are closed even if an exception occurs.

Example

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    using (SqlCommand cmd = new SqlCommand("SELECT * FROM Users", conn))
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        while (reader.Read())
        {
            Console.WriteLine(reader["Username"]);
        }
    } // ✅ Auto-closes reader here
} // ✅ Auto-closes connection here

This approach auto-closes resources when done and it is cleaner and less error-prone than manual closing.

⚡ Alternative: Manually Close in finally Block

If you need explicit control, you can manually close it inside a finally block.

SqlDataReader? reader = null;
try
{
    using SqlConnection conn = new SqlConnection(connectionString);
    conn.Open();
    using SqlCommand cmd = new SqlCommand("SELECT * FROM Users", conn);
    reader = cmd.ExecuteReader();

    while (reader.Read())
    {
        Console.WriteLine(reader["Username"]);
    }
}
finally
{
    reader?.Close();  // ✅ Closes reader if it was opened
}

This is slightly more error prone if you forget to add a finally block. But might make sense when you need to handle the reader separately from the command or connection.

1
168