Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two core library breaking changes #42833

Merged
merged 3 commits into from
Oct 4, 2024
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
2 changes: 2 additions & 0 deletions docs/core/compatibility/9.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ If you're migrating an app to .NET 9, the breaking changes listed here might aff
| [Altered UnsafeAccessor support for non-open generics](core-libraries/9.0/unsafeaccessor-generics.md) | Behavioral change | Preview 6 |
| [API obsoletions with custom diagnostic IDs](core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md) | Source incompatible | Preview 16 |
| [BigInteger maximum length](core-libraries/9.0/biginteger-limit.md) | Behavioral change | Preview 6 |
| [BinaryReader.GetString() returns "/uFFFD" on malformed sequences](core-libraries/9.0/binaryreader.md) | Behavioral change | Preview 7 |
| [Creating type of array of System.Void not allowed](core-libraries/9.0/type-instance.md) | Behavioral change | Preview 1 |
| [Default `Equals()` and `GetHashCode()` throw for types marked with `InlineArrayAttribute`](core-libraries/9.0/inlinearrayattribute.md) | Behavioral change | Preview 6 |
| [FromKeyedServicesAttribute no longer injects non-keyed parameter](core-libraries/9.0/non-keyed-params.md) | Behavioral change | RC 1 |
| [IncrementingPollingCounter initial callback is asynchronous](core-libraries/9.0/async-callback.md) | Behavioral change | RC 1 |
| [Inline array struct size limit is enforced](core-libraries/9.0/inlinearray-size.md) | Behavioral change | Preview 1 |
| [InMemoryDirectoryInfo prepends rootDir to files](core-libraries/9.0/inmemorydirinfo-prepends-rootdir.md) | Behavioral change | Preview 1 |
| [New TimeSpan.From*() overloads that take integers](core-libraries/9.0/timespan-from-overloads.md) | Source incompatible | Preview 3 |
| [RuntimeHelpers.GetSubArray returns different type](core-libraries/9.0/getsubarray-return.md) | Behavioral change | Preview 1 |
| [Support for empty environment variables](core-libraries/9.0/empty-env-variable.md) | Behavioral change | Preview 6 |
| [ZipArchiveEntry names and comments respect UTF8 flag](core-libraries/9.0/ziparchiveentry-encoding.md) | Behavioral change | RC 1 |
Expand Down
58 changes: 58 additions & 0 deletions docs/core/compatibility/core-libraries/9.0/binaryreader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: "Breaking change:BinaryReader.GetString() returns '\uFFFD' on malformed sequences"
description: Learn about the .NET 9 breaking change in core .NET libraries where BinaryReader.GetString() returns "\uFFFD" on malformed encoded string sequences.
ms.date: 10/03/2024
---
# BinaryReader.GetString() returns "\uFFFD" on malformed sequences

A a minor breaking change was introduced that only affects malformed encoded payloads.

Prior to .NET 9, a malformed encoded string `[0x01, 0xC2]` that was parsed with <xref:System.IO.BinaryReader.ReadString?displayProperty=nameWithType> returned an empty string.

Starting in .NET 9, <xref:System.IO.BinaryReader.ReadString?displayProperty=nameWithType> returns "\uFFFD", which is the `REPLACEMENT CHARACTER` used to replace an unknown, unrecognized, or unrepresentable character. This change only affects malformed payloads and matches Unicode standards.

## Previous behavior

```csharp
var ms = new MemoryStream(new byte[] { 0x01, 0xC2 });
using (var br = new BinaryReader(ms))
{
string s = br.ReadString();
Console.WriteLine(s == "\uFFFD"); // false
Console.WriteLine(s.Length); // 0
}
```

## New behavior

Starting in .NET 9, the same code snippet produces different results for `s == "\uFFFD"` and `s.Length`, as shown in the code comments:

```csharp
var ms = new MemoryStream(new byte[] { 0x01, 0xC2 });
using (var br = new BinaryReader(ms))
{
string s = br.ReadString();
Console.WriteLine(s == "\uFFFD"); // true
Console.WriteLine(s.Length); // 1
}
```

## Version introduced

.NET 9 Preview 7

## Type of breaking change

