Skip to content

[Breaking change]: Debug.Assert/WriteIf/WriteLineIf conditional evaluation #26429

@stephentoub

Description

@stephentoub

Description

It is very common to use interpolated strings with asserts, e.g.

Debug.Assert(result != x, $"Unexpected result {result}");

However, this causes a string to be created for the message, including formatting result, on every call, even though typical use for asserts is that they're about something that should never happen.

C# 10 adds support for better string interpolation, including the ability to target custom "handlers" in addition to strings (see https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/ for more details). In .NET 6, Debug has new overloads of Assert and WriteLine that utilize this functionality to conditionally evaluate the interpolated string formatting items only if the message is required. The C# compiler will prefer these new overloads, and thus if the formatting items were mutating state and the program was relying on those mutations to be visible even if the assert didn't fire, a difference in behavior could be observed.

Version

.NET 6 RC 1

Previous behavior

With:

Debug.Assert(true, $"{r.ToString()}");

r.ToString() would always be invoked.

New behavior

With:

Debug.Assert(true, $"{r.ToString()}");

r.ToString() will never be invoked, as the message is only needed if the condition is false.

Type of breaking change

  • Binary incompatible: Existing binaries may encounter a breaking change in behavior, such as failure to load/execute or different run-time behavior.
  • Source incompatible: Source code may encounter a breaking change in behavior when targeting the new runtime/component/SDK, such as compile errors or different run-time behavior.

Reason for change

Performance.

Recommended action

Interpolated strings used with Debug should not mutate shared state (these methods are all conditional on the DEBUG compilation constant as well). If for some reason it's critical that the old behavior be maintained, putting a (string) cast before the interpolated string will force the compiler to bind to the existing overload and will ensure the string is always materialized.

Feature area

Core .NET libraries

Affected APIs

Debug.Assert
Debug.WriteIf
Debug.WriteLineIf

Metadata

Metadata

Assignees

Labels

🏁 Release: .NET 6Issues and PRs for the .NET 6 releasebreaking-changeIndicates a .NET Core breaking changesource incompatibleSource code may encounter a breaking change in behavior when targeting the new version.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions