C#使用WIN32API来高效率的遍历文件和目录

时间:2021-03-22 18:11:48

我们有时需要遍历某个目录下的文件和子目录,可以使用System.IO.DirectoryInfo.GetDirectories或GetFiles来获得目录下的所有的文件和子目录,当这个目录下的内容比较多时,这个操作就比较耗时间,有时我们仅仅需要知道某个目录下是否有子目录,这样的操作显然是浪费时间的。此时我们很容易想到三个Win32API函数 FindFirstFile,FindNextFile和FindClose。这三个API搭配使用就能遍历文件和子目录了,而且可以遍历的时候随时中止,避免无谓的操作。

C#中可以使用foreach来遍历某个序列,遍历使用的对象必须实现 System.Collections.IEnumeable接口,而内部调用的遍历器则必须实现System.Collections.IEnumerator , 为了使用方便,我们在使用FindFirstFile等API函数时封装为 IEnumerator,而且实际上是有条件封装的。

这里很多人就会提到C#调用API的执行效率问题,认为应当用C,C++调用API才是正道,使用C#调用则有些鸡肋。但在我个人编程经历中,也有不少调用API的,经验发现其实效率问题不大,可以省略。我只是做常规的运行在PC机上面的软件,CPU通常超过1GHZ,而且无需考虑高实时性和高效率。若过于考虑效率问题会加大软件开发消耗。从工程开发管理方面看是不合理的。我应当解决比较突出的效率问题,不突出的影响不大的效率问题有时间才去解决。使用C#封装Win32API必然会降低执行效率,但是封装后使用方便快捷,综合考虑认为这是正确的。

这里说一下“技术镀金”这个问题,所谓技术镀金就是开发人员在项目软件开发中过于追求技术的完美性,试图在技术上镀上一层完美的金壳,导致软件开发工作量加大,项目时间拉长,有可能导致项目的失败。我吃过“技术镀金”的苦头,现在我内心是追求完美的,但实际开发时经常有意压制追求完美的心思。

现在继续探讨封装大计,本次封装重点在于实现IEnumerator,而IEnumeable只是IEnumerator的一个包装。IEnumerator实现方法 Reset , MoveNext 和属性 Current,Reset方法用于重新设置遍历器,MoveNext用于查找下一个文件或目录,而Current返回当前文件或目录。

这个遍历器还得注意FindClose的调用,必须在遍历完毕没有找到文件或子目录后调用,若不调用该API函数则会造成内存泄漏。

根据上述设计,我写出如下代码,这段代码功能单一,希望有人能用得上

 

///   <summary>
///  文件或目录遍历器,本类型为 FileDirectoryEnumerator 的一个包装
///   </summary>
///   <remarks>
///  
///  编写 袁永福 (  http://www.xdesigner.cn  )2006-12-8
///  
///  以下代码演示使用这个文件目录遍历器
///  
///  FileDirectoryEnumerable e = new FileDirectoryEnumerable();
///  e.SearchPath = @"c:\";
///  e.ReturnStringType = true ;
///  e.SearchPattern = "*.exe";
///  e.SearchDirectory = false ;
///  e.SearchFile = true;
///  foreach (object name in e)
///  {
///      System.Console.WriteLine(name);
///  }
///  System.Console.ReadLine();
///  
/// </remarks>
public   class  FileDirectoryEnumerable : System.Collections.IEnumerable
{
    
private   bool  bolReturnStringType  =   true ;
    
///   <summary>
    
///  是否以字符串方式返回查询结果,若返回true则当前对象返回为字符串,
    
///  否则返回 System.IO.FileInfo或System.IO.DirectoryInfo类型
    
///   </summary>
     public   bool  ReturnStringType
    {
        
get  {  return  bolReturnStringType; }
        
set  { bolReturnStringType  =  value; }
    }

    
private   string  strSearchPattern  =   " * " ;
    
///   <summary>
    
///  文件或目录名的通配符
    
///   </summary>
     public   string  SearchPattern
    {
        
get  {  return  strSearchPattern; }
        
set  { strSearchPattern  =  value; }
    }
    
private   string  strSearchPath  =   null ;
    
///   <summary>
    
///  搜索路径,必须为绝对路径
    
///   </summary>
     public   string  SearchPath
    {
        
get  {  return  strSearchPath; }
        
set  { strSearchPath  =  value; }
    }

    
private   bool  bolSearchForFile  =   true ;
    
///   <summary>
    
///  是否查找文件
    
///   </summary>
     public   bool  SearchForFile
    {
        
get  {  return  bolSearchForFile; }
        
set  { bolSearchForFile  =  value; }
    }
    
private   bool  bolSearchForDirectory  =   true ;
    
///   <summary>
    
///  是否查找子目录
    
///   </summary>
     public   bool  SearchForDirectory
    {
        
get  {  return  bolSearchForDirectory; }
        
set  { bolSearchForDirectory  =  value; }
    }

    
private   bool  bolThrowIOException  =   true ;
    
///   <summary>
    
///  发生IO错误时是否抛出异常
    
///   </summary>
     public   bool  ThrowIOException
    {
        
get  {  return   this .bolThrowIOException; }
        
set  {  this .bolThrowIOException  =  value; }
    }
    
///   <summary>
    
///  返回内置的文件和目录遍历器
    
///   </summary>
    
///   <returns> 遍历器对象 </returns>
     public  System.Collections.IEnumerator GetEnumerator()
    {
        FileDirectoryEnumerator e 
=   new  FileDirectoryEnumerator();
        e.ReturnStringType 
=   this .bolReturnStringType;
        e.SearchForDirectory 
=   this .bolSearchForDirectory;
        e.SearchForFile 
=   this .bolSearchForFile;
        e.SearchPath 
=   this .strSearchPath;
        e.SearchPattern 
=   this .strSearchPattern;
        e.ThrowIOException 
=   this .bolThrowIOException;
        myList.Add(e);
        
return  e;
    }
    
///   <summary>
    
///  关闭对象
    
///   </summary>
     public   void  Close()
    {
        
foreach  (FileDirectoryEnumerator e  in  myList)
        {
            e.Close();
        }
        myList.Clear();
    }

    
private  System.Collections.ArrayList myList  =   new  System.Collections.ArrayList();

}
// public class FileDirectoryEnumerable : System.Collections.IEnumerable

