Back to Curriculum

Advanced C# Features

📚 Lesson 15 of 15 ⏱️ 55 min

Advanced C# Features

55 min

C# 8.0+ introduces features like nullable reference types, pattern matching, and records, making C# more expressive, safe, and modern. These features address common programming challenges and enable more concise, readable code. Understanding modern C# features enables writing idiomatic, efficient code. Modern C# features improve developer experience.

Records provide immutable data types with value-based equality, enabling concise definition of data-holding types. Records automatically generate equality, ToString(), and copy constructors. Records are ideal for DTOs, value objects, and immutable data. Understanding records enables modern data modeling. Records simplify immutable data types.

Pattern matching simplifies conditional logic by enabling matching on types, values, and conditions in switch expressions and if statements. Pattern matching reduces boilerplate code and makes intent clearer. Switch expressions enable pattern matching as expressions. Understanding pattern matching enables concise conditional logic. Pattern matching is powerful and expressive.

Nullable reference types help prevent null reference exceptions by making nullability explicit in the type system. With nullable reference types enabled, reference types are non-nullable by default, and you must explicitly mark them as nullable (string?). This catches null-related bugs at compile time. Understanding nullable reference types enables safer code. Nullable reference types are a major safety improvement.

Other modern features include init-only setters (immutable object initialization), default interface methods (interfaces with implementations), local functions (functions inside methods), and ranges/indices (array slicing). These features enable more expressive, efficient code. Understanding modern features enables leveraging C#'s full power. Modern C# continues to evolve.

Best practices include enabling nullable reference types, using records for immutable data, leveraging pattern matching for cleaner conditionals, staying updated with C# versions, and understanding when to use modern features. Understanding advanced C# features enables writing modern, idiomatic C# code. Modern C# features improve code quality and developer productivity.

Key Concepts

  • C# 8.0+ introduces nullable reference types, pattern matching, and records.
  • Records provide immutable data types with value-based equality.
  • Pattern matching simplifies conditional logic.
  • Nullable reference types help prevent null reference exceptions.
  • Modern C# features enable more expressive, safe code.

Learning Objectives

Master

  • Using records for immutable data types
  • Applying pattern matching in conditionals
  • Enabling and using nullable reference types
  • Leveraging other modern C# features

Develop

  • Understanding modern language evolution
  • Writing idiomatic, modern C# code
  • Appreciating C#'s continuous improvement

Tips

  • Enable nullable reference types for safer code.
  • Use records for immutable data-holding types.
  • Use pattern matching for cleaner conditional logic.
  • Stay updated with C# versions to leverage new features.

Common Pitfalls

  • Not enabling nullable reference types, missing null safety benefits.
  • Using records when classes would be better.
  • Overusing pattern matching when simple if-else would work.
  • Not understanding modern features, writing outdated code.

Summary

  • Modern C# introduces nullable reference types, pattern matching, and records.
  • Records enable concise immutable data types.
  • Pattern matching simplifies conditionals.
  • Nullable reference types prevent null reference exceptions.
  • Understanding modern C# features enables modern, idiomatic code.

Exercise

Demonstrate modern C# features including records, pattern matching, and nullable reference types.

using System;

// Record (C# 9.0+)
public record Person(string Name, int Age)
{
    public string Greeting => $"Hello, I'm {Name} and I'm {Age} years old";
}

// Pattern matching examples
public class Shape { }
public class Circle : Shape { public double Radius { get; set; } }
public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } }

public static class ShapeCalculator
{
    public static double GetArea(Shape shape) => shape switch
    {
        Circle c => Math.PI * c.Radius * c.Radius,
        Rectangle r => r.Width * r.Height,
        _ => throw new ArgumentException("Unknown shape")
    };
}

class Program
{
    static void Main(string[] args)
    {
        // Records
        var person1 = new Person("Alice", 30);
        var person2 = new Person("Alice", 30);
        
        Console.WriteLine(person1.Greeting);
        Console.WriteLine($"Records equal: {person1 == person2}"); // Value equality
        
        // Pattern matching
        var shapes = new Shape[]
        {
            new Circle { Radius = 5 },
            new Rectangle { Width = 4, Height = 6 }
        };
        
        foreach (var shape in shapes)
        {
            double area = ShapeCalculator.GetArea(shape);
            Console.WriteLine($"Area: {area:F2}");
        }
        
        // Nullable reference types
        string? nullableName = null;
        string nonNullableName = "John";
        
        // This would cause a warning in nullable context
        // Console.WriteLine(nullableName.Length);
        
        // Safe null handling
        Console.WriteLine($"Nullable name: {nullableName ?? "Unknown"}");
        Console.WriteLine($"Non-nullable name: {nonNullableName}");
        
        // Switch expression with pattern matching
        var message = nullableName switch
        {
            null => "Name is null",
            "" => "Name is empty",
            var name => $"Name is: {name}"
        };
        
        Console.WriteLine(message);
    }
}

Code Editor

Output