diff --git a/docs/csharp/fundamentals/null-safety/index.md b/docs/csharp/fundamentals/null-safety/index.md new file mode 100644 index 0000000000000..ab97771883166 --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/index.md @@ -0,0 +1,72 @@ +--- +title: "Null safety in C#" +description: Learn how C# helps you write null-safe code using nullable value types, nullable reference types, and null operators. +ms.date: 04/30/2026 +ms.topic: overview +ai-usage: ai-assisted +--- + +# C# null safety + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. +> +> **Coming from Java or C++?** C# provides compile-time null safety through nullable reference types. The goal is similar to Java's `@NonNull` annotations but enforced by the compiler. C# also has dedicated operators like `?.` and `??` that make null-safe expressions concise. + +`null` represents the absence of a value. When you try to access a member on a `null` reference, by calling a method or reading a property, the runtime throws a : + +:::code language="csharp" source="snippets/null-safety-overview/Program.cs" ID="NullReferenceDemo"::: + +C# gives you three complementary tools to write null-safe code: + +- **Nullable value types**: let a value type like `int` or `bool` also hold `null` +- **Nullable reference types**: let the compiler track whether a reference might be `null` +- **Null operators**: express null-safe access and fallback logic concisely + +## Nullable value types + +Value types like `int`, `double`, and `bool` can't hold `null` by default. Add `?` to the type name to create a *nullable value type* that holds either a value or `null`: + +:::code language="csharp" source="snippets/null-safety-overview/Program.cs" ID="NvtIntro"::: + +Nullable value types are useful when an underlying value type needs to represent "no data." Common scenarios include database columns that might be absent, optional configuration settings, and sensor readings that aren't captured yet. + +For full coverage of declaration, checking, and conversion, see [Nullable value types](nullable-value-types.md). + +## Nullable reference types + +Reference types, such as `string`, arrays, and class instances, can hold `null` at runtime. *Nullable reference types* is a compiler feature that makes null intent explicit and catches mistakes at compile time. + +By using the `?` annotation, you declare your intent: + +- `string?` — this reference *might* be `null`; the compiler warns if you dereference it without checking first +- `string` — this reference *should not* be `null`; the compiler warns if you assign `null` to it + +:::code language="csharp" source="snippets/null-safety-overview/Program.cs" ID="NrtIntro"::: + +All .NET projects that modern SDK templates create enable nullable reference types by default. For complete guidance on enabling and annotating, see [Nullable reference types](../../nullable-references.md). + +## Null operators + +C# includes several operators that let you write null-safe code without manual `if`-null guards everywhere: + +| Operator | Name | Purpose | +|---------------------------|---------------------------------|--------------------------------------------------------| +| `?.` | Null-conditional member access | Access a member only when the object is non-null | +| `?[]` | Null-conditional indexer access | Access an element only when the collection is non-null | +| `??` | Null-coalescing | Return a fallback value when the expression is `null` | +| `??=` | Null-coalescing assignment | Assign only when the variable is `null` | +| `is null` / `is not null` | Null pattern | Preferred null test | + +:::code language="csharp" source="snippets/null-safety-overview/Program.cs" ID="OperatorsQuickRef"::: + +For detailed examples of each operator, see [Null operators](null-operators.md). + +## Nullable value types and nullable reference types serve different purposes + +Nullable value types and nullable reference types aren't alternatives. They solve different problems: + +- Use `T?` for a value type that needs to represent "no value." For example, use `int?` for an optional database column or `DateTime?` for an event that isn't scheduled yet. +- Use `string?` and other nullable reference annotations to document that a reference *might* be `null`, so the compiler can warn you before a `NullReferenceException` occurs at runtime. + +Together, these features and the null operators give you a complete set of tools to write null-safe C# code. diff --git a/docs/csharp/fundamentals/null-safety/null-operators.md b/docs/csharp/fundamentals/null-safety/null-operators.md new file mode 100644 index 0000000000000..e68c46bf72ea5 --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/null-operators.md @@ -0,0 +1,106 @@ +--- +title: "Null operators in C#" +description: Learn how to use the null-conditional (?. and ?[]), null-coalescing (??), null-coalescing assignment (??=), and null pattern (is null) operators to write null-safe C# code. +ms.date: 04/30/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# C# null operators + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For the complete operator reference, see [Member access operators](../../language-reference/operators/member-access-operators.md) and [null-coalescing operators](../../language-reference/operators/null-coalescing-operator.md) in the language reference. + +C# provides several operators that make null-safe code concise. Instead of nesting `if (x != null)` guards throughout your code, these operators let you express null-safe access, fallback values, and null tests in a single expression. + +This article covers `?.` and `?[]` for null-conditional access, `??` for null-coalescing, `??=` for null-coalescing assignment, and `is null`/`is not null` for null pattern matching. + +## Null-conditional member access `?.` + +The `?.` operator accesses a member only when the object is non-null. When the object is `null`, the entire expression evaluates to `null` instead of throwing a : + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullConditionalMember"::: + +The `?.` operator *short-circuits*: when the left-hand side is `null`, everything to the right is skipped. No method calls run and no side effects occur. + +You can chain multiple `?.` operators in a single expression. The chain stops at the first `null` it encounters: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullConditionalMemberChain"::: + +## Null-conditional indexer access `?[]` + +The `?[]` operator applies the same short-circuit behavior to indexer and array access. Use it when the collection itself might be `null`: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullConditionalIndexer"::: + +## Chain null-conditional operators + +Chain multiple `?.` operators to traverse a path of potentially null references. The chain short-circuits at the first `null`: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullConditionalChain"::: + +When `Customer` is `null`, neither `Address` nor `City` is evaluated. The whole expression returns `null`. + +## Thread-safe delegate invocation + +`?.` provides a clean, thread-safe way to invoke a delegate or raise an event. The delegate expression is evaluated only once, so there's no window for another thread to unsubscribe between the null check and the invocation: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullConditionalDelegate"::: + +This pattern replaces the older `if (clicked != null) clicked(...)` idiom. + +## Null-coalescing `??` + +The `??` operator returns its left-hand operand when it's non-null, and its right-hand operand when the left is `null`. Use it to provide a default value: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullCoalescing"::: + +`??` is right-associative, so `a ?? b ?? c` evaluates as `a ?? (b ?? c)`. The first non-null value wins. A common pattern is to chain `?.` with `??`: use `?.` to safely traverse a null-possible chain, then `??` to substitute a default if the chain returned `null`. For a complete example, see [Combine null operators](#combine-null-operators). + +## Null-coalescing assignment `??=` + +The `??=` operator assigns the right-hand value to a variable only when the variable is `null`. Use it for lazy initialization: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullCoalescingAssignment"::: + +The right-hand expression is evaluated only when the variable is `null`. When the variable already has a value, the right side isn't evaluated at all. + +## Null-conditional assignment (C# 14) + +Beginning in C# 14, you can use `?.` and `?[]` as assignment targets. The assignment runs only when the left-hand object is non-null: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullConditionalAssignment"::: + +The right-hand side is evaluated only when the left-hand side is known to be non-null. + +## Null pattern matching: `is null` and `is not null` + +The `is null` and `is not null` patterns test whether an expression is `null`: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="IsNull"::: + +Prefer `is null` over `== null` for null checks. The `==` operator can be overloaded, meaning `x == null` might return `true` even when `x` isn't `null` if the type defines a custom equality operator. The `is null` pattern always tests for the actual null reference, regardless of operator overloading. + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="IsNotNull"::: + +## Combine null operators + +In practice, you often combine several of these operators. One expression can safely traverse a deep object graph, apply a fallback, and then guard on the result: + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="CombinedPattern"::: + +## Null-forgiving operator `!` + +The `!` postfix operator suppresses nullable warnings. Append `!` to tell the compiler "this expression is definitely not null." The operator has no effect at runtime. It only affects the compiler's null-state analysis. + +:::code language="csharp" source="snippets/null-operators/Program.cs" ID="NullForgiving"::: + +Use `!` sparingly, and only when you have information the compiler doesn't. Examples include tests that intentionally pass `null` to validate argument-checking logic, or calling a method whose contract guarantees a non-null return for a known input. Overusing `!` defeats the purpose of nullable reference types. For a full explanation, see [Nullable reference types](../../nullable-references.md). + +## See also + +- [Null safety overview](index.md) +- [Nullable value types](nullable-value-types.md) +- [Nullable reference types](../../nullable-references.md) +- [Member access operators (language reference)](../../language-reference/operators/member-access-operators.md) +- [Null-coalescing operators (language reference)](../../language-reference/operators/null-coalescing-operator.md) diff --git a/docs/csharp/fundamentals/null-safety/nullable-value-types.md b/docs/csharp/fundamentals/null-safety/nullable-value-types.md new file mode 100644 index 0000000000000..61cb688319936 --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/nullable-value-types.md @@ -0,0 +1,69 @@ +--- +title: "Nullable value types: C# Fundamentals" +description: Learn how to use nullable value types (T?) in C# to represent value types that can be absent or undefined. +ms.date: 04/30/2026 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Nullable value types: C# Fundamentals + +> [!TIP] +> This article is part of the **Fundamentals** section for developers who know at least one programming language and are learning C#. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first. For more details, see [Nullable value types](../../language-reference/builtin-types/nullable-value-types.md) in the language reference. + +A *nullable value type* `T?` represents all values of its underlying value type `T`, plus an additional `null` value. A variable of type `int?` holds any integer or `null` to represent "no value." + +Value types like `int`, `bool`, and `DateTime` can't hold `null` by default. This behavior is efficient and prevents many errors. However, this limitation creates a problem when a value might genuinely be absent. A common scenario is reading from a database: an integer column might contain a number, or it might contain no value at all (`NULL` in SQL). A plain `int` can't represent that absence, but `int?` can. + +## Declare a nullable value type + +Append `?` to any value type to make it nullable: + +:::code language="csharp" source="snippets/nullable-value-types/Program.cs" ID="Declaration"::: + +The default value of a nullable value type is `null`, not the underlying type's default. + +## Check whether a value is present + +The recommended way to check a nullable value type and extract its value is with a *type pattern*: + +:::code language="csharp" source="snippets/nullable-value-types/Program.cs" ID="PatternMatching"::: + +The `is int degrees` pattern matches only when `temperature` is non-null, and it simultaneously binds the value to `degrees`. You get both the null check and the value extraction in one step. + +Alternatively, use the `HasValue` and `Value` properties: + +:::code language="csharp" source="snippets/nullable-value-types/Program.cs" ID="HasValue"::: + +Prefer the `is T value` pattern for new code. It introduces a new non-nullable variable scoped to the matched branch, which makes the intent clearer and eliminates any temptation to accidentally use `Value` outside a null check, where it would throw an . + +You can also compare directly with `null`: + +:::code language="csharp" source="snippets/nullable-value-types/Program.cs" ID="NullCheck"::: + +## Get a value with a fallback + +When you need a non-nullable value from a nullable, use `GetValueOrDefault` or the null-coalescing `??` operator: + +:::code language="csharp" source="snippets/nullable-value-types/Program.cs" ID="GetValueOrDefault"::: + +The `??` operator is often cleaner inline: + +:::code language="csharp" source="snippets/nullable-value-types/Program.cs" ID="NullCoalescing"::: + +Both approaches return the actual value when one is present, and the fallback you specify when it isn't. + +## Arithmetic with nullable value types + +Arithmetic and comparison operators on nullable value types are *lifted*: when either operand is `null`, the result is `null` rather than an error. + +:::code language="csharp" source="snippets/nullable-value-types/Program.cs" ID="LiftedOperators"::: + +Null propagates through arithmetic by default. To prevent a null result from cascading further, extract the value with `??` or `GetValueOrDefault` before you use it in a calculation. + +## See also + +- [Null safety overview](index.md) +- [Null operators](null-operators.md) +- [Nullable value types (language reference)](../../language-reference/builtin-types/nullable-value-types.md) +- [Nullable reference types](../../nullable-references.md) diff --git a/docs/csharp/fundamentals/null-safety/snippets/null-operators/Program.cs b/docs/csharp/fundamentals/null-safety/snippets/null-operators/Program.cs new file mode 100644 index 0000000000000..bd8d08e455758 --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/snippets/null-operators/Program.cs @@ -0,0 +1,222 @@ +namespace NullOperatorsSample; + +public record Address(string Street, string City, string State); +public record Customer(string Name, Address? Address); +public record Order(string Id, Customer? Customer); + +public sealed class AppConfig +{ + public string? Theme { get; set; } + public string[]? Tags { get; set; } +} + +public static class Program +{ + public static void Main() + { + ShowNullConditionalMember(); + ShowNullConditionalMemberChain(); + ShowNullConditionalIndexer(); + ShowNullConditionalChain(); + ShowNullConditionalDelegate(); + ShowNullCoalescing(); + ShowNullCoalescingAssignment(); + ShowNullConditionalAssignment(); + ShowIsNull(); + ShowIsNotNull(); + ShowCombinedPattern(); + ShowNullForgiving(); + } + + private static void ShowNullConditionalMember() + { + // + string? name = null; + + // Without ?., accessing a member on null throws NullReferenceException: + // int len = name.Length; // throws if name is null + + // ?. returns null instead of throwing: + int? len = name?.Length; + Console.WriteLine(len.HasValue); // False + + name = "C#"; + Console.WriteLine(name?.Length); // 2 + // + } + + private static void ShowNullConditionalMemberChain() + { + // + string? input = null; + + // Chain ?. across multiple method calls — short-circuits at the first null: + string? upper = input?.Trim()?.ToUpperInvariant(); + Console.WriteLine(upper ?? "(none)"); // (none) + + input = " hello "; + Console.WriteLine(input?.Trim()?.ToUpperInvariant()); // HELLO + // + } + + private static void ShowNullConditionalIndexer() + { + // + string[]? tags = null; + + // ?[] accesses an element only when the collection is non-null + string? first = tags?[0]; + Console.WriteLine(first ?? "(none)"); // (none) + + tags = ["csharp", "dotnet", "nullable"]; + Console.WriteLine(tags?[0]); // csharp + // + } + + private static void ShowNullConditionalChain() + { + // + var order = new Order("ORD-001", null); + + // Each ?. short-circuits when null: Customer is null, so Address and City are never accessed + string? city = order.Customer?.Address?.City; + Console.WriteLine(city ?? "(no city)"); // (no city) + + var fullOrder = new Order("ORD-002", + new Customer("Alice", new Address("123 Main St", "Springfield", "IL"))); + + Console.WriteLine(fullOrder.Customer?.Address?.City); // Springfield + // + } + + private static void ShowNullConditionalDelegate() + { + // + EventHandler? clicked = null; + + // No subscribers — ?.Invoke does nothing instead of throwing NullReferenceException + clicked?.Invoke(null, EventArgs.Empty); + + clicked += (_, _) => Console.WriteLine("Button clicked!"); + + // With a subscriber — ?.Invoke calls the handler + clicked?.Invoke(null, EventArgs.Empty); + // Output: Button clicked! + // + } + + private static void ShowNullCoalescing() + { + // + string? username = null; + + // ?? returns the right-hand value when the left-hand is null + string display = username ?? "Guest"; + Console.WriteLine(display); // Guest + + username = "alice"; + display = username ?? "Guest"; + Console.WriteLine(display); // alice + // + } + + private static void ShowNullCoalescingAssignment() + { + // + List? cache = null; + + // ??= assigns only when the variable is null + cache ??= LoadData(); + Console.WriteLine(cache.Count); // 3 + + // cache is already non-null, so LoadData() isn't called again + cache ??= LoadData(); + Console.WriteLine(cache.Count); // 3 + + static List LoadData() => ["alpha", "beta", "gamma"]; + // + } + + private static void ShowNullConditionalAssignment() + { + // + AppConfig? config = new AppConfig(); + + // Assigns only when config is non-null (C# 14) + config?.Theme = "dark"; + Console.WriteLine(config?.Theme); // dark + + AppConfig? missing = null; + missing?.Theme = "light"; // no-op: missing is null + Console.WriteLine(missing?.Theme ?? "(no config)"); // (no config) + // + } + + private static void ShowIsNull() + { + // + string? input = null; + + // is null is the preferred test — unaffected by operator overloading + if (input is null) + { + Console.WriteLine("No input provided."); + } + + // == null also works, but a custom == operator can change its behavior + if (input == null) + { + Console.WriteLine("Still no input."); + } + // + } + + private static void ShowIsNotNull() + { + // + string? value = "hello"; + + if (value is not null) + { + Console.WriteLine(value.ToUpper()); // HELLO + } + // + } + + private static void ShowCombinedPattern() + { + // + Order? order = GetPendingOrder(); + + // Chain ?. for safe traversal, ?? for a fallback, is null for a clear guard + string city = order?.Customer?.Address?.City ?? "unknown"; + + if (order is null) + { + Console.WriteLine("No pending order."); + } + else + { + Console.WriteLine($"Shipping to: {city}"); + } + // Output: No pending order. + // + } + + private static void ShowNullForgiving() + { + // + string? name = FindUser("alice"); + + // Use ! only when you have information the compiler doesn't. + // FindUser guarantees a non-null result for known usernames. + int length = name!.Length; + Console.WriteLine(length); // 5 + // + } + + private static Order? GetPendingOrder() => null; + + private static string? FindUser(string username) => + username == "alice" ? "alice" : null; +} diff --git a/docs/csharp/fundamentals/null-safety/snippets/null-operators/null-operators.csproj b/docs/csharp/fundamentals/null-safety/snippets/null-operators/null-operators.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/snippets/null-operators/null-operators.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/fundamentals/null-safety/snippets/null-safety-overview/Program.cs b/docs/csharp/fundamentals/null-safety/snippets/null-safety-overview/Program.cs new file mode 100644 index 0000000000000..b209ee05897da --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/snippets/null-safety-overview/Program.cs @@ -0,0 +1,91 @@ +namespace NullSafetyOverview; + +public static class Program +{ + public static void Main() + { + NullReferenceDemo(); + NvtIntro(); + NrtIntro(); + OperatorsQuickRef(); + } + + private static void NullReferenceDemo() + { + // + // Accessing a member on null throws NullReferenceException at runtime: + // string? name = null; + // int length = name.Length; // throws NullReferenceException + + // Check before you dereference: + string? name = null; + if (name is not null) + { + Console.WriteLine($"Name has {name.Length} characters."); + } + else + { + Console.WriteLine("Name has no value."); + } + // Output: Name has no value. + // + } + + private static void NvtIntro() + { + // + int? score = null; + Console.WriteLine(score.HasValue); // False + + score = 95; + Console.WriteLine(score.HasValue); // True + Console.WriteLine(score.GetValueOrDefault()); // 95 + + int? missing = null; + Console.WriteLine(missing.GetValueOrDefault(-1)); // -1 + // + } + + private static void NrtIntro() + { + // + // string? means this reference might be null + // string means this reference should not be null + string? nullableName = null; + string nonNullName = "Alice"; + + // ?. safely accesses a member when the reference might be null + string display = nullableName?.ToUpper() ?? "(no name)"; + Console.WriteLine(display); // (no name) + + display = nonNullName.ToUpper(); // safe: nonNullName is never null + Console.WriteLine(display); // ALICE + // + } + + private static void OperatorsQuickRef() + { + // + string? city = GetCity(); + + // ?. — access a member only when non-null + int? len = city?.Length; + + // ?? — substitute a default when null + string display = city ?? "unknown"; + + // is null — preferred null test + if (city is null) + { + Console.WriteLine("No city provided."); + } + else + { + Console.WriteLine($"{display} ({len} chars)"); + } + // Output: No city provided. + // + } + + private static string? GetCity() => null; +} diff --git a/docs/csharp/fundamentals/null-safety/snippets/null-safety-overview/null-safety-overview.csproj b/docs/csharp/fundamentals/null-safety/snippets/null-safety-overview/null-safety-overview.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/snippets/null-safety-overview/null-safety-overview.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/fundamentals/null-safety/snippets/nullable-value-types/Program.cs b/docs/csharp/fundamentals/null-safety/snippets/nullable-value-types/Program.cs new file mode 100644 index 0000000000000..8f9bcdb0869e4 --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/snippets/nullable-value-types/Program.cs @@ -0,0 +1,131 @@ +namespace NullableValueTypesSample; + +public static class Program +{ + public static void Main() + { + ShowDeclaration(); + ShowPatternMatching(); + ShowHasValue(); + ShowNullCheck(); + ShowGetValueOrDefault(); + ShowNullCoalescing(); + ShowLiftedOperators(); + } + + private static void ShowDeclaration() + { + // + int? age = null; // integer with no value yet + double? price = 9.99; // nullable double with a value + bool? isActive = null; // boolean with no value + + age = 30; // assign a value later + + int?[] scores = [100, null, 85, null, 72]; // array with absent entries + // + + // Suppress unused-variable warnings — these variables are shown for documentation. + _ = age; + _ = price; + _ = isActive; + _ = scores; + } + + private static void ShowPatternMatching() + { + // + int? temperature = 72; + + if (temperature is int degrees) + { + Console.WriteLine($"Temperature is {degrees}°F."); + } + else + { + Console.WriteLine("Temperature is not recorded."); + } + // Output: Temperature is 72°F. + // + } + + private static void ShowHasValue() + { + // + int? count = 42; + + if (count.HasValue) + { + Console.WriteLine($"Count is {count.Value}."); + } + else + { + Console.WriteLine("Count has no value."); + } + // Output: Count is 42. + // + } + + private static void ShowNullCheck() + { + // + int? quantity = null; + + if (quantity != null) + { + Console.WriteLine($"Quantity: {quantity.Value}"); + } + else + { + Console.WriteLine("Quantity is not set."); + } + // Output: Quantity is not set. + // + } + + private static void ShowGetValueOrDefault() + { + // + int? rating = null; + + int result1 = rating.GetValueOrDefault(); // 0 (default for int) + int result2 = rating.GetValueOrDefault(-1); // -1 (specified fallback) + + Console.WriteLine(result1); // 0 + Console.WriteLine(result2); // -1 + + rating = 5; + int result3 = rating.GetValueOrDefault(-1); // 5 (actual value) + Console.WriteLine(result3); // 5 + // + } + + private static void ShowNullCoalescing() + { + // + int? priority = null; + + int effective = priority ?? 0; // 0 because priority is null + Console.WriteLine(effective); // 0 + + priority = 3; + effective = priority ?? 0; // 3 because priority has a value + Console.WriteLine(effective); // 3 + // + } + + private static void ShowLiftedOperators() + { + // + int? a = 10; + int? b = 20; + int? c = null; + + int? sum = a + b; // both non-null: result is 30 + int? product = a * c; // one operand is null: result is null + + Console.WriteLine(sum); // 30 + Console.WriteLine(product.HasValue); // False — null propagates through arithmetic + // + } +} diff --git a/docs/csharp/fundamentals/null-safety/snippets/nullable-value-types/nullable-value-types.csproj b/docs/csharp/fundamentals/null-safety/snippets/nullable-value-types/nullable-value-types.csproj new file mode 100644 index 0000000000000..bad583f080c8c --- /dev/null +++ b/docs/csharp/fundamentals/null-safety/snippets/nullable-value-types/nullable-value-types.csproj @@ -0,0 +1,8 @@ + + + Exe + net10.0 + enable + enable + + diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 7429c663cd5f2..3ad4544c39147 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -74,6 +74,14 @@ items: - name: Anonymous types href: programming-guide/classes-and-structs/anonymous-types.md # TODO: Delegates, lambdas and events + - name: Null safety + items: + - name: Overview + href: fundamentals/null-safety/index.md + - name: Nullable value types + href: fundamentals/null-safety/nullable-value-types.md + - name: Null operators + href: fundamentals/null-safety/null-operators.md - name: Object-oriented programming items: - name: Classes, structs, and records