///   <summary>
///  文件和目录的遍历器
///   </summary>
///   <remarks> 本对象为Win32API函数 FindFirstFile , FindNextFile 
///  和 FindClose 的一个包装
///  
///  以下代码演示使用了 FileDirectoryEnumerator 
///  
///  FileDirectoryEnumerator e = new FileDirectoryEnumerator();
///  e.SearchPath = @"c:\";
///  e.Reset();
///  e.ReturnStringType = true ;
///  while (e.MoveNext())
///  {
///      System.Console.WriteLine
///          ( e.LastAccessTime.ToString("yyyy-MM-dd HH:mm:ss")
///          + "   " + e.FileLength + "  \t" + e.Name );
///  }
///  e.Close();
///  System.Console.ReadLine();
///  
///  编写 袁永福 (  http://www.xdesigner.cn  )2006-12-8 </remarks>
public   class  FileDirectoryEnumerator : System.Collections.IEnumerator
{
    
    
#region  表示对象当前状态的数据和属性 **********************************

    
///   <summary>
    
///  当前对象
    
///   </summary>
     private   object  objCurrentObject  =   null ;

    
private   bool  bolIsEmpty  =   false ;
    
///   <summary>
    
///  该目录为空
    
///   </summary>
     public   bool  IsEmpty
    {
        
get  {  return  bolIsEmpty; }
    }
    
private   int  intSearchedCount  =   0 ;
    
///   <summary>
    
///  已找到的对象的个数
    
///   </summary>
     public   int  SearchedCount
    {
        
get  {  return  intSearchedCount; }
    }
    
private   bool  bolIsFile  =   true ;
    
///   <summary>
    
///  当前对象是否为文件,若为true则当前对象为文件,否则为目录
    
///   </summary>
     public   bool  IsFile
    {
        
get  {  return  bolIsFile; }
    }
    
private   int  intLastErrorCode  =   0 ;
    
///   <summary>
    
///  最后一次操作的Win32错误代码
    
///   </summary>
     public   int  LastErrorCode
    {
        
get  {  return  intLastErrorCode; }
    }
    
///   <summary>
    
///  当前对象的名称
    
///   </summary>
     public   string  Name
    {
        
get
        {
            
if  ( this .objCurrentObject  !=   null )
            {
                
if  (objCurrentObject  is   string )
                    
return  ( string ) this .objCurrentObject;
                
else
                    
return  ((System.IO.FileSystemInfo) this .objCurrentObject).Name;
            }
            
return   null ;
        }
    }
    
///   <summary>
    
///  当前对象属性
    
///   </summary>
     public  System.IO.FileAttributes Attributes
    {
        
get  {  return  (System.IO.FileAttributes)myData.dwFileAttributes; }
    }
    
///   <summary>
    
///  当前对象创建时间
    
///   </summary>
     public  System.DateTime CreationTime
    {
        
get
        {
            
long  time  =  ToLong(myData.ftCreationTime_dwHighDateTime, myData.ftCreationTime_dwLowDateTime);
            System.DateTime dtm 
=  System.DateTime.FromFileTimeUtc(time);
            
return  dtm.ToLocalTime();
        }
    }
    
///   <summary>
    
///  当前对象最后访问时间
    
///   </summary>
     public  System.DateTime LastAccessTime
    {
        
get
        {
            
long  time  =  ToLong(myData.ftLastAccessTime_dwHighDateTime, myData.ftLastAccessTime_dwLowDateTime);
            System.DateTime dtm 
=  System.DateTime.FromFileTimeUtc(time);
            
return  dtm.ToLocalTime();
        }
    }
    
///   <summary>
    
///  当前对象最后保存时间
    
///   </summary>
     public  System.DateTime LastWriteTime
    {
        
get
        {
            
long  time  =  ToLong(myData.ftLastWriteTime_dwHighDateTime, myData.ftLastWriteTime_dwLowDateTime);
            System.DateTime dtm 
=  System.DateTime.FromFileTimeUtc(time);
            
return  dtm.ToLocalTime();
        }
    }
    
///   <summary>
    
///  当前文件长度,若为当前对象为文件则返回文件长度,若当前对象为目录则返回0
    
///   </summary>
     public   long  FileLength
    {
        
get
        {
            
if  ( this .bolIsFile)
                
return  ToLong(myData.nFileSizeHigh, myData.nFileSizeLow);
            
else
                
return   0 ;
        }
    }

    
#endregion

    
#region  控制对象特性的一些属性 ****************************************

    
private   bool  bolThrowIOException  =   true ;
    
///   <summary>
    
///  发生IO错误时是否抛出异常
    
///   </summary>
     public   bool  ThrowIOException
    {
        
get  {  return   this .bolThrowIOException; }
        
set  {  this .bolThrowIOException  =  value; }
    }
    
private   bool  bolReturnStringType  =   true ;
    
///   <summary>
    
///  是否以字符串方式返回查询结果,若返回true则当前对象返回为字符串,
    
///  否则返回 System.IO.FileInfo或System.IO.DirectoryInfo类型
    
///   </summary>
     public   bool  ReturnStringType
    {
        
get  {  return  bolReturnStringType; }
        
set  { bolReturnStringType  =  value; }
    }
    
    
private   string  strSearchPattern  =   " * " ;
    
///   <summary>
    
///  要匹配的文件或目录名,支持通配符
    
///   </summary>
     public   string  SearchPattern
    {
        
get  {  return  strSearchPattern; }
        
set  { strSearchPattern  =  value; }
    }
    
private   string  strSearchPath  =   null ;
    
///   <summary>
    
///  搜索的父目录,必须为绝对路径,不得有通配符,该目录必须存在
    
///   </summary>
     public   string  SearchPath
    {
        
get  {  return  strSearchPath; }
        
set  { strSearchPath  =  value; }
    }

    
private   bool  bolSearchForFile  =   true ;
    
///   <summary>
    
///  是否查找文件
    
///   </summary>
     public   bool  SearchForFile
    {
        
get  {  return  bolSearchForFile; }
        
set  { bolSearchForFile  =  value; }
    }
    
private   bool  bolSearchForDirectory  =   true ;
    
///   <summary>
    
///  是否查找子目录
    
///   </summary>
     public   bool  SearchForDirectory
    {
        
get  {  return  bolSearchForDirectory; }
        
set  { bolSearchForDirectory  =  value; }
    }

    
#endregion

    
///   <summary>
    
///  关闭对象,停止搜索
    
///   </summary>
     public   void  Close()
    {
        
this .CloseHandler();
    }

    
#region  IEnumerator 成员 **********************************************

    
///   <summary>
    
///  返回当前对象
    
///   </summary>
     public   object  Current
    {
        
get  {  return  objCurrentObject ; }
    }
    
///   <summary>
    
///  找到下一个文件或目录
    
///   </summary>
    
///   <returns> 操作是否成功 </returns>
     public   bool  MoveNext()
    {
        
bool  success  =   false ;
        
while  ( true )
        {
            
if  ( this .bolStartSearchFlag)
                success 
=   this .SearchNext();
            
else
                success 
=   this .StartSearch();
            
if  (success)
            {
                
if  ( this .UpdateCurrentObject())
                    
return   true ;
            }
            
else
            {
                
this .objCurrentObject  =   null ;
                
return   false ;
            }
        }
    }

    
///   <summary>
    
///  重新设置对象
    
///   </summary>
     public   void  Reset()
    {
        
if  ( this .strSearchPath  ==   null )
            
throw   new  System.ArgumentNullException( " SearchPath can not null " );
        
if  ( this .strSearchPattern  ==   null   ||   this .strSearchPattern.Length  ==   0 )
            
this .strSearchPattern  =   " * " ;

        
this .intSearchedCount  =   0 ;
        
this .objCurrentObject  =   null ;
        
this .CloseHandler();
        
this .bolStartSearchFlag  =   false ;
        
this .bolIsEmpty  =   false ;
        
this .intLastErrorCode  =   0 ;
    }

    
#endregion

    
#region  声明WIN32API函数以及结构 **************************************

