65.9K
CodeProject 正在变化。 阅读更多。
Home

文件系统搜索引擎

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.08/5 (8投票s)

2009年1月5日

CPOL

13分钟阅读

viewsIcon

57940

downloadIcon

2451

一个通用的文件系统搜索引擎。

引言

我曾几次需要一段代码来搜索驱动器中的文件。我目前正考虑编写一个WCF服务,部署在我所有的机器上,以方便管理。它公开的方法之一将检索主机机器驱动器上所有文件和文件夹的完整列表。我的想法是将所有这些文件填充到数据库中,并编写一个工具来分析这些数据。我目前在线存储的数据量略多于一太字节,我知道有很多重复的文件,所以……

最近,我已经写了一段代码来查找特定的文件和文件夹以便删除它们。这是一个我写来清理开发文件夹的小工具,我经常需要这样做,因为我经常复制/交换代码。它会搜索诸如“.svn”、“bin”、“obj”、“Backup”之类的文件夹并删除它们,这意味着删除它们的全部内容。其他文件夹则会搜索名称类似于“*.pdb”、“*.suo”、“Thumbs.db”等文件,这些文件也会被删除。这类引擎与其他引擎不同之处在于,它必须能够搜索某些文件夹以进行特定匹配,并且它必须能够直接获取其他文件夹中的所有内容。

除此之外,我还在畅想一个未来的“行计数器”工具,用于计算项目中的代码行数,这大概需要获取所有“.cs”和“*.vb”文件的集合。这是一个简单的匹配文件搜索,但最终必须对结果进行处理,因为文件需要被读取。

回到我的WCF服务想法,即使只是将所有文件和文件夹列在我的数据库中,我也已经很满足了,但如果我能避免存储不重要的文件和文件夹,例如“Windows”文件夹下的所有内容,“Program Files”文件夹下的所有内容或其他任何地方的特定文件,那将是更棒的。我想到了可以为我之前的实现添加一项新功能,即引擎应该能够包含或排除匹配项。我希望能够排除特定文件甚至整个文件夹,并且我真的很想重新利用我现有的代码。

当我进一步思考我的想法时,问题才真正开始出现。理想情况下,搜索引擎应该能够考虑除文件名/文件夹名之外的其他因素,而我最终想要的不仅仅是排除“Windows”或“Program Files”文件夹。我想,增加排除具有一个或多个特定属性的文件的可能性肯定能解决问题,我甚至可以排除系统/隐藏文件等。

但是,所有这些都意味着编写一个新的搜索引擎。如果明天我需要能够查看文件内容或分析我今天甚至想不到的文件/文件夹的任何其他方面,我可能会被迫重写或转换我的引擎。所以我觉得是时候考虑通用性了。

如果我们从通用性的角度考虑,搜索引擎不必知道如何处理对象。对于读取文件内容的桌面搜索引擎来说,我们需要为各种文件格式提供不同的读取器,但搜索引擎的任务是定位文件或文件夹并对这些对象应用搜索过滤器,而不是包含这些过滤器的知识。此外,将搜索引擎与可能想到的众多各种文件类型耦合是不理想的。

所以,如果我们从搜索过滤器的角度考虑,我们可以将搜索过滤器视为一个对象,它能够对文件系统对象执行某些操作,并返回一个指示文件系统对象是否满足特定标准的布尔值。“操作”可以是在名称、属性,或者如果是文件则读取文件内容。文件系统对象只是文件或文件夹。

例如,为了读取文件,我们可以考虑一个搜索过滤器,它是一个通用的读取器,能够为它正在查看的文件类型请求一个“提供程序”,该提供程序可以由插件系统提供。这种“提供程序”通常是需要购买的,除非你想在专有二进制文件格式方面积累知识,而这并不是我文章的范畴,所以我实现了一个简单的搜索过滤器,它只查找ASCII字符串。我只是想说明这个“文件系统搜索引擎”足够通用,可以作为桌面搜索引擎使用,但这并不是我的重点,我实现了我真正需要的一些过滤器。当然,我并没有将我的代码与“真正的”桌面搜索引擎进行比较。

