Modern C# · Lesson 3

Property patterns

Choose a result from several fields of a value.

Describe the customer state you accept

A property pattern describes a state requirement directly in the shape of a value. This sample classifies a nullable customer using activity and visit count. The strongest rule requires an active customer with at least ten visits; a weaker rule accepts any active customer; the fallback covers everything else. Thinking in terms of accepted states makes the code easier to explain than treating each brace as unfamiliar syntax. Before running, identify which states overlap and which rule should win when several conditions could describe one customer.

Why twelve visits produces reward

The customer is nonnull, active, and has twelve visits. It matches the first arm because Active is true and twelve satisfies Visits: >= 10. The switch expression returns reward, which is the only printed line. Although the same customer also satisfies the weaker active-only condition, the switch chooses the first matching arm. There is no second classification pass and no later overwrite of the selected label. The source order therefore communicates precedence among rules whose accepted states overlap.

{ Active: true, Visits: >= 10 } => "reward",

Null is part of the input space

A property pattern requires a nonnull input before its property requirements can match. A null customer therefore does not behave like an inactive customer whose visits are zero; it simply fails those property patterns and reaches the fallback arm. Both may receive the same guest label here because the policy intentionally groups them. Keeping that distinction in your reasoning matters if a later product rule needs different messages for missing customer data and a known customer who is inactive.

Do not hide a specific rule behind a broad one

Placing the active-only arm before the active-and-frequent arm would make the more specific state unreachable in this switch. The compiler can diagnose that ordering mistake rather than silently doing the desired classification. Broader patterns belong after narrower overlapping patterns when specificity controls priority. A different pitfall is putting complicated side effects in property accessors and then using those properties in patterns. Keep classification based on straightforward observed state; a readable pattern should not conceal unpredictable work merely because it looks declarative.

Practice: test the threshold and nullable case

Build a four-row input table using active customers with nine, ten, and twelve visits, plus a null customer. Predict welcome, reward, reward, and guest respectively before executing the variations. Then add an inactive customer with a high visit count and explain why the activity requirement still prevents a reward. This exercise separates the numeric threshold from the Boolean requirement and proves that the boundary includes ten rather than only values greater than ten.

  • Change one input property at a time so each result demonstrates one part of the rule.
  • Keep null as its own test row even though it currently shares the guest label with inactive customers.

Practice: add a distinct rule deliberately

For a separate design change, introduce a rule that active customers with zero visits receive a first-visit label. Decide where that rule belongs relative to the frequent and active-only rules, and test it alongside the existing threshold cases. Next decide how negative visit counts should be handled: as a classification case or as invalid model data rejected earlier. Do not let a convenient fallback silently define the business meaning of an impossible count without documenting that decision.

  • Use Check after changing arm order to inspect reachability diagnostics before attempting a run.
  • Write an explicit expected-label table covering zero, nine, ten, inactive, null, and any rejected negative case.

Keep classification and validation distinguishable

The pattern is valuable because a reader can see the accepted state without tracing several nested conditionals. That clarity depends on precise precedence and a complete input model. A fallback means every input obtains a label; it does not prove every input is valid domain data. When a classification changes, review both the overlap between arms and the behavior of previously grouped cases. The result should remain a small, deterministic policy that can be tested locally without network services or device-specific assumptions.

  • Baseline output is exactly reward.
  • The relational boundary is inclusive: ten visits meets the frequent-customer rule.
  • The fallback covers null and all other unmatched states; it is not a validation guarantee.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

Customer? customer = new(true, 12);
var label = customer switch
{
    { Active: true, Visits: >= 10 } => "reward",
    { Active: true } => "welcome",
    _ => "guest"
};
Console.WriteLine(label);
record Customer(bool Active, int Visits);

Expected output

reward
Try this
Put the most specific overlapping rule first.

Further reading