    [Serializable,
    System.Runtime.InteropServices.StructLayout
        (System.Runtime.InteropServices.LayoutKind.Sequential,
        CharSet 
=  System.Runtime.InteropServices.CharSet.Auto
        ),
    System.Runtime.InteropServices.BestFitMapping(
false )]
    
private   struct  WIN32_FIND_DATA
    {
        
public   int  dwFileAttributes;
        
public   int  ftCreationTime_dwLowDateTime;
        
public   int  ftCreationTime_dwHighDateTime;
        
public   int  ftLastAccessTime_dwLowDateTime;
        
public   int  ftLastAccessTime_dwHighDateTime;
        
public   int  ftLastWriteTime_dwLowDateTime;
        
public   int  ftLastWriteTime_dwHighDateTime;
        
public   int  nFileSizeHigh;
        
public   int  nFileSizeLow;
        
public   int  dwReserved0;
        
public   int  dwReserved1;
        [System.Runtime.InteropServices.MarshalAs
            (System.Runtime.InteropServices.UnmanagedType.ByValTStr,
            SizeConst 
=   260 )]
        
public   string  cFileName;
        [System.Runtime.InteropServices.MarshalAs
            (System.Runtime.InteropServices.UnmanagedType.ByValTStr,
            SizeConst 
=   14 )]
        
public   string  cAlternateFileName;
    }