简而言之,搜索引擎

该引擎具有以下功能

  • 文件夹和文件分开处理。
  • 可以组合多个过滤器。
  • 内省逻辑可控:你可以请求特定根文件夹下的所有文件和文件夹列表,包括或排除子文件夹下的整个树,但你也可以只查看匹配特定过滤器的子文件夹;当找到匹配的文件夹时,你可以使用相同的搜索过滤器,但也可以指定匹配文件夹的全部内容都应被保留。这在“批量删除”场景中可能很有用,你只想完全删除符合特定标准的文件夹,以及在不应完全删除的文件夹中符合其他标准的某些其他文件。
  • 该引擎能够通过通配符或正则表达式按名称查找文件和文件夹。
  • 该引擎能够查找具有(或不具有)一个或多个System.IO.FileAttributes属性的文件和文件夹。
  • 该引擎能够查找包含特定ASCII字符串的文件。
  • 该引擎可以通过添加其他过滤器来扩展,以允许几乎任何其他类型的搜索。
  • 正面匹配可用于包含或排除。

FileSystemInfoNameSearchFilter类使用由Reinux编写的优秀的Wildcard类来比较带通配符或正则表达式的文件/文件夹名称。非常感谢Reinux提供的这个优雅的解决方案。

搜索引擎详细介绍

该引擎由13个类组成,构建在4个接口之上

  • IDirectoryBrowser
  • ISearchFilter<T>
  • IDirectoryBrowserSearchFilters
  • IDirectoryBrowsingResults

简而言之,该引擎包含一个实现了IDirectoryBrowser的类,即DirectoryBrowser类。该类使用实现了ISearchFilter<T>的对象来构建过滤后的文件夹和文件列表,并将其封装在一个实现了IDirectoryBrowsingResults的对象中,即DirectoryBrowsingResults类。

DirectoryBrowser可以使用IDirectoryBrowserSearchFilters参数(由DirectoryBrowserSearchFilters类实现);这个接口为实现了ISearchFilter<FileInfo>的对象定义了一个属性,为实现了ISearchFilter<DirectoryInfo>的对象定义了另一个属性,这就是我们如何为文件夹和文件传递单独的过滤器。

DirectoryBrowsingResults类封装了一个引用,该引用指向创建结果的DirectoryBrowser对象,以及作为两个独立集合的文件和文件夹。DirectoryBrowser包含一个在处理过程中可能发生的潜在异常列表,因此,当您使用DirectoryBrowser的静态方法之一而不是自己实例化它时,此引用很有用。

ISearchFilter<T>由抽象类SearchFilter<T>实现,它只是一个包含“装饰器”模式实现的样板代码的基类,以及一个模板方法模式实现,以提供派生类型的算法一致性。我选择实现“装饰器”模式来“堆叠”搜索过滤器,允许只将一个“装饰器”搜索过滤器传递给浏览器对象,并集中结果组合逻辑以及一些异常处理。我只是想这样实现,但也可以是其他方式:我们可以将过滤器列表传递给浏览器,我们可以创建一个新类来封装要维护的功能并公开多个过滤器,我们可以做许多其他优雅的事情。

在此SearchFilter<T>基类的基础上,三个类提供了内置搜索过滤器

  • FileSystemInfoNameSearchFilter可以比较System.IO.FileSystemInfo 对象(可以是System.IO.FileInfoSystem.IO.DirectoryInfo对象)的名称与通配符列表或正则表达式列表进行匹配,以查找文件。
  • FileSystemInfoAttributeSearchFilter略有不同,因为它可以比较System.IO.FileAttributesSystem.IO.FileSystemInfo对象的Attributes属性。
  • FileInfoContentsSearchFilter:可以查看文件内容并搜索特定的ASCII字符串。这个第三个过滤器纯粹是为了说明装饰过的ISearchFilter<T>实现的理念和灵活性,它非常基础和有限,而且性能可能会很差。

