iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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";

Discussion

Loading…