Skip to content

Commit 6471416

Browse files
authored
Two core library breaking changes (#42833)
1 parent 1d717f8 commit 6471416

File tree

4 files changed

+118
-0
lines changed

4 files changed

+118
-0
lines changed

docs/core/compatibility/9.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ If you're migrating an app to .NET 9, the breaking changes listed here might aff
3636
| [Altered UnsafeAccessor support for non-open generics](core-libraries/9.0/unsafeaccessor-generics.md) | Behavioral change | Preview 6 |
3737
| [API obsoletions with custom diagnostic IDs](core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md) | Source incompatible | Preview 16 |
3838
| [BigInteger maximum length](core-libraries/9.0/biginteger-limit.md) | Behavioral change | Preview 6 |
39+
| [BinaryReader.GetString() returns "/uFFFD" on malformed sequences](core-libraries/9.0/binaryreader.md) | Behavioral change | Preview 7 |
3940
| [Creating type of array of System.Void not allowed](core-libraries/9.0/type-instance.md) | Behavioral change | Preview 1 |
4041
| [Default `Equals()` and `GetHashCode()` throw for types marked with `InlineArrayAttribute`](core-libraries/9.0/inlinearrayattribute.md) | Behavioral change | Preview 6 |
4142
| [FromKeyedServicesAttribute no longer injects non-keyed parameter](core-libraries/9.0/non-keyed-params.md) | Behavioral change | RC 1 |
4243
| [IncrementingPollingCounter initial callback is asynchronous](core-libraries/9.0/async-callback.md) | Behavioral change | RC 1 |
4344
| [Inline array struct size limit is enforced](core-libraries/9.0/inlinearray-size.md) | Behavioral change | Preview 1 |
4445
| [InMemoryDirectoryInfo prepends rootDir to files](core-libraries/9.0/inmemorydirinfo-prepends-rootdir.md) | Behavioral change | Preview 1 |
46+
| [New TimeSpan.From*() overloads that take integers](core-libraries/9.0/timespan-from-overloads.md) | Source incompatible | Preview 3 |
4547
| [RuntimeHelpers.GetSubArray returns different type](core-libraries/9.0/getsubarray-return.md) | Behavioral change | Preview 1 |
4648
| [Support for empty environment variables](core-libraries/9.0/empty-env-variable.md) | Behavioral change | Preview 6 |
4749
| [ZipArchiveEntry names and comments respect UTF8 flag](core-libraries/9.0/ziparchiveentry-encoding.md) | Behavioral change | RC 1 |
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
title: "Breaking change:BinaryReader.GetString() returns '\uFFFD' on malformed sequences"
3+
description: Learn about the .NET 9 breaking change in core .NET libraries where BinaryReader.GetString() returns "\uFFFD" on malformed encoded string sequences.
4+
ms.date: 10/03/2024
5+
---
6+
# BinaryReader.GetString() returns "\uFFFD" on malformed sequences
7+
8+
A a minor breaking change was introduced that only affects malformed encoded payloads.
9+
10+
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.
11+
12+
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.
13+
14+
## Previous behavior
15+
16+
```csharp
17+
var ms = new MemoryStream(new byte[] { 0x01, 0xC2 });
18+
using (var br = new BinaryReader(ms))
19+
{
20+
string s = br.ReadString();
21+
Console.WriteLine(s == "\uFFFD"); // false
22+
Console.WriteLine(s.Length); // 0
23+
}
24+
```
25+
26+
## New behavior
27+
28+
Starting in .NET 9, the same code snippet produces different results for `s == "\uFFFD"` and `s.Length`, as shown in the code comments:
29+
30+
```csharp
31+
var ms = new MemoryStream(new byte[] { 0x01, 0xC2 });
32+
using (var br = new BinaryReader(ms))
33+
{
34+
string s = br.ReadString();
35+
Console.WriteLine(s == "\uFFFD"); // true
36+
Console.WriteLine(s.Length); // 1
37+
}
38+
```
39+
40+
## Version introduced
41+
42+
.NET 9 Preview 7
43+
44+
## Type of breaking change
45+
46+
This change is a [behavioral change](../../categories.md#behavioral-change).
47+
48+
## Reason for change
49+
50+
This change was made as a performance improvement that affects a rare scenario.
51+
52+
## Recommended action
53+
54+
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.
55+
56+
## Affected APIs
57+
58+
- <xref:System.IO.BinaryReader.ReadString?displayProperty=fullName>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
title: "Breaking change: New TimeSpan.From*() overloads that take integers"
3+
description: Learn about the .NET 9 breaking change in core .NET libraries where new TimeSpan.From*() overloads were introduced that take integer arguments.
4+
ms.date: 10/03/2024
5+
---
6+
# New TimeSpan.From*() overloads that take integers
7+
8+
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.
9+
10+
## Previous behavior
11+
12+
Previously, there was a single overload for each `TimeSpan.From*()` method, namely:
13+
14+
- <xref:System.TimeSpan.FromDays(System.Double)>
15+
- <xref:System.TimeSpan.FromHours(System.Double)>
16+
- <xref:System.TimeSpan.FromMicroseconds(System.Double)>
17+
- <xref:System.TimeSpan.FromMilliseconds(System.Double)>
18+
- <xref:System.TimeSpan.FromMinutes(System.Double)>
19+
- <xref:System.TimeSpan.FromSeconds(System.Double)>
20+
21+
## New behavior
22+
23+
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:
24+
25+
> 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
26+
27+
## Version introduced
28+
29+
.NET 9 Preview 3
30+
31+
## Type of breaking change
32+
33+
This change can affect [source compatibility](../../categories.md#source-compatibility) for F# code.
34+
35+
## Reason for change
36+
37+
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.
38+
39+
## Recommended action
40+
41+
If this change affects your F# code, specify the type of argument so the compiler selects the appropriate overload.
42+
43+
## Affected APIs
44+
45+
- <xref:System.TimeSpan.FromDays*>
46+
- <xref:System.TimeSpan.FromHours*>
47+
- <xref:System.TimeSpan.FromMicroseconds*>
48+
- <xref:System.TimeSpan.FromMilliseconds*>
49+
- <xref:System.TimeSpan.FromMinutes*>
50+
- <xref:System.TimeSpan.FromSeconds*>

docs/core/compatibility/toc.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ items:
4040
href: core-libraries/9.0/inlinearray-size.md
4141
- name: InMemoryDirectoryInfo prepends rootDir to files
4242
href: core-libraries/9.0/inmemorydirinfo-prepends-rootdir.md
43+
- name: New TimeSpan.From*() overloads that take integers
44+
href: core-libraries/9.0/timespan-from-overloads.md
4345
- name: RuntimeHelpers.GetSubArray returns different type
4446
href: core-libraries/9.0/getsubarray-return.md
4547
- name: Support for empty environment variables
@@ -150,6 +152,8 @@ items:
150152
href: core-libraries/8.0/file-path-backslash.md
151153
- name: Base64.DecodeFromUtf8 methods ignore whitespace
152154
href: core-libraries/8.0/decodefromutf8-whitespace.md
155+
- name: BinaryReader.GetString() returns "/uFFFD" on malformed sequences
156+
href: core-libraries/9.0/binaryreader.md
153157
- name: Boolean-backed enum type support removed
154158
href: core-libraries/8.0/bool-backed-enum.md
155159
- name: Complex.ToString format changed to `<a; b>`
@@ -1244,6 +1248,8 @@ items:
12441248
href: core-libraries/9.0/obsolete-apis-with-custom-diagnostics.md
12451249
- name: BigInteger maximum length
12461250
href: core-libraries/9.0/biginteger-limit.md
1251+
- name: BinaryReader.GetString() returns "/uFFFD" on malformed sequences
1252+
href: core-libraries/9.0/binaryreader.md
12471253
- name: Creating type of array of System.Void not allowed
12481254
href: core-libraries/9.0/type-instance.md
12491255
- name: "`Equals`/`GetHashCode` throw for `InlineArrayAttribute` types"
@@ -1256,6 +1262,8 @@ items:
12561262
href: core-libraries/9.0/inlinearray-size.md
12571263
- name: InMemoryDirectoryInfo prepends rootDir to files
12581264
href: core-libraries/9.0/inmemorydirinfo-prepends-rootdir.md
1265+
- name: New TimeSpan.From*() overloads that take integers
1266+
href: core-libraries/9.0/timespan-from-overloads.md
12591267
- name: RuntimeHelpers.GetSubArray returns different type
12601268
href: core-libraries/9.0/getsubarray-return.md
12611269
- name: Support for empty environment variables

0 commit comments

Comments
 (0)