← Back to Articles         
C# 11.0 – Important Features Explained

C# 11.0 – Important Features Explained

Published on 12 Oct 2026     11 min read C#
C# 11 - Features

C# 11.0 was released in November 2022 alongside .NET 7. It introduced improvements in object initialization, string handling, generic programming, pattern matching, and UTF-8 processing.

The five important features are:

  1. Required Members (required) – Enforces initialization of mandatory properties.

  2. Raw String Literals – Simplifies multiline strings, JSON, SQL, and text containing special characters.

  3. Generic Math – Supports mathematical operations on generic numeric types.

  4. List Patterns – Enables pattern matching on arrays and lists.

  5. UTF-8 String Literals – Creates UTF-8 encoded byte data directly from string literals.

 

1. Required Members (required)

What is it?

The required keyword was introduced in C# 11 to ensure that specific fields or properties are initialized when creating an object.

Before C# 11, developers commonly used constructors to enforce mandatory values.

With required, we can declare mandatory properties and initialize them using object initializers.

Why is it used?

Consider an Employee class with three properties:

  • Id – Mandatory

  • Name – Mandatory

  • Department – Optional

We want to ensure that every employee has an ID and name.

Example Program

public class Employee
{
    public required int Id { get; set; }

    public required string Name { get; set; }

    public string? Department { get; set; }
}

class Program
{
    static void Main()
    {
        Employee emp = new Employee
        {
            Id = 101,
            Name = "John",
            Department = "IT"
        };

        Console.WriteLine(emp.Id);
        Console.WriteLine(emp.Name);
        Console.WriteLine(emp.Department);
    }
}

Output

101
John
IT

How does it work?

Employee Class
      |
      |-- required Id
      |-- required Name
      |-- optional Department
      |
      v
Create Employee Object
      |
      v
Compiler checks required members
      |
      +-- All assigned --> Compilation succeeds
      |
      +-- Missing one --> Compilation error

What happens if a required property is missing?

Employee emp = new Employee
{
    Id = 101
};

Result: Compilation error because the Name property was not initialized.

Difference between required and init

required

init

Enforces initialization

Restricts when assignment is allowed

Can use get; set;

Uses get; init;

Property must be assigned during object creation

Property initialization is optional unless also marked required

Does not prevent later changes

Prevents normal assignment after initialization

We can combine both:

public required string Name { get; init; }

This ensures the caller supplies Name during object creation and prevents normal reassignment afterward.

Key Points

  • Introduced in C# 11.

  • Applicable to fields and properties.

  • Enforced by the compiler, not automatically by runtime validation.

  • A required reference property can still receive null in some circumstances; nullable reference type analysis helps detect that.

  • Constructors marked with [SetsRequiredMembers] can indicate that they initialize required members.

  • Useful for DTOs, configuration objects, and domain models.

Interview Tip: required enforces initialization, whereas init restricts modification after initialization. They solve different problems.

 

2. Raw String Literals

What is it?

Raw String Literals were introduced in C# 11 to simplify writing strings containing special characters, double quotes, and multiple lines.

They use three or more double quotes (""") as delimiters.

Unlike normal strings, raw strings do not require escaping most special characters.

Why is it used?

Before C# 11, writing JSON or multiline SQL queries could require escape characters or verbatim strings.

For example:

string json = "{\"name\":\"John\",\"age\":30}";

With C# 11:

string json = """
{
    "name": "John",
    "age": 30
}
""";

The second version is easier to read and maintain.

Example Program

class Program
{
    static void Main()
    {
        string json = """
        {
            "id": 101,
            "name": "John",
            "department": "IT"
        }
        """;

        Console.WriteLine(json);
    }
}

Output

{
    "id": 101,
    "name": "John",
    "department": "IT"
}

How does it work?

Raw String Literal
        |
        v
Opening delimiter (""")
        |
        v
String Content
  - Quotes allowed
  - Backslashes preserved
  - Multiple lines supported
        |
        v
Closing delimiter (""")
        |
        v
String created without escape processing

Example: SQL Query

string sql = """
SELECT
    EmployeeId,
    EmployeeName,
    Department
FROM Employees
WHERE Department = 'IT'
ORDER BY EmployeeName
""";

Console.WriteLine(sql);

This is particularly useful when working with SQL Server queries in .NET applications.

Raw String with Interpolation

Raw strings also support interpolation.

string name = "John";

string message = $"""
Hello {name},
Welcome to C# 11.
""";

Console.WriteLine(message);

Output

Hello John,
Welcome to C# 11.

Normal String vs Verbatim String vs Raw String

Feature

Normal

Verbatim (@)

Raw (""")

Multiline support

With escapes

Yes

Yes

Backslash escaping

Required

Not required

Not required

Double quotes

\"

""

Usually no escaping

Best for

Short text

Paths, multiline text

JSON, SQL, XML

Key Points

  • Introduced in C# 11.

  • Uses at least three double quotes.

  • Supports multiline content.

  • Preserves backslashes without escape processing.

  • Supports string interpolation.

  • More quotes can be used as delimiters when the content itself contains triple quotes.

  • Useful for JSON, XML, SQL, regular expressions, and templates.

