Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
98d914a
Add FileExtensions with WriteAllZeroes and ReadAllBytes (sync + async)
Copilot Apr 12, 2026
5aa9d98
Remove spurious bufferSize:1 from WriteAllZeroes FileStream
Copilot Apr 12, 2026
ecf7af5
Merge branch 'prime' into copilot/add-file-extensions-with-async-version
Tyrrrz Apr 12, 2026
896fb22
Update PowerKit/Extensions/FileExtensions.cs
Tyrrrz Apr 12, 2026
1663629
Fix build errors: replace FileStreamOptions with FileOptions ctor, ca…
Copilot Apr 12, 2026
a9a4a41
Change offset to long, add ReadAllBytes/Async overloads with offset+l…
Copilot Apr 12, 2026
36d7e9d
Use MemoryPool<byte>.Shared for buffer allocation in ReadAllBytes(Async)
Copilot Apr 12, 2026
f970532
Avoid redundant memory slicing by storing slice in local variable
Copilot Apr 12, 2026
5f3abb3
Merge branch 'prime' into copilot/add-file-extensions-with-async-version
Tyrrrz Apr 12, 2026
c09c408
Guard offset/length, drop MemoryPool in ReadAllBytes(Async); add edge…
Copilot Apr 12, 2026
43734d6
Return empty array instead of throwing when offset >= stream.Length
Copilot Apr 12, 2026
55cc511
Use async ReadAllBytesAsync in WriteAllZeroes_Test
Copilot Apr 12, 2026
63a574b
Guard length > int.MaxValue with ArgumentOutOfRangeException in offse…
Copilot Apr 12, 2026
7312aff
Change length parameter from long to int in offset+length overloads
Copilot Apr 12, 2026
3d8e839
Merge branch 'prime' into copilot/add-file-extensions-with-async-version
Tyrrrz Apr 12, 2026
33caa32
Use TempFile.Create() in FileExtensionsTests instead of manual temp p…
Copilot Apr 12, 2026
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
134 changes: 134 additions & 0 deletions PowerKit.Tests/FileExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System.IO;
using System.Threading.Tasks;
using FluentAssertions;
using PowerKit;
using PowerKit.Extensions;
using Xunit;

namespace PowerKit.Tests;

public class FileExtensionsTests
{
[Fact]
public async Task WriteAllZeroes_Test()
{
// Arrange
using var tempFile = TempFile.Create();

// Act
File.WriteAllZeroes(tempFile.Path, 1024);

// Assert
var bytes = await File.ReadAllBytesAsync(tempFile.Path);
bytes.Should().HaveCount(1024);
bytes.Should().AllSatisfy(b => b.Should().Be(0));
}

[Fact]
public void ReadAllBytes_WithOffset_Test()
{
// Arrange
using var tempFile = TempFile.Create();
File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]);

// Act
var bytes = File.ReadAllBytes(tempFile.Path, offset: 2L);

// Assert
bytes.Should().Equal(3, 4, 5);
}

[Fact]
public void ReadAllBytes_WithOffset_AtEndOfFile_Test()
{
// Arrange
using var tempFile = TempFile.Create();
File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]);

// Act
var bytes = File.ReadAllBytes(tempFile.Path, offset: 5L);

// Assert
bytes.Should().BeEmpty();
}

[Fact]
public void ReadAllBytes_WithOffset_PastEndOfFile_Test()
{
// Arrange
using var tempFile = TempFile.Create();
File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]);

// Act & Assert
var bytes = File.ReadAllBytes(tempFile.Path, offset: 10L);
bytes.Should().BeEmpty();
}

[Fact]
public void ReadAllBytes_WithOffsetAndLength_Test()
{
// Arrange
using var tempFile = TempFile.Create();
File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]);

// Act
var bytes = File.ReadAllBytes(tempFile.Path, offset: 1L, length: 3);

// Assert
bytes.Should().Equal(2, 3, 4);
}

[Fact]
public async Task ReadAllBytesAsync_WithOffset_Test()
{
// Arrange
using var tempFile = TempFile.Create();
File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]);

