29

I am trying to write a function to determine if a file exists. The two methods prove to return inconsistent results (fileExists() seems to provide accurate results, compared to isFileFound(), that returns false positives - i would have expected an exception when trying to create the instance).

protected bool isFileFound(string path, string fileName)
    {
        System.IO.FileInfo fi = null;

        bool found = false;
        try
        {
            fi = new System.IO.FileInfo(path + fileName);
            found = true;
        }
        catch (Exception e)
        {
            baselogger.Fatal(e.Message + " " + e.StackTrace + " \n" + path + fileName);
        }

        return found;
    }

    protected bool fileExists(string path, string pattern)
    {
        bool success = false;

        try
        {
            success = File.Exists(path + pattern);
        }
        catch (Exception e)
        {
            baselogger.Warn(e.Message + " " + e.StackTrace + " " + e.Source);
        }

        return success;
    }

Neither seems to be able to resolve a UNC path of the following syntax: \\abcserver\c$\xyzfolder\foo.bar

Any idea why the unc path is failing for these methods would be greatly appreciated.

steve_mtl
  • 960
  • 4
  • 12
  • 18
  • 14
    FYI - As a general practice, I try to use System.IO.Path.Combine(path, filename) instead of string concatenation: path + filename – Ryan Jan 19 '09 at 17:36
  • 4
    A caveat: IO.Path.Combine("D:\Files", "\foo.xml") will give you "\foo.xml" and next thing you know you've got files saving to root. – Tyler Jan 31 '13 at 19:46
  • double slashes (@"D:\files\\foo.xml") works with File.Exists, but doesn't work with UNC (@"\\?\d:\files\\foo.xml") – ephraim Aug 28 '22 at 11:44

6 Answers6

40

You can create a FileInfo for an non-existing file. But then you can check the FileInfo.Exists property to determine whether the file exists, e.g:

FileInfo fi = new FileInfo(somePath);
bool exists = fi.Exists;

Update: In a short test this also worked for UNC paths, e.g. like this:

FileInfo fi = new FileInfo(@"\\server\share\file.txt");
bool exists = fi.Exists;

Are you sure that the account (under which your application is running) has access to the share. I think that (by default) administrative rights are required to access the share "c$".

M4N
  • 94,805
  • 45
  • 217
  • 260
  • 1
    Ok, but what about the UNC path? Even with the modification to this method, neither method can resolve the UNC path format in my original question. – steve_mtl Jan 19 '09 at 17:24
  • Tried an alternate folder witha d$ share, and it worked. A little digging concluded that the UNC path is valid, but some permissions changes are needed. – steve_mtl Jan 19 '09 at 18:15
  • If the UNC path is not avaiable anymore isn't there a timeout with this method? – zirbel Nov 28 '13 at 09:34
  • 2
    This has helped me a great deal - thanks M4N. In my example a System.IO.File.Exists returns TRUE but a FileInfo.exists on the same path returns FALSE!? And I know for a fact that the file does not exist... – JLo Jan 30 '15 at 09:27
16

See this question:
how can you easily check if access is denied for a file in .NET?

The short version of that question is that you don't, because the file system is volatile. Just try to open the file and catch the exception if it fails.

The reason your isFileFound method doesn't work is because the FileInfo structure you are using can also be used to create files. You can create a FileInfo object with the desired info for a non-existing file, call it's .Create() method, and you've set your desired properties all at once.

I suspect the reason the UNC path fails is either 1) a permissions issue accessing the admin share from the user running your app, or 2) The $ symbol is throwing the method off, either because it's not being input correctly or because of a bug in the underlying .Exists() implementation.

Update:

When I post this suggestion, I nearly always get a complaint about exception performance. Let's talk about that. Yes, handling exceptions is expensive: very expensive. There are few things you can do in programming that are slower. But you know what one those few things is? Disk and network I/O. Here's a link that demonstrates just how much disk I/O and network I/O cost:

https://gist.github.com/jboner/2841832