Interview Tip: Raw String Literals improve readability by eliminating unnecessary escaping, especially when embedding JSON and SQL.

 

3. Generic Math

What is it?

Generic Math was introduced in C# 11 to perform mathematical operations on generic numeric types.

Previously, mathematical operators such as +, -, *, and / could not generally be applied to unconstrained generic type parameters.

C# 11 introduced support for static abstract interface members, enabling generic numeric interfaces such as INumber<T>.

Why is it used?

Suppose we want to create a method that adds two numbers.

Without Generic Math, we might create separate methods:

int Add(int a, int b)
{
    return a + b;
}

double Add(double a, double b)
{
    return a + b;
}

This duplicates logic.

With Generic Math, we can write one generic method for supported numeric types.

Example Program

using System.Numerics;

class Program
{
    static T Add<T>(T a, T b)
        where T : INumber<T>
    {
        return a + b;
    }

    static void Main()
    {
        Console.WriteLine(Add(10, 20));

        Console.WriteLine(Add(10.5, 20.5));

        Console.WriteLine(Add(100m, 200m));
    }
}

Output

30
31
300

Program Explanation

Step 1: Import System.Numerics

using System.Numerics;

This namespace provides generic numeric interfaces.

Step 2: Define a generic method

static T Add<T>(T a, T b)
    where T : INumber<T>

Here:

  • T represents a numeric type.

  • INumber<T> constrains T to implement the required numeric operations.

  • The + operator can be used with T.

Step 3: Call the method

Add(10, 20);        // int
Add(10.5, 20.5);    // double
Add(100m, 200m);    // decimal

The compiler infers the appropriate type for each call.

How does it work?

Generic Add<T>()
       |
       v
Check T : INumber<T>
       |
       +-- int --------+
       |               |
       +-- double -----+--> Execute a + b
       |               |
       +-- decimal ----+
       |
       v
Return result as T

Real-World Example: Calculate Total

using System.Numerics;

public static T CalculateTotal<T>(IEnumerable<T> values)
    where T : INumber<T>
{
    T total = T.Zero;

    foreach (T value in values)
    {
        total += value;
    }

    return total;
}

Usage:

int[] quantities = { 10, 20, 30 };

decimal[] amounts = { 100.50m, 200.25m };

Console.WriteLine(CalculateTotal(quantities));

Console.WriteLine(CalculateTotal(amounts));

Output

60
300.75

The same method works for integer quantities and decimal amounts.

Important Generic Math Interfaces

Interface

Purpose

INumber<T>

General numeric operations

IAdditionOperators<TSelf, TOther, TResult>

Addition

ISubtractionOperators<TSelf, TOther, TResult>

Subtraction

IMultiplyOperators<TSelf, TOther, TResult>

Multiplication

IDivisionOperators<TSelf, TOther, TResult>

Division

INumberBase<T>

Fundamental numeric capabilities

Key Points

  • Introduced in C# 11, with generic math interfaces available in .NET 7.

  • Uses static abstract members in interfaces.

  • Enables arithmetic on appropriately constrained generic types.

  • Reduces duplicate methods for different numeric types.

  • Supports numeric types such as int, double, and decimal.

  • Useful for mathematical libraries, financial calculations, statistics, and reusable algorithms.

Interview Tip: Generic Math allows strongly typed arithmetic on generic numeric types without relying on dynamic or runtime type checks.

 

4. List Patterns

What is it?

List Patterns were introduced in C# 11 to match arrays and list-like collections based on their elements and structure.

They allow pattern matching using square brackets ([]).

Why is it used?

Suppose we want to check whether an array:

  • Contains specific values.

  • Starts with a particular value.

  • Ends with a particular value.

  • Contains a particular sequence.

Before C# 11, we generally used multiple conditions.

if (numbers.Length == 3 &&
    numbers[0] == 10 &&
    numbers[1] == 20 &&
    numbers[2] == 30)
{
    Console.WriteLine("Matched");
}

With List Patterns:

if (numbers is [10, 20, 30])
{
    Console.WriteLine("Matched");
}

Example Program

class Program
{
    static void Main()
    {
        int[] numbers = { 10, 20, 30 };

        if (numbers is [10, 20, 30])
        {
            Console.WriteLine("Exact Match");
        }

        if (numbers is [10, ..])
        {
            Console.WriteLine("Starts with 10");
        }

        if (numbers is [.., 30])
        {
            Console.WriteLine("Ends with 30");
        }
    }
}

Output

Exact Match
Starts with 10
Ends with 30

How does it work?

Array: [10, 20, 30]
          |
          v
Apply List Pattern
          |
          +-- [10, 20, 30]
          |       Exact Match
          |
          +-- [10, ..]
          |       Starts with 10
          |
          +-- [.., 30]
                  Ends with 30

Different List Patterns

Pattern

Meaning

[]

Empty collection

[10]

Exactly one element equal to 10

[10, 20]

Exactly two matching elements

[10, ..]

Starts with 10

[.., 30]

Ends with 30

[10, .., 30]

Starts with 10 and ends with 30

[var first, ..]

Captures the first element

[_, 20, _]

Exactly three elements, with 20 in the middle

Example: Using Switch Expression

int[] numbers = { 10, 20, 30 };

string result = numbers switch
{
    [] => "Empty Array",

    [10] => "Only 10",

    [10, 20] => "Two Elements",

    [10, ..] => "Starts with 10",

    _ => "Other Pattern"
};

Console.WriteLine(result);

Output

Starts with 10

Key Points

  • Introduced in C# 11.

  • Uses square brackets for pattern matching.

  • Supports exact matching and partial matching.

  • .. represents zero or more elements.

  • Can be combined with if, switch, and switch expressions.

  • Works with arrays, List<T>, and compatible indexable types.

  • List patterns check structure and elements; they do not automatically search every element of a collection.

Interview Tip: List Patterns simplify conditional logic when checking collection structure, such as matching the first or last element.

 

5. UTF-8 String Literals

What is it?

UTF-8 String Literals were introduced in C# 11 to create UTF-8 encoded byte sequences directly from string literals.

They use the u8 suffix.

Normally, C# strings use UTF-16 encoding internally.

When UTF-8 bytes are required, developers commonly use Encoding.UTF8.GetBytes().

C# 11 provides a simpler alternative for constant text.

Why is it used?

UTF-8 is commonly used in:

  • Web APIs

  • JSON processing

  • Network communication

  • File processing

  • Binary protocols

Before C# 11

using System.Text;

byte[] data = Encoding.UTF8.GetBytes("Hello");

With C# 11

ReadOnlySpan<byte> data = "Hello"u8;

The compiler provides UTF-8 encoded bytes for the literal.

Example Program

class Program
{
    static void Main()
    {
        ReadOnlySpan<byte> data = "Hello"u8;

        foreach (byte value in data)
        {
            Console.WriteLine(value);
        }
    }
}

Output

72
101
108
108
111

These are the UTF-8 byte values for the characters in Hello.

How does it work?

String Literal
    "Hello"
       |
       v
UTF-8 Literal Suffix (u8)
       |
       v
UTF-8 Encoded Bytes
       |
       +-- H --> 72
       +-- e --> 101
       +-- l --> 108
       +-- l --> 108
       +-- o --> 111
       |
       v
ReadOnlySpan<byte>

Example: Compare UTF-8 Data

ReadOnlySpan<byte> expected = "SUCCESS"u8;

ReadOnlySpan<byte> actual = "SUCCESS"u8;

bool isEqual = actual.SequenceEqual(expected);

Console.WriteLine(isEqual);

Output

True

UTF-8 Literal vs Encoding.UTF8.GetBytes()

Feature

UTF-8 Literal (u8)

Encoding.UTF8.GetBytes()

Introduced

C# 11

Earlier .NET versions

Input

Compile-time string literal

Runtime string

Result

Read-only UTF-8 byte data

Usually byte[]

Allocation

Can avoid per-call byte-array allocation

Returning byte[] allocates an array

Best use

Constant UTF-8 text

Dynamic strings

Important Limitation

UTF-8 literals cannot directly interpolate runtime variables.

This is invalid:

string name = "John";

var data = $"Hello {name}"u8; // Compilation error

For dynamic strings, use:

using System.Text;

string name = "John";

byte[] data = Encoding.UTF8.GetBytes($"Hello {name}");

Key Points

  • Introduced in C# 11.

  • Uses the u8 suffix.

  • Produces UTF-8 encoded byte data.

  • Commonly assigned to ReadOnlySpan<byte>.

  • Can reduce runtime encoding overhead and allocations for constant text.

  • Does not support string interpolation.

  • Useful for high-performance APIs, networking, and binary data processing.

Interview Tip: UTF-8 String Literals are useful when working with constant UTF-8 byte sequences because they can avoid repeated runtime string-to-byte conversions.

 

C# 11.0 – Quick Revision

Feature

Main purpose

Important syntax

Required Members

Enforce initialization

required string Name

Raw String Literals

Simplify multiline and special-character strings

"""text"""

Generic Math

Arithmetic with generic numeric types

where T : INumber<T>

List Patterns

Match collection structure and elements

numbers is [10, ..]

UTF-8 String Literals

Create UTF-8 byte data from literals

"Hello"u8

Most Important Interview Questions

  1. What is the difference between required and init?

  2. What are Raw String Literals, and how do they differ from verbatim strings?

  3. How does Generic Math work using INumber<T>?

  4. What are static abstract interface members, and why are they important for Generic Math?

  5. What are List Patterns, and what is the purpose of ..?

  6. What is the difference between UTF-8 String Literals and Encoding.UTF8.GetBytes()?

For a Senior .NET Developer or Technical Lead interview, prioritize Required Members, Generic Math, and List Patterns, while understanding the practical use cases of Raw String Literals and UTF-8 String Literals.

← Back to Articles