-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Opening a file today in. NET today requires developers to introduce exception handling into their code. That is often unwanted and adds unnecessary complexity to otherwise straight forward code. .NET should offer non-throwing alternatives to facilitate these cases.
Rationale and Usage
File operations in .NET today require developers to introduce exception handling for even the simplest of operations. This is due to a combination of the operations being inherently unpredictable in nature and the BCL only provides exceptions as a way to indicate failure.
This is problematic because it forces exception handling into every .NET app which use the file system. In many cases developers would prefer to use simple if checks as it fits into the flow of their code. This is particularly true of lower level code which tends to be written with local control flow vs. exceptions. Using .NET though there is no way to avoid exceptions when dealing with the file system.
Additionally this is particularly annoying when debugging code in Visual Studio. Even when code has proper exception handling VS will still break File.Open calls when first chance exceptions are enabled (quite common). Disabling notifications for these exceptions is often not an option because in addition to hiding benign case it could hide the real error that you're trying to debug.
Many developers attempt to work around this by using the following anti-pattern:
static bool TryOpen(string filePath, out FileStream fileStream) {
if (!File.Exists(filePath)) {
fileStream = null;
return false;
}
fileStream = File.Open(filePath);
return true;
}This code reads great but is quite broken in at least the following ways:
- The developer mistakenly assumes that
File.Existsis a side effect free operation. This is not always the case. Example is whenfilePathpoints to a named pipe. CallingExistsfor a named pipe which is waiting for a connection will actually complete the connection under the hood (and then immediately break the pipe). - The
File.Opencall can still throw exceptions hence even with theFile.Existspredicate. The file could be deleted between the call, it could be open as part of an NTFS transactional operation, permissions could change, etc ... Hence correct code must still use exception handling here.
A more correct work around looks like the following. Even this sample I'm sure is subtle to cases that would have an observable difference from just calling File.Open):
static bool TryOpen(string filePath, out FileStream fileStream) {
if (!filePath.StartsWith(@"\\.\pipe", StringComparison.OrdinalIgnoreCase) && !File.Exists(filePath)) {
fileStream = null;
return false;
}
try {
fileStream = File.Open(filePath);
return true;
}
catch (Exception) {
fileStream = null;
return false;
}
}To support these scenarios .NET should offer a set of file system helpers that use return values to indicate failure via the Try pattern: File.TryOpen.
Proposed API
public static class File {
public static bool TryOpen(
string path,
FileMode mode,
FileAccess access,
FileShare share,
out FileStream fileStream);
public static bool TryOpen(
string path,
FileMode mode,
out FileStream fileStream);
public static bool TryOpen(
string path,
FileMode mode,
FileAccess access,
out FileStream fileStream);Details
- Considered
TryOpenExistinginstead ofTryOpenas there is prior art here in
Mutex.TryOpenExisting. That seems like a good fit forMutexas it had anOpenExistingmethod. Not a good fit forFilewhich hasOpen
and a series of other modes viaFileModethat don't like up with the terminology Existing. - Considered expanding this to other types like
Directory. Similar rationale exists for that but theFileexamples are much more predominant. Can expand based on feedback. - Any pattern which involves
File.Existsis inherently broken. When many developers are using it for common patterns it generally indicates there is a more correct API that has not been provided.