Skip to content

[API Proposal]: Add a FileStream constructor that takes a SafeFileHandle and ownsValue or leaveOpen #128603

@Neme12

Description

@Neme12

Background and motivation

FileStream has obsoloted constructors that take a file handle as an IntPtr, and all of them have a ownsHandle parameter to say whether the FileStream disposal should dispose of the handle, but none of the constructors that take a SafeFileHandle have a similar parameter at all - they unconditionally dispose of the SafeFileHandle. I need to create a FileStream from a handle, but have the FileStream leave the handle open when it's disposed.

Creating a new SafeFileHandle(existingHandle.DangerousGetHandle(), ownsHandle: false) is not a solution as it does not hold on to the lifetime of the SafeFileHandle it comes from - it could be finalized while the FileStream is open. The only current solution is to create a derived type - LeaveOpenFileStream, and deriving from FileStream leaves perf on the table as it has checks whether it is a derived type or not.

API Proposal

namespace System.IO;

public partial class FileStream : Stream
{
	// Existing IntPtr constructors

	// [EditorBrowsable(EditorBrowsableState.Never)]
    // [Obsolete("This constructor has been deprecated. Use FileStream(SafeFileHandle handle, FileAccess access) instead.")]
    // public FileStream(IntPtr handle, FileAccess access);

    // [EditorBrowsable(EditorBrowsableState.Never)]
    // [Obsolete("This constructor has been deprecated. Use FileStream(SafeFileHandle handle, FileAccess access) and optionally make a new SafeFileHandle with ownsHandle=false if needed instead.")]
    // public FileStream(IntPtr handle, FileAccess access, bool ownsHandle);

    // [EditorBrowsable(EditorBrowsableState.Never)]
    // [Obsolete("This constructor has been deprecated. Use FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) and optionally make a new SafeFileHandle with ownsHandle=false if needed instead.")]
    // public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize);

    // [EditorBrowsable(EditorBrowsableState.Never)]
    // [Obsolete("This constructor has been deprecated. Use FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) and optionally make a new SafeFileHandle with ownsHandle=false if needed instead.")]
    // public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync);

	// Existing SafeFileHandle constructors

    // public FileStream(SafeFileHandle handle, FileAccess access);
    // public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize);
    // public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync);

	// Proposed constructor
	public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync, bool leaveOpen);
}

API Usage

using var fileStream = new FileStream(handle, FileAccess.Read, 4096, true, leaveOpen: true)
// ...use fileStream

Alternative Designs

As an alternative, we could create a method on SafeFileHandle that creates a new handle with a lifetime linked to the original handle - i.e. the original handle won't get finalized until the new one gets finalized too, but with ownsHandle set to false so that its disposal won't dispose of the main SafeFileHandle. The new handle would essentially just be a reference to the original one, and it could be an internal derived type.

namespace Microsoft.Win32.SafeHandles;

public sealed partial class SafeFileHandle
{
	public SafeFileHandle CreateNonOwnedHandle();
}

We could then use this API to create a non-owned handle to the original handle, whose disposal won't dispose of the original handle, but with no lifetime risks.

Risks

I don't know.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.IOuntriagedNew issue has not been triaged by the area owner

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions