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));
}
}