OneCompiler

Modern C#

Modern C# Features

Modern C# has evolved significantly with powerful features that enhance code safety, readability, and performance. Here are some of the most important modern features introduced in recent versions of C#.

1. Nullable Reference Types

Nullable reference types help prevent null reference exceptions by making nullability explicit in your code. Introduced in C# 8.0, this feature provides compile-time warnings when you might be dereferencing a null reference.

Enabling Nullable Reference Types

#nullable enable

public class Person
{
    public string Name { get; set; } = string.Empty; // Non-nullable
    public string? Email { get; set; } // Nullable
    public int Age { get; set; }
}

Example Usage

using System;

#nullable enable

namespace NullableExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Person person = new Person();
            person.Name = "John Doe";
            person.Email = null; // This is allowed
            
            Console.WriteLine($"Name: {person.Name}");
            Console.WriteLine($"Email: {person.Email ?? "No email provided"}");
            
            // This would generate a warning if Email is null
            // Console.WriteLine($"Email length: {person.Email.Length}");
        }
    }
    
    public class Person
    {
        public string Name { get; set; } = string.Empty;
        public string? Email { get; set; }
        public int Age { get; set; }
    }
}

2. Records

Records provide a concise syntax for creating immutable data types with value-based equality. They're perfect for data transfer objects and value objects.

Basic Record Syntax

public record Person(string FirstName, string LastName, int Age);

Record with Custom Members

using System;

namespace RecordExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var person1 = new Person("John", "Doe", 30);
            var person2 = new Person("John", "Doe", 30);
            var person3 = person1 with { Age = 31 }; // Non-destructive mutation
            
            Console.WriteLine($"Person 1: {person1}");
            Console.WriteLine($"Person 2: {person2}");
            Console.WriteLine($"Person 3: {person3}");
            Console.WriteLine($"Are person1 and person2 equal? {person1 == person2}");
        }
    }
    
    public record Person(string FirstName, string LastName, int Age)
    {
        public string FullName => $"{FirstName} {LastName}";
        
        public bool IsAdult => Age >= 18;
    }
}

3. Pattern Matching

Pattern matching allows you to test expressions against patterns and extract values. It's been enhanced significantly in modern C#.

Switch Expressions

using System;

namespace PatternMatchingExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(GetDayType(DayOfWeek.Monday));
            Console.WriteLine(GetDayType(DayOfWeek.Saturday));
            Console.WriteLine(GetDayType(DayOfWeek.Sunday));
            
            var numbers = new int[] { 1, 2, 3, 4, 5 };
            Console.WriteLine(DescribeCollection(numbers));
            
            var emptyArray = new int[] { };
            Console.WriteLine(DescribeCollection(emptyArray));
        }
        
        public static string GetDayType(DayOfWeek day) => day switch
        {
            DayOfWeek.Monday or DayOfWeek.Tuesday or DayOfWeek.Wednesday 
                or DayOfWeek.Thursday or DayOfWeek.Friday => "Weekday",
            DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend",
            _ => "Unknown"
        };
        
        public static string DescribeCollection<T>(T[] array) => array switch
        {
            { Length: 0 } => "Empty array",
            { Length: 1 } => "Single element array",
            { Length: > 1 and <= 5 } => "Small array",
            { Length: > 5 } => "Large array",
            _ => "Unknown"
        };
    }
}

Type Patterns and Property Patterns

using System;

namespace AdvancedPatternMatching
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var shapes = new Shape[]
            {
                new Circle(5),
                new Rectangle(4, 6),
                new Square(3)
            };
            
            foreach (var shape in shapes)
            {
                Console.WriteLine($"Area: {GetArea(shape)}");
                Console.WriteLine($"Description: {DescribeShape(shape)}");
            }
        }
        
        public static double GetArea(Shape shape) => shape switch
        {
            Circle { Radius: var r } => Math.PI * r * r,
            Rectangle { Width: var w, Height: var h } => w * h,
            Square { Side: var s } => s * s,
            _ => 0
        };
        
        public static string DescribeShape(Shape shape) => shape switch
        {
            Circle { Radius: > 10 } => "Large circle",
            Circle => "Circle",
            Rectangle { Width: var w, Height: var h } when w == h => "Square-like rectangle",
            Rectangle => "Rectangle",
            Square { Side: < 5 } => "Small square",
            Square => "Square",
            _ => "Unknown shape"
        };
    }
    
    public abstract record Shape;
    public record Circle(double Radius) : Shape;
    public record Rectangle(double Width, double Height) : Shape;
    public record Square(double Side) : Shape;
}

4. Top-Level Programs

Top-level programs eliminate the need for boilerplate code in simple applications. Available since C# 9.0.

Traditional Program Structure

using System;

namespace TraditionalProgram
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

Top-Level Program (Modern)

using System;

Console.WriteLine("Hello, World!");
Console.WriteLine("This is a top-level program!");

// You can still define methods and classes
void PrintMessage(string message)
{
    Console.WriteLine($"Message: {message}");
}

PrintMessage("Top-level programs are concise!");

// Local functions and classes are also supported
public class Helper
{
    public static void DoSomething()
    {
        Console.WriteLine("Helper method called");
    }
}

Helper.DoSomething();

5. String Interpolation and Raw Strings

String Interpolation

String interpolation provides a more readable way to format strings.

using System;

namespace StringInterpolationExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            string name = "Alice";
            int age = 30;
            double salary = 75000.50;
            
            // Basic interpolation
            Console.WriteLine($"Name: {name}, Age: {age}");
            
            // With formatting
            Console.WriteLine($"Salary: {salary:C}");
            Console.WriteLine($"Age in hex: {age:X}");
            
            // With expressions
            Console.WriteLine($"Next year, {name} will be {age + 1} years old");
            
            // Conditional expressions
            Console.WriteLine($"{name} is {(age >= 18 ? "an adult" : "a minor")}");
        }
    }
}

Raw Strings (C# 11+)

Raw strings allow you to include quotes and other special characters without escaping.

using System;

namespace RawStringsExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Traditional string with escaping
            string json1 = "{\n  \"name\": \"John\",\n  \"age\": 30\n}";
            
            // Raw string - no escaping needed
            string json2 = """
                {
                  "name": "John",
                  "age": 30
                }
                """;
            
            // Raw string with interpolation
            string name = "Alice";
            int age = 25;
            string interpolatedJson = $$"""
                {
                  "name": "{{name}}",
                  "age": {{age}},
                  "status": "active"
                }
                """;
            
            Console.WriteLine("Traditional JSON:");
            Console.WriteLine(json1);
            Console.WriteLine("\nRaw string JSON:");
            Console.WriteLine(json2);
            Console.WriteLine("\nInterpolated raw string JSON:");
            Console.WriteLine(interpolatedJson);
            
            // Multi-line raw string for SQL
            string sql = """
                SELECT p.FirstName, p.LastName, p.Age
                FROM Person p
                WHERE p.Age > 18
                ORDER BY p.LastName, p.FirstName
                """;
            
            Console.WriteLine("\nSQL Query:");
            Console.WriteLine(sql);
        }
    }
}

Summary

Modern C# features significantly improve code quality, safety, and developer productivity:

  • Nullable Reference Types help prevent null reference exceptions
  • Records provide immutable data types with value-based equality
  • Pattern Matching enables powerful and expressive conditional logic
  • Top-Level Programs reduce boilerplate code for simple applications
  • String Interpolation and Raw Strings make string formatting more readable and maintainable

These features work together to make C# a more powerful and expressive language while maintaining backward compatibility and performance.