Learn C# - Advanced Types & Collections
Series/Learn C#/Episode 5
Episode 5 of 23

Learn C# - Advanced Types & Collections

This episode expands your knowledge of C# types: struct, enum, tuple, and nullable value types, collections like Array, List, Dictionary, and HashSet, LINQ basics for querying collections, and nullable reference types for modern null safety.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

In episode 4 you mastered OOP with classes and records. Now we broaden our type vocabulary. Production developers spend most of their time working with data collections — and C# provides a very complete toolset for this.

Understanding when to use a struct versus a class, when to use a List versus a HashSet, and how to express data that may have no value will determine the quality and safety of your code.

Episode 5 covers the advanced types: struct, enum, tuple, and nullable types, then the core collections, followed by LINQ basics, and closes with nullable reference types, which are enabled by default in modern projects.

Struct, Enum, and Tuple

Struct: A Value Type

Unlike a class, which is a reference type, a struct is a value type — its value is copied when it's moved. Structs suit small, immutable data such as coordinates or quantities:

Struct and enum
struct Titik
{
    public int X { get; }
    public int Y { get; }
 
    public Titik(int x, int y) => (X, Y) = (x, y);
}
 
enum Ukuran
{
    Kecil = 1,
    Sedang = 2,
    Besar = 3
}
 
var p = new Titik(10, 20);
var ukuran = Ukuran.Besar;
Console.WriteLine($"{p.X},{p.Y} ukuran {(int)ukuran}");

Tuple

A tuple is a concise way to group several values without creating a new class. It's useful for simple return values:

Tuple with named elements
(int Min, int Max) Batas() => (1, 100);
 
var batas = Batas();
Console.WriteLine($"Min {batas.Min}, Max {batas.Max}");

The tuple (int Min, int Max) names its elements, making the code more readable. For data used across methods and classes, still choose a record.

Nullable Types

Nullable Value Types

Value types like int cannot normally hold a null value. For situations where the absence of a value is meaningful — such as a date of birth that hasn't been filled in — use a nullable value type with a question mark:

Nullable value type
int? umur = null;
umur = 30;
 
if (umur.HasValue)
{
    Console.WriteLine($"Umur {umur.Value}");
}
else
{
    Console.WriteLine("Umur belum diisi.");
}

Safe access can be shortened with umur ?? 0 — the null-coalescing operator returns a default value when the variable is null.

Core Collections

Array, List, Dictionary, HashSet

These four collections cover almost every need:

  • Array: fixed size, fast indexing.
  • List<T>: a dynamic list that grows as needed.
  • Dictionary<TKey, TValue>: fast lookup by key.
  • HashSet<T>: a set of unique values with set operations.
Using four collections
var angka = new[] { 1, 2, 3 };
 
var produk = new List<string> { "Kopi", "Teh" };
produk.Add("Susu");
 
var stok = new Dictionary<string, int>
{
    ["Kopi"] = 10,
    ["Teh"] = 5
};
 
var idUnik = new HashSet<int> { 1, 2, 2, 3 };
Console.WriteLine($"Stok kopi {stok["Kopi"]}, unik {idUnik.Count}");

Notice that HashSet rejects duplicates: idUnik.Count is 3, not 4. Choose the data structure according to your access patterns — Dictionary and HashSet offer O(1) average lookup.

LINQ Fundamentals

Method Syntax for Querying Collections

LINQ changes how you work with collections. Instead of writing manual loops, you describe what you want, not how:

Query LINQ
var nilaiSiswa = new List<int> { 55, 78, 90, 62, 88 };
 
var lulus = nilaiSiswa
    .Where(n => n >= 70)
    .OrderByDescending(n => n)
    .ToList();
 
Console.WriteLine($"Lulus {lulus.Count} siswa: {string.Join(", ", lulus)}");

The operators Where, OrderByDescending, and ToList are examples of deferred execution — a query is only executed when its results are actually needed. nilaiSiswa.Where(n => n >= 70) produces an IEnumerable that is evaluated when iterated.

Nullable Reference Types

Null Safety in the Compiler

Since C# 8, nullable reference types (NRT) warn you when you might misuse null. New projects enable them through the Nullable property in the .csproj file. Here's how to read them: string means must not be null, string? means may be null:

Nullable annotations
string nama = "Arman";      // tidak boleh null
string? kota = null;        // boleh null
 
if (kota is not null)
{
    Console.WriteLine($"{nama} tinggal di {kota}");
}

The compiler will warn you if you use kota without checking for null first. The is not null guard asserts that inside that block, the value is definitely present.

Closing

Key takeaways:

  • Struct is a value type; class and record are reference types.
  • Tuple for simple value groups; record for data used broadly.
  • Nullable value types and the ?? operator handle values that may be absent.
  • Choose collections based on access patterns: List, Dictionary, or HashSet.
  • LINQ uses deferred execution; NRT moves null detection to the compiler.

In the next episode 6 we deal with the unwanted but inevitable: exception handling and debugging — try-catch-finally, throw and custom exceptions, using declarations for resources, debugging in Visual Studio and VS Code, and basic logging.

Learn C# - Advanced Types & Collections | Learn C#