// Act
var bytes = await File.ReadAllBytesAsync(tempFile.Path, offset: 2L);

// Assert
bytes.Should().Equal(3, 4, 5);
}

[Fact]
public async Task ReadAllBytesAsync_WithOffset_AtEndOfFile_Test()
{
// Arrange
using var tempFile = TempFile.Create();
File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]);

// Act
var bytes = await File.ReadAllBytesAsync(tempFile.Path, offset: 5L);

// Assert
bytes.Should().BeEmpty();
}

[Fact]
public async Task ReadAllBytesAsync_WithOffset_PastEndOfFile_Test()
{
// Arrange
using var tempFile = TempFile.Create();
File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]);

// Act & Assert
var bytes = await File.ReadAllBytesAsync(tempFile.Path, offset: 10L);
bytes.Should().BeEmpty();
}

[Fact]
public async Task ReadAllBytesAsync_WithOffsetAndLength_Test()
{
// Arrange
using var tempFile = TempFile.Create();
File.WriteAllBytes(tempFile.Path, [1, 2, 3, 4, 5]);

// Act
var bytes = await File.ReadAllBytesAsync(tempFile.Path, offset: 1L, length: 3);

// Assert
bytes.Should().Equal(2, 3, 4);
}
}
140 changes: 140 additions & 0 deletions PowerKit/Extensions/FileExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace PowerKit.Extensions;

internal static class FileExtensions
{
extension(File)
{
/// <summary>
/// Creates a file at the specified path and fills it with zeroes.
/// </summary>
public static void WriteAllZeroes(string path, long count)
{
using var stream = new FileStream(
path,
FileMode.Create,
FileAccess.Write,
FileShare.None
);

stream.SetLength(count);
}

/// <summary>
/// Reads all bytes from the specified file starting at the given offset.
/// </summary>
public static byte[] ReadAllBytes(string path, long offset)
{
using var stream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite
);

stream.Seek(offset, SeekOrigin.Begin);

if (offset >= stream.Length)
{
return [];
}

var buffer = new byte[checked((int)(stream.Length - offset))];
stream.ReadExactly(buffer);

Comment thread
Tyrrrz marked this conversation as resolved.
return buffer;
}

/// <summary>
/// Reads the specified number of bytes from the file starting at the given offset.
/// </summary>
public static byte[] ReadAllBytes(string path, long offset, int length)
{
using var stream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite
);

stream.Seek(offset, SeekOrigin.Begin);

if (length < 0)
{
throw new ArgumentOutOfRangeException(nameof(length));
}

var buffer = new byte[length];
stream.ReadExactly(buffer);

Comment thread
Tyrrrz marked this conversation as resolved.
Comment thread
Tyrrrz marked this conversation as resolved.
return buffer;
}

/// <summary>
/// Reads all bytes from the specified file starting at the given offset asynchronously.
/// </summary>
public static async Task<byte[]> ReadAllBytesAsync(
string path,
long offset,
CancellationToken cancellationToken = default
)
{
using var stream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 4096,
FileOptions.Asynchronous
);

stream.Seek(offset, SeekOrigin.Begin);

if (offset >= stream.Length)
{
return [];
}

var buffer = new byte[checked((int)(stream.Length - offset))];
await stream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false);

return buffer;
Comment thread
Tyrrrz marked this conversation as resolved.
}

/// <summary>
/// Reads the specified number of bytes from the file starting at the given offset asynchronously.
/// </summary>
public static async Task<byte[]> ReadAllBytesAsync(
string path,
long offset,
int length,
CancellationToken cancellationToken = default
)
{
using var stream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 4096,
FileOptions.Asynchronous
);

stream.Seek(offset, SeekOrigin.Begin);

if (length < 0)
{
throw new ArgumentOutOfRangeException(nameof(length));
}

var buffer = new byte[length];
await stream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false);

Comment thread
Tyrrrz marked this conversation as resolved.
Comment thread
Tyrrrz marked this conversation as resolved.
return buffer;
}
}
}