Variables
C# variables: value vs reference, var inference, const vs readonly, nullable reference types, and pattern-matching declarations.
C# — variables
EXAMPLE
// ===== Value types =====
int age = 30;
long id = 4_000_000_000L;
double pi = 3.14159;
bool ok = true;
char ch = 'A';
decimal aud = 49.95m; // 28-29 sig figs; use for money
// Defaults if uninitialised AS A FIELD: 0, false, '\0', null.
// Local variables MUST be assigned before reading.
// ===== Reference types =====
string name = "Alex";
List<int> nums = new() { 1, 2, 3 }; // target-typed new
// ===== var: implicit type =====
var greeting = "hi"; // string
var users = new Dictionary<string, User>();
// var only when the right-hand side names the type clearly.
// ===== const vs readonly =====
const int MaxRetries = 3; // compile-time constant; primitive or string only
public static readonly DateTime BootedAt = DateTime.UtcNow; // initialised at runtime, once
// ===== Nullable reference types (NRT) =====
#nullable enable
string definitely = "hi";
string? maybe = null;
maybe = ReadFromConfig(); // returns string?
if (maybe is not null)
{
Console.WriteLine(maybe.Length); // compiler tracks the flow analysis
}
string forced = maybe!; // null-forgiving '!' — use sparingly
// Null-coalescing and null-conditional:
int len = maybe?.Length ?? 0;
// ===== Tuples =====
var person = (Name: "Alex", Age: 30);
Console.WriteLine(person.Name);
var (n, a) = person; // destructure
// ===== Pattern declarations =====
object input = 42;
if (input is int n2 && n2 > 0)
Console.WriteLine("positive int " + n2);
// Switch expression:
string size = input switch
{
int x when x < 10 => "small",
int x when x < 100 => "medium",
int _ => "large",
_ => "non-int",
};
// ===== Records =====
public record User(int Id, string Name, string Email);
// Auto: equality by value, ToString, immutable by default, with-expressions:
var u = new User(1, "Alex", "a@x.io");
var u2 = u with { Name = "Sam" };
// ===== Scope =====
void Demo()
{
int outer = 1;
{
int inner = outer + 1;
// both in scope
}
// inner out of scope
}
// ===== params and out =====
int Sum(params int[] xs) => xs.Sum();
bool TryParseAge(string s, out int age)
{
if (int.TryParse(s, out age) && age >= 0) return true;
age = 0; return false;
}
// ===== Patterns to internalise =====
// - decimal for money, double for science, int for counts
// - var when the type is obvious from the right-hand side
// - Records for value-shape DTOs; with-expressions for non-destructive updates
// - Pattern matching > chains of casts
// - #nullable enable in every new file; treat warnings as errors
// ===== Pitfalls =====
// - decimal vs double on money -> floating-point rounding bugs
// - var with a network/IO return type your reader can't predict -> use the type name
// - Forgetting to initialise a local before use -> compile error (not runtime)
// - Comparing strings with == is reference equality? NO -- in C# string == uses value
// equality (operator overloaded). But Equals(StringComparison.Ordinal) is still safer for locale-sensitive paths.
// - Using ! to silence nullable warnings -> hides real bugs
Why it matters
Modern C# rewards reaching for records, nullable reference types, pattern matching, and target-typed new. decimal for money, NRT on by default, var where it reads cleanly. The language quietly does a lot of work to catch null and type slips before they hit prod.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
int age = 36; double pi = 3.14; var name = "Ada"; // type inferred const int MAX = 100;Try it Yourself »
Exercise
Use type inference.
name = "Ada";
Three letters.
Discussion
Loading…