Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions docs/azure/includes/dotnet-all.md

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions docs/azure/includes/dotnet-new.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions docs/core/project-sdk/msbuild-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,8 @@ The `TestingPlatformCommandLineArguments` property lets you specify command-line
</PropertyGroup>
```

You can also use conditions to pass different arguments to projects that use different test frameworks or extensions. For more information, see [Solutions with mixed test frameworks or extensions](../testing/unit-testing-with-dotnet-test.md#solutions-with-mixed-test-frameworks-or-extensions).

### TestingPlatformDotnetTestSupport

The `TestingPlatformDotnetTestSupport` property enables testing MTP apps when using the VSTest mode of `dotnet test`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,9 @@ This error can occur if not all of the Fakes assemblies are present in the bin f

- Ensure that the project either uses the [MSTest.SDK](./unit-testing-mstest-sdk.md) or references [Microsoft.Testing.Extensions.Fakes](./microsoft-testing-platform-fakes.md).
- For .NET Framework projects, avoid setting `<PlatformTarget>AnyCPU</PlatformTarget>` as this results in NuGet not copying all files to the bin folder.

### Unrecognized command-line option in solutions with mixed test frameworks or extensions

If your solution contains projects that use different test frameworks (for example, MSTest and xUnit.net) or different sets of extensions (for example, only some projects reference `Microsoft.Testing.Extensions.HangDump`), running `dotnet test` with a framework-specific or extension-specific command-line option can fail with exit code 5. The option is valid for one project but unrecognized by another.

To resolve this issue, use the `TestingPlatformCommandLineArguments` MSBuild property with conditions to route arguments to the correct projects. For detailed instructions, see [Solutions with mixed test frameworks or extensions](unit-testing-with-dotnet-test.md#solutions-with-mixed-test-frameworks-or-extensions).
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ xUnit.net specific options:

For more information, see [Microsoft.Testing.Platform documentation for xUnit.net](https://xunit.net/docs/getting-started/v3/microsoft-testing-platform) and [Query Filter Language for xUnit.net](https://xunit.net/docs/query-filter-language).

> [!TIP]
> If your solution mixes test frameworks that use different filter syntaxes (for example, MSTest and xUnit.net), you can conditionally route framework-specific arguments using the `TestingPlatformCommandLineArguments` MSBuild property. For details, see [Solutions with mixed test frameworks or extensions](unit-testing-with-dotnet-test.md#solutions-with-mixed-test-frameworks-or-extensions).

#### `--logger`

What was usually referred to as "logger" in VSTest is referred to as "reporter" in MTP. In MTP, logging is explicitly for diagnosing purposes only.
Expand Down
42 changes: 42 additions & 0 deletions docs/core/testing/unit-testing-with-dotnet-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,45 @@ For users of MTP that are using the VSTest mode of `dotnet test`, there are few
1. If passing a specific solution (or directory containing solution), for example, `dotnet test MySolution.sln`, this should become `dotnet test --solution MySolution.sln`.
1. If passing a specific project (or directory containing project), for example, `dotnet test MyProject.csproj`, this should become `dotnet test --project MyProject.csproj`.
1. If passing a specific dll, for example, `dotnet test path/to/UnitTests.dll`, this should become `dotnet test --test-modules path/to/UnitTests.dll`. Note that `--test-modules` also supports globbing.

## Solutions with mixed test frameworks or extensions

When a solution contains test projects that use different test frameworks (for example, MSTest and xUnit.net) or different sets of extensions, running `dotnet test` with framework-specific or extension-specific command-line options can fail. Options that are valid for one project are unrecognized by another, causing exit code 5 (invalid command-line arguments). For example:

- xUnit.net uses `--filter-trait` while MSTest uses `--filter`, and each framework rejects the other's options.
- A project that references `Microsoft.Testing.Extensions.HangDump` accepts `--hangdump`, but a project without that extension fails on the same option.

Use the [`TestingPlatformCommandLineArguments`](../project-sdk/msbuild-props.md#testingplatformcommandlinearguments) MSBuild property with conditions to pass arguments only to the projects that understand them.

### Use MSBuild conditions to route arguments

Define custom MSBuild properties for the framework-specific arguments, and conditionally append them to `TestingPlatformCommandLineArguments` in a `Directory.Build.props` or `Directory.Build.targets` file. Use a condition that checks for a property set by the test framework's SDK or NuGet package to determine which framework each project uses.

The following example shows a `Directory.Build.props` file for a solution that mixes MSTest (with MSTest.Sdk) and xUnit.net projects:

```xml
<PropertyGroup>
<TestingPlatformCommandLineArguments
Condition="'$(UsingMSTestSdk)' == 'true'"
>$(TestingPlatformCommandLineArguments) $(MSTestSpecificArgs)</TestingPlatformCommandLineArguments>
<TestingPlatformCommandLineArguments
Condition="'$(UsingMSTestSdk)' != 'true'"
>$(TestingPlatformCommandLineArguments) $(XUnitSpecificArgs)</TestingPlatformCommandLineArguments>
</PropertyGroup>
```

> [!NOTE]
> `UsingMSTestSdk` is a property defined by `MSTest.Sdk`. If your MSTest projects don't use `MSTest.Sdk`, use a different condition. Check whether your test framework's SDK or NuGet package already sets a property you can use. If it doesn't, define your own property in each project and condition on that property instead (in this case, use Directory.Build.targets instead).

With this configuration in place, you can pass framework-specific arguments from the command line using MSBuild properties:

```dotnetcli
dotnet test -p:MSTestSpecificArgs="--filter FullyQualifiedName~IntegrationTests" -p:XUnitSpecificArgs="--filter-trait Category=Integration"
```

Each test project receives only the arguments relevant to its framework, and the other framework's arguments are never passed.

> [!TIP]
> For arguments that are the same across all frameworks (such as `--ignore-exit-code 8` or `--report-trx`), set them directly in `TestingPlatformCommandLineArguments` without any condition.

The same pattern applies when only some test projects in a solution reference a particular extension. For example, if only certain projects reference `Microsoft.Testing.Extensions.HangDump`, passing `--hangdump` globally causes the other projects to fail with an unrecognized option error. Use the same conditional approach to route extension-specific arguments only to the projects that have the extension.
2 changes: 1 addition & 1 deletion docs/core/tools/dotnet-test-mtp.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ With MTP, `dotnet test` operates faster than with VSTest. The test-related argum
Specifies extra arguments to pass to the test application(s). Use a space to separate multiple arguments. For more information and examples on what to pass, see [MTP overview](../testing/microsoft-testing-platform-intro.md) and [MTP features](../testing/microsoft-testing-platform-features.md).

> [!TIP]
> To specify extra arguments for specific projects, use the `TestingPlatformCommandLineArguments` MSBuild property.
> To specify extra arguments for specific projects, use the `TestingPlatformCommandLineArguments` MSBuild property. This property is especially useful when your solution mixes test frameworks (for example, MSTest and xUnit.net) or when only some projects reference a particular extension. For more information, see [Solutions with mixed test frameworks or extensions](../testing/unit-testing-with-dotnet-test.md#solutions-with-mixed-test-frameworks-or-extensions).

> [!NOTE]
> To enable trace logging to a file, use the environment variable `DOTNET_CLI_TEST_TRACEFILE` to provide the path to the trace file.
Expand Down
72 changes: 72 additions & 0 deletions docs/csharp/fundamentals/null-safety/index.md
Original file line number Diff line number Diff line change
@@ -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 <xref:System.NullReferenceException>:

:::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 such as `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 such as `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.
106 changes: 106 additions & 0 deletions docs/csharp/fundamentals/null-safety/null-operators.md
Original file line number Diff line number Diff line change
@@ -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 <xref:System.NullReferenceException>:

:::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)
Loading
Loading