This change is a [behavioral change](../../categories.md#behavioral-change).

## Reason for change

This change was made as a performance improvement that affects a rare scenario.

## Recommended action

If you want to keep the previous behavior where incomplete byte sequence were omitted at the end of the string, call `TrimEnd("\uFFFD")` on the result.

## Affected APIs

- <xref:System.IO.BinaryReader.ReadString?displayProperty=fullName>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: "Breaking change: New TimeSpan.From*() overloads that take integers"
description: Learn about the .NET 9 breaking change in core .NET libraries where new TimeSpan.From*() overloads were introduced that take integer arguments.
ms.date: 10/03/2024
---
# New TimeSpan.From*() overloads that take integers

New `TimeSpan.From*()` overloads that accept integers were introduced in .NET 9. This change can cause ambiguity for the F# compiler and result in compile-time errors.

## Previous behavior

Previously, there was a single overload for each `TimeSpan.From*()` method, namely:

- <xref:System.TimeSpan.FromDays(System.Double)>
- <xref:System.TimeSpan.FromHours(System.Double)>
- <xref:System.TimeSpan.FromMicroseconds(System.Double)>
- <xref:System.TimeSpan.FromMilliseconds(System.Double)>
- <xref:System.TimeSpan.FromMinutes(System.Double)>
- <xref:System.TimeSpan.FromSeconds(System.Double)>

## New behavior

Starting in .NET 9, new overloads have been added that accept integer arguments. Calling a method such as `TimeSpan.FromMinutes(20)` in F# code results in a compile-time error:

> error FS0041: A unique overload for method 'FromMinutes' could not be determined based on type information prior to this program point. A type annotation may be needed. Known type of argument: intCandidates: - TimeSpan.FromMinutes(minutes: int64) : TimeSpan - TimeSpan.FromMinutes(minutes: int64, ?seconds: int64, ?milliseconds: int64, ?microseconds: int64) : TimeSpan - TimeSpan.FromMinutes(value: float) : TimeSpan

## Version introduced

.NET 9 Preview 3

## Type of breaking change

This change can affect [source compatibility](../../categories.md#source-compatibility) for F# code.

## Reason for change

The pre-existing overloads accepted a <xref:System.Double> argument. However, <xref:System.Double> is a binary-based, floating-point format and thus has natural imprecision that can introduce error. This behavior has led to user confusion and bugs in the API surface. It's also one of the less efficient ways to represent this data. To produce the intended behavior, new overloads were introduced that allow users to pass in integers.

## Recommended action

If this change affects your F# code, specify the type of argument so the compiler selects the appropriate overload.

## Affected APIs

- <xref:System.TimeSpan.FromDays*>
- <xref:System.TimeSpan.FromHours*>
- <xref:System.TimeSpan.FromMicroseconds*>
- <xref:System.TimeSpan.FromMilliseconds*>
- <xref:System.TimeSpan.FromMinutes*>
- <xref:System.TimeSpan.FromSeconds*>
8 changes: 8 additions & 0 deletions docs/core/compatibility/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ items:
href: core-libraries/9.0/inlinearray-size.md
- name: InMemoryDirectoryInfo prepends rootDir to files
href: core-libraries/9.0/inmemorydirinfo-prepends-rootdir.md
- name: New TimeSpan.From*() overloads that take integers
href: core-libraries/9.0/timespan-from-overloads.md
- name: RuntimeHelpers.GetSubArray returns different type
href: core-libraries/9.0/getsubarray-return.md
- name: Support for empty environment variables
Expand Down Expand Up @@ -150,6 +152,8 @@ items:
href: core-libraries/8.0/file-path-backslash.md
- name: Base64.DecodeFromUtf8 methods ignore whitespace
href: core-libraries/8.0/decodefromutf8-whitespace.md
- name: BinaryReader.GetString() returns "/uFFFD" on malformed sequences
href: core-libraries/9.0/binaryreader.md
- name: Boolean-backed enum type support removed
href: core-libraries/8.0/bool-backed-enum.md
- name: Complex.ToString format changed to `<a; b>`
Expand Down Expand Up @@ -1242,6 +1246,8 @@ items:
href: core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md
- name: BigInteger maximum length
href: core-libraries/9.0/biginteger-limit.md
- name: BinaryReader.GetString() returns "/uFFFD" on malformed sequences
href: core-libraries/9.0/binaryreader.md
- name: Creating type of array of System.Void not allowed
href: core-libraries/9.0/type-instance.md
- name: "`Equals`/`GetHashCode` throw for `InlineArrayAttribute` types"
Expand All @@ -1254,6 +1260,8 @@ items:
href: core-libraries/9.0/inlinearray-size.md
- name: InMemoryDirectoryInfo prepends rootDir to files
href: core-libraries/9.0/inmemorydirinfo-prepends-rootdir.md
- name: New TimeSpan.From*() overloads that take integers
href: core-libraries/9.0/timespan-from-overloads.md
- name: RuntimeHelpers.GetSubArray returns different type
href: core-libraries/9.0/getsubarray-return.md
- name: Support for empty environment variables
Expand Down
Loading