Let's assume the following records (class).

record class Person(string Name, int Age, Address Address);
record class Address(string Country);

Before property pattern matching we did something like this.

if (person?.Name != null && (person?.Age == 18 || (person?.Age >= 21 && person.Age <= 25))
    && person.Name.StartsWith("A")
    && person.Address.Country.Contains("land"))
{
    Console.WriteLine($"{person.Name} authorized.");
}

With property pattern matching we can do this in a more declarative way.
Combined with the var pattern we can write more readable and less code by using the newly created variables.

if (person is { Name: not null, Name: var name, Age: 18 or >= 21 and <= 25, Address.Country: var country }
    && name.StartsWith("A")
    && country.Contains("land"))
{
    Console.WriteLine($"{name} authorized.");
}

We do NOT have to check for null with property pattern matching, because the C# compiler will "lowering" your high level C# code (syntax sugar) into lower level C# code (desugar).
See how C# lowering is done with sharplab

Null checks with pattern matching

if (person is { })
{
    Console.WriteLine("Person is not null");
}

// New since C# 9.0
if (person is not null)
{
    Console.WriteLine("Person is not null");
}

This gets lowered to:

if ((object)person != null)
{
    Console.WriteLine("Person is not null");
}

Because of the cast to System.Object, overloaded != operator will not be called!

Nice to know: declarative vs imperative programming

  • With declarative programming you write code that describes what you want done.
  • With imperative programming, it is about the implementation details.

C# documentation:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#property-pattern
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#var-pattern