Latency Comparison Numbers
--------------------------
L1 cache reference                            0.5 ns
Branch mispredict                             5   ns
L2 cache reference                            7   ns             14x L1 cache
Mutex lock/unlock                            25   ns
Main memory reference                       100   ns             20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy              3,000   ns
Send 1K bytes over 1 Gbps network        10,000   ns    0.01 ms
Read 4K randomly from SSD*              150,000   ns    0.15 ms
Read 1 MB sequentially from memory      250,000   ns    0.25 ms
Round trip within same datacenter       500,000   ns    0.5  ms
Read 1 MB sequentially from SSD*      1,000,000   ns    1    ms  4X memory
Disk seek                            10,000,000   ns   10    ms  20x datacenter roundtrip
Read 1 MB sequentially from disk     20,000,000   ns   20    ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA     150,000,000   ns  150    ms

If thinking in nanoseconds isn't your thing, here's another link that normalizes one CPU cycle as 1 second and scales from there:

http://blog.codinghorror.com/the-infinite-space-between-words/

1 CPU cycle             0.3 ns      1 s
Level 1 cache access    0.9 ns      3 s
Level 2 cache access    2.8 ns      9 s
Level 3 cache access    12.9 ns     43 s
Main memory access      120 ns      6 min
Solid-state disk I/O    50-150 μs   2-6 days
Rotational disk I/O     1-10 ms     1-12 months
Internet: SF to NYC     40 ms       4 years
Internet: SF to UK      81 ms       8 years
Internet: SF to AUS     183 ms      19 years
OS virt. reboot         4 s         423 years
SCSI command time-out   30 s        3000 years
Hardware virt. reboot   40 s        4000 years
Physical system reboot  5 m         32 millenia

Taking even the best-case scenario for exceptions, you can access memory at least 480 times while waiting on the first response from a disk, and that's assuming a very fast SSD. Many of us still need spinning hard-drives, where things get much, much worse.

And that's only the beginning of the story. When you use .Exists(), you incur this additional cost (and it is an addition: you have to do the same work again when you go to open the file) on every attempt. You pay this costs whether the file exists or not, because the disk still has to go look for it in it's file tables. With the exception method, you only pay the extra cost of unwinding the call stack in the case of failure.

In other words, yes: exceptions are horribly costly. But compared to the disk check, it's still faster: and not by just a small margin. Thankfully, this is unlikely to drive your app's general performance... but I still want to put to bed the "exceptions are slow" argument for this specific task.

Community
  • 1
  • 1
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • 2
    Beware that catching thrown exceptions can be very expensive. If you are going to be performing batch file operations you would be much better of testing for file existence *and* catching exceptions. – Ifeanyi Echeruo Jan 20 '09 at 18:06
  • 1
    @IfeanyiEcheruo Compared to the overhead of the network file operations, and the cost of the possible douboemprocessinand timing window errors, the cost of catching an exception is utterly negligible. – user207421 Oct 20 '14 at 21:18
  • 2
    Put another way: with the `.Exists()` check, you _always_ pay the network and disk i/o costs to discover this, whether the file exists or not. With the exception method, you only pay the extra cost of unwinding the call stack in the case of failure. Additionally, as expensive as exceptions are, network and disk i/o are much, _much_ worse. In fact, I think I'll add this to the answer. – Joel Coehoorn Oct 20 '14 at 22:03
  • What about the caching done by the operating system or even by the disk itself? – Cristian Ciupitu Feb 09 '16 at 14:34
3

This may or may not be the case, but could you be joining your path an file name incorrectly for one of your cases.

This:

success = File.Exists(path + pattern);

vs:

success = File.Exists(Path.Join(path,pattern));