可以禁止或允许分析子文件夹,也可以只允许分析匹配提供的SearchFilter<DirectoryInfo>的子文件夹;匹配文件夹的内容可以完整地添加到结果中,也可以使用搜索过滤器正常搜索。所有这些都在DirectoryBrowserSearchBehavior类中通过BrowserModeDirectoryMatchRules属性进行定义。

逐个展示每个类会有点繁琐。因此,我将重点介绍主要的关注点。

正如你可能已经猜到的,所有的魔术都发生在DirectoryBrowser类中。在根文件夹级别,BrowseDirectory()方法实际上只有两个关注点:确定是否应该检查其子文件夹(以及如何检查),以及提供其自身的直接子文件和文件夹列表。

public IDirectoryBrowsingResults BrowseDirectory(
    DirectoryBrowserSearchBehavior searchBehavior,
    IDirectoryBrowserSearchFilters searchFilters)
{
    DirectoryBrowserSearchBehavior behavior = 
        searchBehavior ?? new DirectoryBrowserSearchBehavior();

    this.Results.ClearResults();
    this.ExceptionsList.Clear();
    this.HasBrowsed = true;

    if (this.RootFolderExists)
    {
        IEnumerable<DirectoryInfo> allDirectories;
        IEnumerable<DirectoryInfo> matchingDirectories =
            this.GetDirectories(searchFilters);
        IEnumerable<FileInfo> files = this.GetFiles(searchFilters);

        if (behavior.BrowserMode == DirectoryBrowserModes.SearchOnlyMatchingDirectories)
        {
            if (behavior.DirectoryMatchRules ==
                DirectoryMatchRules.IncludeCompleteDirectoryContents)
            {
                this.Results.AddResults(this.BrowseChildDirectories(matchingDirectories,
                    searchBehavior, null));
            }
            else
            {
                this.Results.AddResults(this.BrowseChildDirectories(matchingDirectories,
                    searchBehavior, searchFilters));
            }
        }
        else if (behavior.BrowserMode == DirectoryBrowserModes.SearchAllChildDirectories)
        {
            allDirectories = this.GetDirectories(null);
            if (behavior.DirectoryMatchRules ==
                DirectoryMatchRules.IncludeCompleteDirectoryContents)
            {
                // Search all directories except the matching ones using the filters
                this.Results.AddResults(this.BrowseChildDirectories(
                    allDirectories.Except(matchingDirectories,
                    new DirectoryInfoEqualityComparerByFullName()),
                    searchBehavior, searchFilters));
                // Then search the matching directories without filter to include
                // their complete contents
                this.Results.AddResults(this.BrowseChildDirectories(
                    matchingDirectories, searchBehavior, null));
            }
            else
            {
                // Search all directories using the filters
                this.Results.AddResults(this.BrowseChildDirectories(allDirectories,
                    searchBehavior, searchFilters));
            }
        }

        this.Results.AddDirectories(matchingDirectories);
        this.Results.AddFiles(files);
    }

    return this.Results;
}

当需要搜索子目录时,操作无非是为每个子文件夹实例化一个新的DirectoryBrowser对象,运行新的搜索,并将结果附加到我们自己的结果中。

private IDirectoryBrowsingResults BrowseChildDirectories(
    IEnumerable<DirectoryInfo> directories,
    DirectoryBrowserSearchBehavior searchBehavior,
    IDirectoryBrowserSearchFilters searchFilters)
{
    IDirectoryBrowsingResults results = new DirectoryBrowsingResults(this,
        this.RootFolder);

    if (directories != null)
    {
        foreach (DirectoryInfo dir in directories)
        {
            IDirectoryBrowser browser = CreateBrowser(dir.FullName);
            results.AddResults(browser.BrowseDirectory(searchBehavior, searchFilters));
            if (browser.HasErrors)
            {
                this.HandleExceptions(browser.Errors);
            }
        }
    }

    return results;
}

