Skip to content

Commit d9ddaaa

Browse files
Copilotstephentoub
andauthored
Add LoadFromAsync and SaveToAsync helper methods to DataContent (#7159)
Co-authored-by: stephentoub Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent 281fde6 commit d9ddaaa

File tree

18 files changed

+1724
-164
lines changed

18 files changed

+1724
-164
lines changed

eng/MSBuild/LegacySupport.props

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,16 @@
7575
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\CollectionBuilder\*.cs" LinkBase="LegacySupport\CollectionBuilder" />
7676
</ItemGroup>
7777

78+
<ItemGroup Condition="'$(InjectMediaTypeMapOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net8.0' or '$(TargetFramework)' == 'net9.0' or '$(TargetFramework)' == 'net10.0')">
79+
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\MediaTypeMap\*.cs" LinkBase="LegacySupport\MediaTypeMap" />
80+
</ItemGroup>
81+
7882
<ItemGroup Condition="'$(InjectNullabilityInfoContextOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1')">
7983
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\NullabilityInfoContext\*.cs" LinkBase="LegacySupport\NullabilityInfoContext" />
8084
</ItemGroup>
85+
86+
<!-- FilePolyfills: Adds C# 14 extension members for File methods not available on legacy frameworks -->
87+
<ItemGroup Condition="'$(InjectFilePolyfillsOnLegacy)' == 'true'">
88+
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\FilePolyfills\*.cs" LinkBase="LegacySupport\FilePolyfills" />
89+
</ItemGroup>
8190
</Project>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if !NET9_0_OR_GREATER
5+
6+
#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
7+
8+
using System.Diagnostics.CodeAnalysis;
9+
using System.Runtime.InteropServices;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
13+
namespace System.IO;
14+
15+
/// <summary>
16+
/// Provides polyfill extension members for <see cref="File"/> for older frameworks.
17+
/// </summary>
18+
[ExcludeFromCodeCoverage]
19+
internal static class FilePolyfills
20+
{
21+
extension(File)
22+
{
23+
#if !NET
24+
/// <summary>
25+
/// Asynchronously reads all bytes from a file.
26+
/// </summary>
27+
/// <param name="path">The file to read from.</param>
28+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
29+
/// <returns>A task that represents the asynchronous read operation, which wraps the byte array containing the contents of the file.</returns>
30+
public static async Task<byte[]> ReadAllBytesAsync(string path, CancellationToken cancellationToken = default)
31+
{
32+
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true);
33+
byte[] data = new byte[stream.Length];
34+
int totalRead = 0;
35+
while (totalRead < data.Length)
36+
{
37+
int read = await stream.ReadAsync(data, totalRead, data.Length - totalRead, cancellationToken).ConfigureAwait(false);
38+
if (read == 0)
39+
{
40+
break;
41+
}
42+
43+
totalRead += read;
44+
}
45+
46+
return data;
47+
}
48+
#endif
49+
50+
/// <summary>
51+
/// Asynchronously writes all bytes to a file.
52+
/// </summary>
53+
/// <param name="path">The file to write to.</param>
54+
/// <param name="bytes">The bytes to write to the file.</param>
55+
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
56+
/// <returns>A task that represents the asynchronous write operation.</returns>
57+
public static async Task WriteAllBytesAsync(string path, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken = default)
58+
{
59+
// Try to avoid ToArray() if the data is backed by a byte[] with offset 0 and matching length
60+
byte[] byteArray;
61+
if (MemoryMarshal.TryGetArray(bytes, out ArraySegment<byte> segment) &&
62+
segment.Offset == 0 &&
63+
segment.Count == segment.Array!.Length)
64+
{
65+
byteArray = segment.Array;
66+
}
67+
else
68+
{
69+
byteArray = bytes.ToArray();
70+
}
71+
72+
#if NET
73+
await File.WriteAllBytesAsync(path, byteArray, cancellationToken).ConfigureAwait(false);
74+
#else
75+
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true);
76+
await stream.WriteAsync(byteArray, 0, byteArray.Length, cancellationToken).ConfigureAwait(false);
77+
#endif
78+
}
79+
}
80+
}
81+
82+
#endif
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# About FilePolyfills
2+
3+
This folder contains C# 14 extension member polyfills for `System.IO.File` methods
4+
that are not available on older frameworks.
5+
6+
- `File.ReadAllBytesAsync` - Added in .NET Core 2.0, not available in .NET Framework 4.6.2 or .NET Standard 2.0
7+
8+
The polyfill uses C# 14 extension members so the call site can use `File.ReadAllBytesAsync` naturally
9+
and it will use the real one on supported platforms and the polyfill elsewhere.

0 commit comments

Comments
 (0)