    [System.Runtime.InteropServices.DllImport
        (
" kernel32.dll " ,
        CharSet 
=  System.Runtime.InteropServices.CharSet.Auto,
        SetLastError 
=   true )]
    
private   static   extern  IntPtr FindFirstFile( string  pFileName,  ref  WIN32_FIND_DATA pFindFileData);

    [System.Runtime.InteropServices.DllImport
        (
" kernel32.dll " ,
       CharSet 
=  System.Runtime.InteropServices.CharSet.Auto,
        SetLastError 
=   true )]
    
private   static   extern   bool  FindNextFile(IntPtr hndFindFile,  ref  WIN32_FIND_DATA lpFindFileData);

    [System.Runtime.InteropServices.DllImport(
" kernel32.dll " , SetLastError  =   true )]
    
private   static   extern   bool  FindClose(IntPtr hndFindFile);

    
private   static   long  ToLong(  int  height ,  int  low)
    {
        
long  v  =  (  uint  ) height ;
        v 
=  v  <<   0x20 ;
        v 
=  v  |  ( (  uint  )low );
        
return  v;
    }

    
private   static   void  WinIOError( int  errorCode,  string  str)
    {
        
switch  (errorCode)
        {
            
case   80 :
                
throw   new  System.IO.IOException( " IO_FileExists : "   +  str);
            
case   0x57 :
                
throw   new  System.IO.IOException( " IOError: "   +  MakeHRFromErrorCode(errorCode));
            
case   0xce :
                
throw   new  System.IO.PathTooLongException( " PathTooLong: "   +  str );
            
case   2 :
                
throw   new  System.IO.FileNotFoundException( " FileNotFound: "   +  str);
            
case   3 :
                
throw   new  System.IO.DirectoryNotFoundException( " PathNotFound: "   +  str);
            
case   5 :
                
throw   new  UnauthorizedAccessException( " UnauthorizedAccess: "   +  str);
            
case   0x20 :
                
throw   new  System.IO.IOException( " IO_SharingViolation: "   +  str);
        }
        
throw   new  System.IO.IOException( " IOError: "   +  MakeHRFromErrorCode(errorCode));
    }

    
private   static   int  MakeHRFromErrorCode( int  errorCode)
    {
        
return  ( - 2147024896   |  errorCode);
    }

    
#endregion

    
#region  内部代码群 ****************************************************

    
private   static   readonly  IntPtr INVALID_HANDLE_VALUE  =   new  IntPtr( - 1 );
    
///   <summary>
    
///  查找处理的底层句柄
    
///   </summary>
     private  System.IntPtr intSearchHandler  =  INVALID_HANDLE_VALUE;

    
private  WIN32_FIND_DATA myData  =   new  WIN32_FIND_DATA();
    
///   <summary>
    
///  开始搜索标志
    
///   </summary>
     private   bool  bolStartSearchFlag  =   false ;
    
///   <summary>
    
///  关闭内部句柄
    
///   </summary>
     private   void  CloseHandler()
    {
        
if  ( this .intSearchHandler  !=  INVALID_HANDLE_VALUE)
        {
            FindClose(
this .intSearchHandler);
            
this .intSearchHandler  =  INVALID_HANDLE_VALUE;
        }
    }
    
///   <summary>
    
///  开始搜索
    
///   </summary>
    
///   <returns> 操作是否成功 </returns>
     private   bool  StartSearch()
    {
        bolStartSearchFlag 
=   true ;
        bolIsEmpty 
=   false ;
        objCurrentObject 
=   null ;
        intLastErrorCode 
=   0 ;

        
string  strPath  =  System.IO.Path.Combine(strSearchPath,  this .strSearchPattern);
        
this .CloseHandler();
        intSearchHandler 
=  FindFirstFile(strPath,  ref  myData);
        
if  (intSearchHandler  ==  INVALID_HANDLE_VALUE)
        {
            intLastErrorCode 
=  System.Runtime.InteropServices.Marshal.GetLastWin32Error();
            
if  (intLastErrorCode  ==   2 )
            {
                bolIsEmpty 
=   true ;
                
return   false ;
            }
            
if this .bolThrowIOException )
                WinIOError( intLastErrorCode , strSearchPath);
            
else
                
return   false ;
        }
        
return   true ;
    }
    