收集文件夹子文件夹的操作是一个单独的职责。我们委托给一个使用lambda表达式的LINQ扩展方法进行过滤,该lambda表达式本身也委托给搜索过滤器来测试匹配。

protected IEnumerable<DirectoryInfo> GetDirectories(
    IDirectoryBrowserSearchFilters searchFilters)
{
    try
    {
        if (searchFilters == null || searchFilters.DirectoryFilter == null || 
            !searchFilters.DirectoryFilter.ContainsFilterCriterias)
            return this.RootFolderInfo.GetDirectories();

        // We provide an error handler to the search filter Matches() method
        // to prevent a single Matches() instruction to crash the complete predicate.
        // We would otherwise loose the complete set of results for the folder.
        // The search filter is expected to swallow exceptions, but also to make
        // use of the delegate to register errors.
        return this.RootFolderInfo.GetDirectories().Where(dirInfo => 
            searchFilters.DirectoryFilter.Matches(dirInfo, this.HandleException));
    }
    catch (Exception ex)
    {
        // Probably security exception due to an unauthorized access
        // to the folder.
        this.HandleException(ex);
        return new List<DirectoryInfo>();
    }
}

对于文件,自然也大致相同。

protected IEnumerable<FileInfo> GetFiles(IDirectoryBrowserSearchFilters searchFilters)
{
    try
    {
        if (searchFilters == null || searchFilters.FileFilter == null || 
            !searchFilters.FileFilter.ContainsFilterCriterias)
            return this.RootFolderInfo.GetFiles();

        // We provide an error handler to the search filter Matches() method
        // to prevent a single Matches() instruction to crash the complete predicate.
        // We would otherwise loose the complete set of results for the folder.
        // The search filter is expected to swallow exceptions, but also to make use
        // of the delegate to register errors.
        return this.RootFolderInfo.GetFiles().Where(fileInfo => 
            searchFilters.FileFilter.Matches(fileInfo, this.HandleException));
    }
    catch (Exception ex)
    {
        // Probably security exception due to an unauthorized access
        // to the folder.
        this.HandleException(ex);
        return new List<FileInfo>();
    }
}

除了DirectoryBrowser类之外,另一个值得关注的类是SearchFilter<T>抽象类,用于“装饰器”模式实现。正如你所见,“装饰器”模式实现首先以某种方式定义一个实现相同接口的“内部”对象,即一个“被装饰的ISearchFilter<T>”。在这里,我选择使用第二个构造函数。

public abstract class SearchFilter<T>
    : ISearchFilter<T>
{

    protected SearchFilter()
        : this(null)
    {
    }


    protected SearchFilter(ISearchFilter<T> innerFilter)
    {
        this.InnerFilter = innerFilter;
    }

}

下一步是实现ISearchFilter<T>接口定义的Matches()方法。这就是我们执行被装饰的ISearchFilter<T>的代码(如果有的话)。这也是我们控制如何实际组合结果的地方。如果我使用了“装饰器”模式来处理DirectoryBrowser对象本身,我将在这里附加DirectoryBrowsingResults

为了确保被装饰的过滤器总是被调用,逻辑骨架必须存在于一个非虚拟方法中;我们这里调用的IsMatch()方法实际上是一个抽象方法。派生自SearchFilter<T>的搜索过滤器类只需重写这个IsMatch()方法并提供至少类似的构造函数,所有其他工作都通过这个基类安全地完成。

public Boolean Matches(T item, ExceptionHandler errorHandler)
{

    if (this.HasInnerFilter && !this.InnerFilter.Matches(item, errorHandler))
    {
        return false;
    }

    try
    {
        return this.IsMatch(item);
    }
    catch (Exception ex)
    {
        if (errorHandler != null)
        {
            errorHandler(ex);
        }
        return false;
    }

}

