2
\$\begingroup\$

Anyone can tell how to make this code faster? This search takes too much time... 700 000 files for 15 minutes... I will be grateful for each micro optimization!

Search Class:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;

public class FindFile
{
    private static readonly char[] InvalidFilePathChars;
    private static readonly char[] InvalidFilePatternChars;
    static FindFile()
    {
        InvalidFilePathChars = Path.GetInvalidPathChars();
        List<char> set = new List<char>(Path.GetInvalidFileNameChars());
        set.Remove('*');
        set.Remove('?');
        InvalidFilePatternChars = set.ToArray();
    }
    // Enumerates the files directly in the directory specified
    public static void FilesIn(string directory, Action<FileFoundEventArgs> e)
    {
        FindFile ff = new FindFile(directory, STAR, false, false, true);
        ff.FileFound = (o, a) => e(a);
        ff.Find();
    }
    //Enumerates the folders directly in the directory specified
    public static void FoldersIn(string directory, Action<FileFoundEventArgs> e)
    {
        FindFile ff = new FindFile(directory, STAR, false, true, false);
        ff.FileFound = (o, a) => e(a);
        ff.Find();
    }
    //Enumerates the files and folders directly in the directory specified
    public static void FilesAndFoldersIn(string directory, Action<FileFoundEventArgs> e)
    {
        FindFile ff = new FindFile(directory, STAR, false, true, true);
        ff.FileFound = (o, a) => e(a);
        ff.Find();
    }
    //Enumerates the files anywhere under the directory specified
    public static void AllFilesIn(string directory, Action<FileFoundEventArgs> e)
    {
        FindFile ff = new FindFile(directory, STAR, true, false, true);
        ff.FileFound = (o, a) => e(a);
        ff.Find();
    }
    //Enumerates the folders anywhere under the directory specified
    public static void AllFoldersIn(string directory, Action<FileFoundEventArgs> e)
    {
        FindFile ff = new FindFile(directory, STAR, true, true, false);
        ff.FileFound = (o, a) => e(a);
        ff.Find();
    }
    //Enumerates the files and folders anywhere under the directory specified
    public static void AllFilesAndFoldersIn(string directory, Action<FileFoundEventArgs> e)
    {
        FindFile ff = new FindFile(directory, STAR, true, true, true);
        ff.FileFound = (o, a) => e(a);
        ff.Find();
    }
    #region Kernel32
    internal static class Kernel32
    {
        internal const int MAX_PATH = 260;
        internal const int MAX_ALTERNATE = 14;
        internal const int ERROR_FILE_NOT_FOUND = 2;
        internal const int ERROR_PATH_NOT_FOUND = 3;
        internal const int ERROR_ACCESS_DENIED = 5;

        [StructLayout(LayoutKind.Sequential)]
        public struct FILETIME
        {
            public uint dwLowDateTime;
            public uint dwHighDateTime;