///   <summary>
    
///  搜索下一个
    
///   </summary>
    
///   <returns> 操作是否成功 </returns>
     private   bool  SearchNext()
    {
        
if  (bolStartSearchFlag  ==   false )
            
return   false ;
        
if  (bolIsEmpty)
            
return   false ;
        
if  (intSearchHandler  ==  INVALID_HANDLE_VALUE)
            
return   false ;
        intLastErrorCode 
=   0  ;
        
if  (FindNextFile(intSearchHandler,  ref  myData)  ==   false )
        {
            intLastErrorCode 
=  System.Runtime.InteropServices.Marshal.GetLastWin32Error();
            
this .CloseHandler();
            
if  (intLastErrorCode  !=   0   &&  intLastErrorCode  !=   0x12 )
            {
                
if  ( this .bolThrowIOException)
                    WinIOError(intLastErrorCode , strSearchPath);
                
else
                    
return   false ;
            }
            
return   false ;
        }
        
return   true ;
    }
// private bool SearchNext()

    
///   <summary>
    
///  更新当前对象
    
///   </summary>
    
///   <returns> 操作是否成功 </returns>
     private   bool  UpdateCurrentObject()
    {
        
if  (intSearchHandler  ==  INVALID_HANDLE_VALUE)
            
return   false ;
        
bool  Result  =   false ;
        
this .objCurrentObject  =   null ;
        
if  ((myData.dwFileAttributes  &   0x10 ==   0 )
        {
            
//  当前对象为文件
             this .bolIsFile  =   true ;
            
if  ( this .bolSearchForFile)
                Result 
=   true ;
        }
        
else  
        {
            
//  当前对象为目录
             this .bolIsFile  =   false ;
            
if  ( this .bolSearchForDirectory)
            {
                
if  (myData.cFileName  ==   " . "   ||  myData.cFileName  ==   " .. " )
                    Result 
=   false ;
                
else
                    Result 
=   true ;
            }
        }
        
if  (Result)
        {
            
if  ( this .bolReturnStringType)
                
this .objCurrentObject  =  myData.cFileName;
            
else
            {
                
string  p  =  System.IO.Path.Combine( this .strSearchPath, myData.cFileName);
                
if  ( this .bolIsFile)
                {
                    
this .objCurrentObject  =   new  System.IO.FileInfo(p);
                }
                
else
                {
                    
this .objCurrentObject  =   new  System.IO.DirectoryInfo(p);
                }
            }
            
this .intSearchedCount ++ ;
        }
        
return  Result;
    }
// private bool UpdateCurrentObject()

    
#endregion

}
// public class FileDirectoryEnumerator : System.Collections.IEnumerator

 

 

袁永福 ( http://www.xdesigner.cn )2007