llamaoo7
  • 4,051
  • 2
  • 22
  • 22
  • 2
    I think you meant [`Path.Combine`](https://msdn.microsoft.com/en-us/library/fyy7a5kt.aspx), not `Path.Join`. – Cristian Ciupitu Feb 09 '16 at 14:33
  • [`Path.Join`](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.join?view=net-5.0) exists since .NET Core 2.1. – Manuzor Feb 26 '21 at 14:56
1

Edit: Well I just realized file.exists works ok. That would definitely be the preferred method. Below code would give you the option of having windows prompt the user for authentication, if the share should be accessed under a different domain account. Might help somebody someday so I'll just leave the code here.

If you need to access a UNC path or admin share using different credentials: MSDN

To bootstrap use of WNetAddConnection2 use this code:

using System;
using System.Runtime.InteropServices;

namespace Win32Api
{
    public enum ResourceScope
    {
        RESOURCE_CONNECTED = 1,
        RESOURCE_GLOBALNET,
        RESOURCE_REMEMBERED,
        RESOURCE_RECENT,
        RESOURCE_CONTEXT
    };

    public enum ResourceType
    {
        RESOURCETYPE_ANY,
        RESOURCETYPE_DISK,
        RESOURCETYPE_PRINT,
        RESOURCETYPE_RESERVED = 8
    };

    [Flags]
    public enum ResourceUsage
    {
        RESOURCEUSAGE_CONNECTABLE = 0x00000001,
        RESOURCEUSAGE_CONTAINER = 0x00000002,
        RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
        RESOURCEUSAGE_SIBLING = 0x00000008,
        RESOURCEUSAGE_ATTACHED = 0x00000010,
        RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE |
                             RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
    };

    public enum ResourceDisplayType
    {
        RESOURCEDISPLAYTYPE_GENERIC,
        RESOURCEDISPLAYTYPE_DOMAIN,
        RESOURCEDISPLAYTYPE_SERVER,
        RESOURCEDISPLAYTYPE_SHARE,
        RESOURCEDISPLAYTYPE_FILE,
        RESOURCEDISPLAYTYPE_GROUP,
        RESOURCEDISPLAYTYPE_NETWORK,
        RESOURCEDISPLAYTYPE_ROOT,
        RESOURCEDISPLAYTYPE_SHAREADMIN,
        RESOURCEDISPLAYTYPE_DIRECTORY,
        RESOURCEDISPLAYTYPE_TREE,
        RESOURCEDISPLAYTYPE_NDSCONTAINER
    };

    [StructLayout(LayoutKind.Sequential)]
    public class NetResource
    {
        public ResourceScope Scope;
        public ResourceType Type;
        public ResourceDisplayType DisplayType;
        public ResourceUsage Usage;
        public string LocalName;
        public string RemoteName;
        public string Comment;
        public string Provider;
    };

    [Flags]
    public enum AddConnectionOptions
    {
        CONNECT_UPDATE_PROFILE = 0x00000001,
        CONNECT_UPDATE_RECENT = 0x00000002,
        CONNECT_TEMPORARY = 0x00000004,
        CONNECT_INTERACTIVE = 0x00000008,
        CONNECT_PROMPT = 0x00000010,
        CONNECT_NEED_DRIVE = 0x00000020,
        CONNECT_REFCOUNT = 0x00000040,
        CONNECT_REDIRECT = 0x00000080,
        CONNECT_LOCALDRIVE = 0x00000100,
        CONNECT_CURRENT_MEDIA = 0x00000200,
        CONNECT_DEFERRED = 0x00000400,
        CONNECT_RESERVED = unchecked((int)0xFF000000),
        CONNECT_COMMANDLINE = 0x00000800,
        CONNECT_CMD_SAVECRED = 0x00001000,
        CONNECT_CRED_RESET = 0x00002000
    }

    public static class NativeMethods
    {
        [DllImport("mpr.dll", EntryPoint = "WNetAddConnection2")]
        public static extern int WNetAddConnection2(
            NetResource netResource, string password,
            string username, AddConnectionOptions options);

        [DllImport("mpr.dll")]
        public static extern int WNetCancelConnection2(string name, int flags,
        bool force);

    }
}
HAL9000
  • 1,002
  • 2
  • 17
  • 28
1

This can help you:
http://www.codeplex.com/FileDirectoryPath
It's NDepend.Helpers.FilePathDirectory, that have a "Path validity check API" among other that can be useful.

Click Ok
  • 8,700
  • 18
  • 70
  • 106
1

So i went with the

bool success = File.Exists(path + Filename);

option, as opposed to using the FileInfo route.

Thanks for all the suggestions!

steve_mtl
  • 960
  • 4
  • 12
  • 18
  • 8
    You may want to use System.IO.Path.Combine(path, Filename) instead of string addition. This takes care of corner cases e.g. "a\" + "b" = "a\b" and Path.Combine("a\", "b") = "a\b" but "a" + "b" = "ab" and Path.Combine("a", "b") = "a\b" – Ifeanyi Echeruo Jan 20 '09 at 18:10