views:

224

answers:

6

Please, explain your thoughts.

1.  DateTime dt = System.IO.File.GetLastAccessTime("C:\\There_is_no_such_file.txt");
2.  DateTime dt = System.IO.File.GetLastAccessTime("");
  1. If the file described in the path parameter does not exist, this method returns 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC), adjusted to local time.

  2. In the second situation argument exception is thrown.

Why in first case FileNotFoundException (or smth. simmilar) is not thrown?

A: 

i believe its by design

12:00 midnight, January 1, 1601 A.D. (C.E.) is the minium date value, some people consider it as a no value, but that was after the nullable types

Oscar Cabrero
Yes, but the question was why?
David Neale
+2  A: 

We're dealing with two different things.

When you call a method with an invalid argument, it should throw an exception.

If the file doesn't exist, this is not necessarily an exception. Therefore, a default value is returned, which you can test and decide how to proceed. For the GetLastAccessTime method, it isn't critical that the file exists. If it is critical for YOUR code, then you should be responsible for generating an error...

if (!File.Exists("C:\\There_is_no_such_file.txt")) {
    throw new FileNotFoundException();
}
Sohnee
I would have naturally checked for the existance of the file before calling GetLastAccessTime anyway. This behaviour seems odd.
David Neale
@David Neale: well consider thread safety as Donal Fellows mentioned in comment to my response. consider you have a solution where a non-existant file is the expected behavior, but if there is a file, you want to know its access time - and you want this to be thread safe. you'd have to retort to try/catch! if we can assume that the claim "this file exists and was accessed in the late renaissance" is nonsensical, than this is an informative way to go.
David Hedlund
... just as `"abc".indexOf("d")` will arguably tell you that "the string 'abc' contains 'd', at the position -1" only that's not how we interpret the result. if these types were nullable, then null would be the ubiquitous response to yield in these scenarios. but neither int or datetime is...
David Hedlund
A: 

The reason might be that you have turned off the "Enable unmanaged code debugging option" check in project properties under the Debug section.

Wodzu
This question does not call any unmanaged code.
David Neale
Of course it is but not explicitly.
Wodzu
+1  A: 

Well, I didn't write any of the System.IO library, so I can't claim to have the answer to what exceptions are thrown at what point. What qualifies as an exception will always be a decision for the developer to take.

I can take a stab at the reasoning behind this, though.

Having a file that doesn't exist may in a lot of cases be expected behavior. Having to hit the file system just to query whether a file exists, and then hit it again to get the access time for that file, might just have seemed like overhead, compared to simply hitting the file system once and verifying the result. If DateTime was nullable, this would probably have yielded null, just as one can imagine that IndexOf would have, instead of -1.

In the second case, however, passing an invalid path is evidence that somewhere in your code, something is maknig an expectation about something that cannot possibly work, and it might arguably make sense to bring this to the attention of the developer, by means of throwing an exception.

David Hedlund
Of course, if it threw an exception on any non-existent file, the right thing would be to just handle the exception rather than trying to beat a race condition (between the code reading the access time and some other process trying to delete the file).
Donal Fellows
@Donal Fellows: well yes, i suppose that's right, but even that would be a sad way to have to retort to. a non-existant file might even be the *most common scenario* in some cases, and having to treat that expected behavior by try-catching and exception would be poor design. I guess this line of reasoning is exactly what lead to the current design.
David Hedlund
@David: I think I disagree. One of the pernicious problems in C programming, especially when interfacing with the OS, is failing to check for error cases where special signal values are returned. Too many programmers just don't do that in the wild, or at least not until they've got burned badly by it. Exceptions help a lot by ensuring that code explicitly deals with things (or at least there's a deliberate policy to ignore problems).
Donal Fellows
@Donal Fellows: granted, but exceptions are heavy as well, and it's quite conceivable that a file not found can be the expected outcome in a given implementation. if you want that implementation thread-safe, you'd *have* to rely on catching exceptions. better solutions would be for the method to return a result object, such as `class { bool FileFound { get; set; } DateTime? AccessDate { get; set; } }`, or just returning a nullable datetime, `DateTime?`. to continue on a previous parallel: would you argue that `String#IndexOf` should throw an exception, if the input was not found in the string?
David Hedlund
a few more points to that: an implementation where an invalid path is the expected outcome is *not* very conceivable, hence the differences in treatment of the two cases. returning `null` for a nullable datetime is only better than returning `DateTime.MinValue` because you're going to notice a null value rather quickly, if you need the value at all, and because the return type would indicate that it *can* be null. Another pattern that would be acceptable is `bool TryGetLastAccessDate(string path, out DateTime? accessDate)`
David Hedlund
+1  A: 

If you ask the question "When was the last time, the file "There_is_no_such_file.txt" was accessed?", you could either answer "There is no such file" or "never".

Obviously, the team that designed the IO library opted for the second answer, with never being represented as DateTime.MinValue.

Jens
+7  A: 

This is documented behavior. From the Remarks section in the MSDN Library topic:

If the file described in the path parameter does not exist, this method returns 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC), adjusted to local time.

The exception you get when you pass an empty string is one that's generated by code that checks if the passed string is a valid path name. Which is fair, that would be bug in the program.

The code is explicit so it wasn't done by oversight or by mistake. It uses the FindFirstFile() API function to locate the file. If that fails, it checks the Windows error. And explicitly ignores, the "File not found", "Path not found" and "Drive busy" errors.

Beware that offered solutions that use File.Exists don't actually prevent this problem. Windows is a multi-tasking operating system. Your thread may be pre-empted right after the Exists call and another process may delete the file. When your thread regains the CPU, you'll still get the bogus date.

The only guaranteed way to get an accurate date is to open the file first so that nobody can delete the file from under you. Which I think explains why the method behaves like it does. The framework designers were stuck between a rock and a hard place. If they would have opened the file first, they would have risked other programs bombing on a file sharing error. If they don't open the file first, they risk your program bombing randomly and infrequently. Extremely hard to diagnose. Having to choose between two unpleasant options, they chose the one that doesn't bomb anything.

Anyhoo, make it reliable by opening the file.

Hans Passant
Very nice and clear description. Thanks.
LexRema
Excellent answer, +10 if I could.
Codesleuth
Interesting info. However, while the answer describes the mechanics, I don't find that it explains the design decision not to throw `FileNotFoundException` when the file is not found. After the call to `FindFirstFile`, *it is a known fact for the code if the file (or path) does not exist*, and there is no locking issue (as far as I can see) since there is no file. So I still don't understand the reasoning not to throw an exception in that particular case (as it does with `File.GetAttributes`).
Fredrik Mörk