            public DateTime ToDateTimeUtc()
            {
                return DateTime.FromFileTimeUtc(dwLowDateTime | ((long)dwHighDateTime << 32));
            }
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct WIN32_FIND_DATA
        {
            public FileAttributes dwFileAttributes;
            public FILETIME ftCreationTime;
            public FILETIME ftLastAccessTime;
            public FILETIME ftLastWriteTime;
            public uint nFileSizeHigh; //changed all to uint from int, otherwise you run into unexpected overflow
            public uint nFileSizeLow;
            private uint dwReserved0;
            private uint dwReserved1;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_PATH)]
            public char[] cFileName;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_ALTERNATE)]
            private char[] cAlternateFileName;

            public bool IgnoredByName
            {
                get
                {
                    return
                        (cFileName[0] == ZERO) ||
                        (cFileName[0] == '.' && cFileName[1] == ZERO) ||
                        (cFileName[0] == '.' && cFileName[1] == '.' && cFileName[2] == ZERO);
                }
            }
        }

        public enum FINDEX_INFO_LEVELS
        {
            FindExInfoStandard = 0,
            FindExInfoBasic = 1
        }

        public enum FINDEX_SEARCH_OPS
        {
            FindExSearchNameMatch = 0,
            FindExSearchLimitToDirectories = 1,
            FindExSearchLimitToDevices = 2
        }

        [Flags]
        public enum FINDEX_ADDITIONAL_FLAGS
        {
            FindFirstExCaseSensitive = 1,
            FindFirstExLargeFetch = 2,
        }

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern IntPtr FindFirstFileEx(
            IntPtr lpFileName,
            FINDEX_INFO_LEVELS fInfoLevelId,
            out WIN32_FIND_DATA lpFindFileData,
            FINDEX_SEARCH_OPS fSearchOp,
            IntPtr lpSearchFilter,
            FINDEX_ADDITIONAL_FLAGS dwAdditionalFlags);

        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

        [DllImport("kernel32.dll")]
        public static extern bool FindClose(IntPtr hFindFile);
    }
    #endregion

    public struct Info
    {
        //Returns the parent folder's full path
        public string ParentPath { get { return Path.GetDirectoryName(FullPath); } }
        //Gets or sets the full path of the file or folder
        public string FullPath { get; set; }
        //Returns the file or folder name (with extension)
        public string Name { get { return Path.GetFileName(FullPath); } }
        //Returns the extenion or String.Empty
        public string Extension { get { return Path.GetExtension(FullPath); } }
        //Returns the UNC path to the parent folder
        public string ParentPathUnc { get { return (FullPath.StartsWith(@"\\")) ? ParentPath : (UncPrefix + ParentPath); } }
        //Returns the UNC path to the file or folder
        public string FullPathUnc { get { return (FullPath.StartsWith(@"\\")) ? FullPath : (UncPrefix + FullPath); } }
        //Gets or sets the length in bytes
        public long Length { get; set; }
        //Gets or sets the file or folder attributes
        public FileAttributes Attributes { get; set; }
        //Gets or sets the file or folder CreationTime in Utc
        public DateTime CreationTimeUtc { get; set; }
        //Gets or sets the file or folder LastAccessTime in Utc
        public DateTime LastAccessTimeUtc { get; set; }
        //Gets or sets the file or folder LastWriteTime in Utc
        public DateTime LastWriteTimeUtc { get; set; }
    }

    internal class Win32FindData
    {
        public char[] Buffer;
        public IntPtr BufferAddress;
        public Kernel32.WIN32_FIND_DATA Value;
    }

    // Provides access to the file or folder information durring enumeration, DO NOT keep a reference to this
    // class as it's meaning will change durring enumeration.

    public sealed class FileFoundEventArgs : EventArgs
    {
        private readonly Win32FindData _ff;
        private int _uncPrefixLength;
        private int _parentNameLength;
        private int _itemNameLength;
        private bool _cancelEnumeration;

        internal FileFoundEventArgs(Win32FindData ff)
        {
            _ff = ff;
        }

        internal void SetNameOffsets(int uncPrefixLength, int parentIx, int itemIx)
        {
            _parentNameLength = parentIx;
            _itemNameLength = itemIx;
            _uncPrefixLength = uncPrefixLength;
        }

        //Returns the parent folder's full path
        public string ParentPath { get { return new String(_ff.Buffer, _uncPrefixLength, _parentNameLength - _uncPrefixLength); } }
        //Returns the UNC path to the parent folder
        public string ParentPathUnc { get { return new String(_ff.Buffer, 0, _parentNameLength); } }
        //Gets the full path of the file or folder
        public string FullPath { get { return new String(_ff.Buffer, _uncPrefixLength, _itemNameLength - _uncPrefixLength); } }
        //Returns the UNC path to the file or folder
        public string FullPathUnc { get { return new String(_ff.Buffer, 0, _itemNameLength); } }
        //Returns the file or folder name (with extension)
        public string Name { get { return new String(_ff.Buffer, _parentNameLength, _itemNameLength - _parentNameLength); } }
        //Returns the extenion or String.Empty
        public string Extension
        {
            get
            {
                for (int ix = _itemNameLength; ix > _parentNameLength; --ix)
                    if (_ff.Buffer[ix] == '.')
                        return new String(_ff.Buffer, ix, _itemNameLength - ix);
                return string.Empty;
            }
        }

        //Gets the length in bytes
        public long Length { get { return _ff.Value.nFileSizeLow | ((long)_ff.Value.nFileSizeHigh << 32); } }
        //Gets the file or folder attributes
        public FileAttributes Attributes { get { return _ff.Value.dwFileAttributes; } }
        //Gets the file or folder CreationTime in Utc
        public DateTime CreationTimeUtc { get { return _ff.Value.ftCreationTime.ToDateTimeUtc(); } }
        //Gets the file or folder LastAccessTime in Utc
        public DateTime LastAccessTimeUtc { get { return _ff.Value.ftLastAccessTime.ToDateTimeUtc(); } }
        //Gets the file or folder LastWriteTime in Utc
        public DateTime LastWriteTimeUtc { get { return _ff.Value.ftLastWriteTime.ToDateTimeUtc(); } }
        //Returns true if the file or folder is ReadOnly
        public bool IsReadOnly { get { return (Attributes & FileAttributes.ReadOnly) != 0; } }
        //Returns true if the file or folder is Hidden
        public bool IsHidden { get { return (Attributes & FileAttributes.Hidden) != 0; } }
        //Returns true if the file or folder is System
        public bool IsSystem { get { return (Attributes & FileAttributes.System) != 0; } }
        //Returns true if the file or folder is Directory
        public bool IsDirectory { get { return (Attributes & FileAttributes.Directory) != 0; } }
        //Returns true if the file or folder is ReparsePoint
        public bool IsReparsePoint { get { return (Attributes & FileAttributes.ReparsePoint) != 0; } }
        //Returns true if the file or folder is Compressed
        public bool IsCompressed { get { return (Attributes & FileAttributes.Compressed) != 0; } }
        //Returns true if the file or folder is Offline
        public bool IsOffline { get { return (Attributes & FileAttributes.Offline) != 0; } }
        //Returns true if the file or folder is Encrypted
        public bool IsEncrypted { get { return (Attributes & FileAttributes.Encrypted) != 0; } }
        // Captures the current state as a <see cref="FindFile.Info"/> structure.

        public Info GetInfo()
        {
            return new Info
            {
                FullPath = FullPath,
                Length = Length,
                Attributes = Attributes,
                CreationTimeUtc = CreationTimeUtc,
                LastAccessTimeUtc = LastAccessTimeUtc,
                LastWriteTimeUtc = LastWriteTimeUtc,
            };
        }
        //Gets or sets the Cancel flag to abort the current enumeration
        public bool CancelEnumeration
        {
            get { return _cancelEnumeration; }
            set { _cancelEnumeration = value; }
        }
    }

    private const string STAR = "*";
    private const char SLASH = '\\';
    private const char ZERO = '\0';
    //Returns the Unc path prefix used
    public const string UncPrefix = @"\\?\";

    private readonly Win32FindData _ff;

    private char[] _fpattern;
    private int _baseOffset;
    private bool _recursive;
    private bool _includeFolders;
    private bool _includeFiles;
    private bool _isUncPath;
    //Creates a FindFile instance.
    public FindFile() : this(UncPrefix, STAR, true, true, true) { }
    //Creates a FindFile instance.
    public FindFile(string rootDirectory) : this(rootDirectory, STAR, true, true, true) { }
    //Creates a FindFile instance.
    public FindFile(string rootDirectory, string filePattern) : this(rootDirectory, filePattern, true, true, true) { }
    //Creates a FindFile instance.
    public FindFile(string rootDirectory, string filePattern, bool recursive) : this(rootDirectory, filePattern, recursive, true, true) { }
    //Creates a FindFile instance.
    public FindFile(string rootDirectory, string filePattern, bool recursive, bool includeFolders) : this(rootDirectory, filePattern, recursive, includeFolders, true) { }
    //Creates a FindFile instance.
    public FindFile(string rootDirectory, string filePattern, bool recursive, bool includeFolders, bool includeFiles)
    {
        if (String.IsNullOrEmpty(rootDirectory) || String.IsNullOrEmpty(filePattern))
            throw new ArgumentException();

        _ff = new Win32FindData();
        _ff.BufferAddress = IntPtr.Zero;
        _ff.Buffer = new char[0x1000];
        _ff.Value = new Kernel32.WIN32_FIND_DATA();

        _recursive = recursive;
        _includeFolders = includeFolders;
        _includeFiles = includeFiles;

        BaseDirectory = rootDirectory;
        FilePattern = filePattern;
    }
    // The event-handler to raise when a file or folder is found
    public event EventHandler<FileFoundEventArgs> FileFound;
    // Gets or sets the maximum number of allowed characters in a complete path, default = 4kb

    public int MaxPath
    {
        get { return _ff.Buffer.Length; }
        set { Array.Resize(ref _ff.Buffer, Check.InRange(value, Kernel32.MAX_PATH, 0x100000)); }
    }
    private int UncPrefixLength { get { return _isUncPath ? 4 : 0; } }
    //Gets or sets the base directory to search within
    public string BaseDirectory
    {
        get { return new String(_ff.Buffer, UncPrefixLength, _baseOffset - UncPrefixLength); }
        set
        {
            if (value.IndexOfAny(InvalidFilePathChars) > 0)
                throw new InvalidOperationException("Invalid characters in path.");

            if (!value.StartsWith(@"\\"))
                value = UncPrefix + value;
            if (!value.EndsWith(@"\"))
                value += @"\";

            _isUncPath = value.StartsWith(UncPrefix);
            value.CopyTo(0, _ff.Buffer, 0, _baseOffset = value.Length);
        }
    }
    // Gets or sets the file pattern to match while enumerating files and folders.

    public string FilePattern
    {
        get { return new String(_fpattern); }
        set
        {
            if (value.IndexOfAny(InvalidFilePatternChars) >= 0)
                throw new InvalidOperationException("Invalid characters in pattern");
            _fpattern = value.TrimStart(SLASH).ToCharArray();
        }
    }
    //Gets or sets the Recursive flag
    public bool Recursive { get { return _recursive; } set { _recursive = value; } }
    //Gets or sets the IncludeFiles flag
    public bool IncludeFiles { get { return _includeFiles; } set { _includeFiles = value; } }
    //Gets or sets the IncludeFolders flag
    public bool IncludeFolders { get { return _includeFolders; } set { _includeFolders = value; } }
    //Gets or sets the RaiseOnAccessDenied flag, when set to true an 'Access Denied' can be raised
    public bool RaiseOnAccessDenied { get; set; }
    //Performs the search raising the FileFound event for each entry matching the request
    public void Find(string pattern)
    {
        FilePattern = pattern;
        Find();
    }
    //Performs the search raising the FileFound event for each entry matching the request
    public void Find()
    {
        Check.NotNull(FileFound);
        GCHandle hdl = GCHandle.Alloc(_ff.Buffer, GCHandleType.Pinned);
        try
        {
            FileFoundEventArgs args = new FileFoundEventArgs(_ff);
            _ff.BufferAddress = hdl.AddrOfPinnedObject();

            FindFileEx(args, _baseOffset);
        }
        finally
        {
            _ff.BufferAddress = IntPtr.Zero;
            hdl.Free();
        }
    }

    private bool IsWild()
    {
        return (_fpattern.Length == 1 && _fpattern[0] == '*')
               ||
               (_fpattern.Length == 3 && _fpattern[0] == '*' && _fpattern[1] == '.' && _fpattern[2] == '*');
    }

    private void FindFileEx(FileFoundEventArgs args, int slength)
    {
        Kernel32.FINDEX_INFO_LEVELS findInfoLevel = Kernel32.FINDEX_INFO_LEVELS.FindExInfoStandard;
        Kernel32.FINDEX_ADDITIONAL_FLAGS additionalFlags = 0;

        if (Environment.OSVersion.Version.Major >= 6)
        {
            //Ignore short-names
            findInfoLevel = Kernel32.FINDEX_INFO_LEVELS.FindExInfoBasic;
            //Use large fetch table
            additionalFlags = Kernel32.FINDEX_ADDITIONAL_FLAGS.FindFirstExLargeFetch;
        }

        _fpattern.CopyTo(_ff.Buffer, slength);
        _ff.Buffer[slength + _fpattern.Length] = ZERO;

        IntPtr hFile = Kernel32.FindFirstFileEx(
            _ff.BufferAddress,
            findInfoLevel,
            out _ff.Value,
            Kernel32.FINDEX_SEARCH_OPS.FindExSearchNameMatch,
            IntPtr.Zero,
            additionalFlags);

        if ((IntPtr.Size == 4 && hFile.ToInt32() == -1) ||
            (IntPtr.Size == 8 && hFile.ToInt64() == -1L))
        {
            Win32Error(Marshal.GetLastWin32Error());
            return;
        }

        bool traverseDirs = _recursive && IsWild();

        try
        {
            do
            {
                int sposition = slength;
                for (int ix = 0; ix < Kernel32.MAX_PATH && sposition < _ff.Buffer.Length && _ff.Value.cFileName[ix] != 0; ++ix)
                    _ff.Buffer[sposition++] = _ff.Value.cFileName[ix];

                if (sposition == _ff.Buffer.Length)
                    throw new PathTooLongException();

                if (!_ff.Value.IgnoredByName)
                {
                    bool isDirectory = (_ff.Value.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory;

                    if ((_includeFolders && isDirectory) || (_includeFiles && !isDirectory))
                    {
                        args.SetNameOffsets(UncPrefixLength, slength, sposition);
                        FileFound(this, args);
                    }
                    if (traverseDirs && isDirectory)
                    {
                        _ff.Buffer[sposition++] = SLASH;
                        FindFileEx(args, sposition);
                    }
                }
            } while (!args.CancelEnumeration && Kernel32.FindNextFile(hFile, out _ff.Value));
        }
        finally
        {
            Kernel32.FindClose(hFile);
        }

        // Recursive search for patterns other than '*' and '*.*' requires we enum directories again
        if (_recursive && !traverseDirs)
        {
            _ff.Buffer[slength] = '*';
            _ff.Buffer[slength + 1] = ZERO;

            hFile = Kernel32.FindFirstFileEx(
                _ff.BufferAddress,
                findInfoLevel,
                out _ff.Value,
                Kernel32.FINDEX_SEARCH_OPS.FindExSearchNameMatch,
                IntPtr.Zero,
                additionalFlags);

            if ((IntPtr.Size == 4 && hFile.ToInt32() == -1) || (IntPtr.Size == 8 && hFile.ToInt64() == -1L))
            {
                Win32Error(Marshal.GetLastWin32Error());
                return;
            }

            try
            {
                do
                {
                    int sposition = slength;
                    for (int ix = 0; ix < Kernel32.MAX_PATH && sposition < _ff.Buffer.Length && _ff.Value.cFileName[ix] != 0; ++ix)
                        _ff.Buffer[sposition++] = _ff.Value.cFileName[ix];

                    if (sposition == _ff.Buffer.Length)
                        throw new PathTooLongException();

                    if (!_ff.Value.IgnoredByName)
                    {
                        bool isDirectory = (_ff.Value.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory;
                        if (isDirectory)
                        {
                            _ff.Buffer[sposition++] = SLASH;
                            FindFileEx(args, sposition);
                        }
                    }
                } while (!args.CancelEnumeration && Kernel32.FindNextFile(hFile, out _ff.Value));
            }
            finally
            {
                Kernel32.FindClose(hFile);
            }
        }
    }

    private void Win32Error(int errorCode)
    {
        switch (errorCode)
        {
            case Kernel32.ERROR_FILE_NOT_FOUND:
            case Kernel32.ERROR_PATH_NOT_FOUND:
                return;
            case Kernel32.ERROR_ACCESS_DENIED:
                if (!RaiseOnAccessDenied) return;
                goto default;
            default:
                throw new Win32Exception(errorCode);
        }
    }
}

Check Class:

using System;
using System.Reflection;

[System.Diagnostics.DebuggerNonUserCode]
static partial class Check
{
    public static void Assert<TException>(bool condition) where TException : Exception, new()
    {
        if (!condition)
            throw new TException();
    }

    public static void Assert<TException>(bool condition, string message) where TException : Exception, new()
    {
        if (!condition)
        {
            ConstructorInfo ci = typeof(TException).GetConstructor(new Type[] { typeof(string) });
            if (ci != null)
            {
                TException e = (TException)ci.Invoke(new object[] { message });
                throw e;
            }
            throw new TException();
        }
    }

    public delegate Exception ExceptionBuilder();

    public static void Assert(bool condition, ExceptionBuilder fnExceptionBuilder)
    {
        if (!condition)
            throw fnExceptionBuilder();
    }

    public static void Assert<TException>(bool condition, string message, Exception innerException) where TException : Exception, new()
    {
        if (!condition)
        {
            ConstructorInfo ci = typeof(TException).GetConstructor(new Type[] { typeof(string), typeof(Exception) });
            if (ci != null)
            {
                TException e = (TException)ci.Invoke(new object[] { message, innerException });
                throw e;
            }
            throw new TException();
        }
    }

    public static T NotNull<T>(T value)
    {
        if (value == null)
            throw new ArgumentNullException();
        return value;
    }

    public static string NotEmpty(string value)
    {
        if (value == null) throw new ArgumentNullException();
        if (value.Length == 0) throw new ArgumentOutOfRangeException();
        return value;
    }

    public static Guid NotEmpty(Guid value)
    {
        if (value == Guid.Empty)
            throw new ArgumentOutOfRangeException();
        return value;
    }

    public static T NotEmpty<T>(T value) where T : System.Collections.IEnumerable
    {
        if (value == null) throw new ArgumentNullException();
        if (!value.GetEnumerator().MoveNext()) throw new ArgumentOutOfRangeException();
        return value;
    }

    public static void IsEqual<T>(T a, T b) where T : IEquatable<T>
    {
        if (false == a.Equals(b))
            throw new ArgumentException();
    }

    public static void NotEqual<T>(T a, T b) where T : IEquatable<T>
    {
        if (true == a.Equals(b))
            throw new ArgumentException();
    }

    public static T[] ArraySize<T>(T[] value, int min, int max)
    {
        if (value == null)
            throw new ArgumentNullException();
        if (value.Length < min || value.Length > max)
            throw new ArgumentOutOfRangeException();
        return value;
    }

    public static T InRange<T>(T value, T min, T max) where T : IComparable<T>
    {
        if (value == null)
            throw new ArgumentNullException();
        if (value.CompareTo(min) < 0)
            throw new ArgumentOutOfRangeException();
        if (value.CompareTo(max) > 0)
            throw new ArgumentOutOfRangeException();
        return value;
    }

    public static T IsAssignable<T>(object value)
    {
        return (T)IsAssignable(typeof(T), value);
    }

    public static object IsAssignable(Type toType, object fromValue)
    {
        NotNull(toType);
        if (fromValue == null)
        {
            if (toType.IsValueType)
                throw new ArgumentException(string.Format("Can not set value of type {0} to null.", toType));
        }
        else
            IsAssignable(toType, fromValue.GetType());
        return fromValue;
    }

    public static void IsAssignable(Type toType, Type fromType)
    {
        if (!NotNull(toType).IsAssignableFrom(NotNull(fromType)))
            throw new ArgumentException(string.Format("Can not set value of type {0} to a value of type {1}", toType, fromType));
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Are you sure it's the file search that is slow and not your event handlers that get the results? Have you run it with the profiler? \$\endgroup\$
    – t3chb0t
    Aug 29 '16 at 4:11
2
\$\begingroup\$

What exactly are you searching for? The code contains too many "distractions" so I can't spot the actual problem you're trying to solve.

Any reason why you didn't use managed APIs that are already provided, like Systme.IO.Directory.EnumerateFiles() or System.IO.Directory.EnumerateFileSystemEntries()? It ends up calling the native API eventually, so I don't see any major performance gain by reinventing the wheel.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.