这是FileSystemInfoNameSearchFilter内置过滤器中的实际实现。T类型的对象将是System.IO.FileSystemInfo对象(可能是System.IO.FileInfoSystem.IO.DirectoryInfo对象),你可以根据自己的选择编写算法来保留或不保留该对象。请注意,这里使用了Reinux提供的优秀的Wildcard类。

protected override sealed Boolean IsMatch(T item)
{
    Boolean result = false;
    String text = item == null ? null : item.Name;

    if (item == null || String.IsNullOrEmpty(text))
        return false;

    if (! this.ContainsFilterCriterias)
        return true;

    foreach (String pattern in this.Patterns)
    {
        if (this.CreateMatchObject(pattern).IsMatch(text))
        {
            result = true;
            break;
        }
    }

    return (this.SearchOption == SearchOptions.IncludeMatches ? result : !result);
}

private Regex CreateMatchObject(String pattern)
{
    if (this.MatchMode == FileSystemInfoNameMatchMode.UseRegEx)
        return new Regex(pattern, RegexOptions.IgnoreCase);

    return new Wildcard(pattern, RegexOptions.IgnoreCase);
}

最后,来自DirectoryInfoEqualityComparerByName类的一个快速提示:如果你不想编写一个生成哈希码的算法,你可以随时将此任务委托给String对象,并专注于创建一个唯一标识对象的字符串。

public int GetHashCode(DirectoryInfo obj)
{
    if (obj != null)
        return obj.FullName.GetHashCode();

    return String.Empty.GetHashCode();
}

Using the Code

定义一个复杂的搜索可能有些繁琐。通常,需要实例化一个DirectoryBrowser对象。然后你可能需要为文件和文件夹定义并组合多个过滤器,这可能需要先创建几个模式列表或另一个“复杂”的结构。然后你必须创建一个DirectoryBrowserSearchFilters对象来存储你的过滤器,最后你调用适当的BrowseDirectory()方法并提供DirectoryBrowserSearchFilters对象。

只有当你确实需要做所有这些事情时,这才有意义,但大多数时候你并不需要。因此,在DirectoryBrowserDirectoryBrowserSearchFilters类中添加了一些静态的“快捷方式”/“助手”方法。DirectoryBrowserSearchFilters类封装了一些功能,以方便创建内置过滤器(例如,接受逗号分隔的字符串作为模式,并为你构建列表),而DirectoryBrowser类则通过提供多个重载来反映这些快捷方式,这些重载可以使用最少的参数为你创建过滤器。

下面的四个示例说明了简单和更复杂的搜索。随着复杂度的提高,你可以从这些示例中看到,当我们被迫自己构建自定义过滤器时,后台发生了什么,过滤器是如何自动构建的。我非常清楚,我本可以添加更多的重载来使其更容易。我没有为搜索行为做更多事情的原因是,它不会真正提高可读性,我仍然需要将BrowseDirectory()方法的参数放在多行上,因为枚举器的名称很长。

最简单的情况,无过滤器

public IDirectoryBrowsingResults ListAllFilesAndFolders()
{
    DirectoryBrowserSearchBehavior searchBehavior;

    searchBehavior = new DirectoryBrowserSearchBehavior
                         {
                             BrowserMode = 
                             DirectoryBrowserModes.SearchAllChildDirectories,
                             DirectoryMatchRules = 
                                 DirectoryMatchRules.IncludeCompleteDirectoryContents
                         };

    // As we do not have any search criteria, we can call the shortest static shortcut
    return DirectoryBrowser.BrowseDirectory(@"C:\", searchBehavior);
}

基于通配符的简单搜索

/// This method returns for the C:\ drive all files with a '.pdb' or a '.dll' extension,
/// and all folders called 'bin', 'obj' or '.svn' + the complete files and folders
/// hierarchy under these matching folders. Such search can be useful when you
/// write a piece of code to perform some automatic cleaning.
public IDirectoryBrowsingResults SimpleMatches()
{
    DirectoryBrowserSearchBehavior searchBehavior;

    searchBehavior = new DirectoryBrowserSearchBehavior
                         {
                             BrowserMode = 
                                 DirectoryBrowserModes.SearchAllChildDirectories,
                             DirectoryMatchRules =
                                 DirectoryMatchRules.IncludeCompleteDirectoryContents
                         };

    // As we have simple requirements (one built-in wildcards filter for files
    // and one built-in wildcards filter for folders), we can use one of the
    // static methods and forget about creating filters ourselves.
    return DirectoryBrowser.BrowseDirectory(@"C:\", searchBehavior,
                                                "bin,obj,.svn",
                                                SearchOptions.IncludeMatches,
                                                "*.pdb,*.dll",
                                                SearchOptions.IncludeMatches);
}

基于文件内容而非文件名或属性的搜索

/// This method searches the C:\ drive for a file containing the string
/// "I love C#". The search is not done on child folders and  the engine
/// is instructed not to put direct child folders in the results because we want only
/// files. We could perfectly skip the instantiation of a filter for directories,
/// as a result we would have the list of direct child folders in the C:\ drive
/// as a part of the results but we are not forced to look at them because
/// files and folders are stored separately in the results.
public IDirectoryBrowsingResults SearchFilesContents()
{
    DirectoryBrowserSearchBehavior searchBehavior;
    DirectoryBrowserSearchFilters searchFilters;
    FileSystemInfoNameSearchFilter<DirectoryInfo> directoryFilter;
    FileInfoContentsSearchFilter fileFilter;

    // Define a search behavior
    searchBehavior = new DirectoryBrowserSearchBehavior
    {
        BrowserMode = DirectoryBrowserModes.ExcludeChildDirectories,
        DirectoryMatchRules = DirectoryMatchRules.IncludeMatchingDirectoryContents
    };

    // Define a filter to forbid results for folders
    directoryFilter = new FileSystemInfoNameSearchFilter<DirectoryInfo>
                          { SearchOption = SearchOptions.ExcludeMatches };
    directoryFilter.Patterns.Add("*");

    // Define a filter to include any file that would contain de string "I love C#"
    fileFilter = new FileInfoContentsSearchFilter
                     {
                         SearchOption = SearchOptions.IncludeMatches,
                         TextToFind = "I love C#",
                     };

    // Then, instantiate the DirectoryBrowserSearchFilters required by the
    // IDirectoryBrowser object using our folder filter and file filter.
    searchFilters = new DirectoryBrowserSearchFilters(directoryFilter, fileFilter);

    // Call a static method of the DirectoryBrowser instead of instantiating an
    // DirectoryBrowser ourselves. The IDirectoryBrowsingResults will contain a
    // reference to the IDirectoryBrowser object that processed
    // the search, which can be useful to access the list of eventual errors that
    // may have occured during processing.
    return DirectoryBrowser.BrowseDirectory(@"C:\", searchBehavior, searchFilters);
}

最后,一个结合了多个过滤器的完全自定义搜索

/// This method makes use of multiple search filters to obtain fine-grained results.
/// Here we ask to search the complete C:\ drive; we want the search engine to look
/// only inside folders that are not called "bin", "obj", or ".svn", and we want all files
/// as long as their name does not end with ".config", and as long as they do not have
/// the System or Hidden attribute. We do not use a single FileAttributes filter because
/// we do not restrict our exclusion to files having BOTH the System and Hidden
/// attributes.
public IDirectoryBrowsingResults CombineMultipleSearchFilters()
{
    DirectoryBrowserSearchBehavior searchBehavior;
    DirectoryBrowserSearchFilters searchFilters;
    FileSystemInfoAttributeSearchFilter<FileInfo> excludeSystemFiles;
    FileSystemInfoAttributeSearchFilter<FileInfo> excludeHiddenFiles;
    FileSystemInfoNameSearchFilter<FileInfo> excludeConfigFiles;
    FileSystemInfoNameSearchFilter<DirectoryInfo> folderExclusions;

    // Create a FileAttributes search filter to exclude any file having the
    // FileAttributes.System attribute
    excludeSystemFiles = new FileSystemInfoAttributeSearchFilter<FileInfo>(
        SearchOptions.ExcludeMatches, FileAttributes.System);
    // Create a FileAttributes search filter to exclude any file having the
    // FileAttributes.System attribute
    // Notice the "Decorator" pattern, we pass the excludeSystemFiles filter as
    // "inner filter" to combine both filters
    excludeHiddenFiles = new FileSystemInfoAttributeSearchFilter<FileInfo>(
        excludeSystemFiles, SearchOptions.ExcludeMatches, FileAttributes.Hidden);
    // Create a FileName search filter to exclude any file having a name ending
    // with ".config"
    // Notice the "Decorator" pattern, we pass the excludeHiddenFiles filter
    // as "inner filter" to combine the three filters
    excludeConfigFiles = new FileSystemInfoNameSearchFilter<FileInfo>(excludeHiddenFiles,
        SearchOptions.ExcludeMatches, new List<String>(1) { "*.config" });

    // Create a filter to exclude any folder name "bin", "obj", or ".svn"
    // Notice that we do not instantiate the object ourselves,
    // the DirectoryBrowserSearchFilters contains a couple of static "shortcut"
    // methods to make building search filters easier. We could
    // have instantiated the three file filters using similar shortcuts, which is
    // actually what the DirectoryBrowserSearchFilters
    // static "shortcut" methods also do to offer some flexibility for the numerous
    // arguments.
    folderExclusions = DirectoryBrowserSearchFilters.CreateDirectoryInfoNameSearchFilter(
        "bin,obj,.svn", SearchOptions.ExcludeMatches);

    // Then, instantiate the DirectoryBrowserSearchFilters required by the
    // IDirectoryBrowser object using our folder filter and our facade file filter.
    searchFilters = new DirectoryBrowserSearchFilters(folderExclusions,
        excludeConfigFiles);

    // Finally, define a search behavior
    searchBehavior =new DirectoryBrowserSearchBehavior
                        {
                            BrowserMode =
                                DirectoryBrowserModes.SearchOnlyMatchingDirectories,
                                DirectoryMatchRules = 
                                DirectoryMatchRules.IncludeMatchingDirectoryContents
                        };

    // Call a static method of the DirectoryBrowser instead of instantiating an
    // DirectoryBrowser ourselves.
    // The IDirectoryBrowsingResults will contain a reference to the IDirectoryBrowser
    // object that processed
    // the search, which can be useful to access the list of eventual errors that
    // may have occured during processing.
    return DirectoryBrowser.BrowseDirectory(@"C:\", searchBehavior, searchFilters);
}

当你组合多个过滤器时,你会注意到一个文件夹/文件必须满足所有过滤器才能成为结果的一部分(除非它在一个应该被完整添加到结果中的文件夹里)。这个行为是设计好的,但可以在SearchFilter<T>抽象类中更改。如果一个被装饰的SearchFilter<T>返回了false,Matches()方法会自动返回false。通过向SearchBehavior类添加一个额外的属性,可以轻松地控制此行为,但在编写引擎时,实现它并没有多大意义。

关注点

SearchFilter<T>类有望具有启发性,因为它使用了两个可能不是最广泛使用的设计模式。它非常不起眼,但它说明了“装饰器”模式可以做什么,而“装饰器”模式通常最好与模板方法模式一起在基类中实现,以保持算法逻辑不变,就像我所做的那样。

除此之外,我最初想尽量少地使用.NET框架的一些最新功能,如LINQ、Lambda表达式和自动属性。最后,我实际上只在3个非常小的位置使用了带lambda表达式的LINQ扩展方法(好吧,第三个位置使用的是比较器而不是lambda表达式),但至少它在被使用时是有意义的。在声明相应的字段完全没有用处的地方,自动属性的使用范围更广一些,但它不值得一提。所有这些意味着该引擎可以很容易地适应.NET Framework 2.0。

历史

2009-01-01:首次发布。

© . All rights reserved.