42

.NET 4.0には、列挙を介してストリーミング方式でディレクトリ内のファイルを取得するための優れた新しい方法があります。

ここでの問題は、すべてのファイルを列挙したい場合、どのファイルまたはフォルダーがアクセス保護されているかを事前に知らず、UnauthorizedAccessExceptionをスローする可能性があることです。

再現するには、次のフラグメントを実行するだけです。

foreach (var file in Directory.EnumerateFiles(@"c:\", "*", SearchOption.AllDirectories))
{
   // whatever
}

この.NETメソッドが存在する前は、文字列配列を返すメソッドに再帰的イテレータを実装することで、ほぼ同じ効果を達成することができました。しかし、新しい.NETメソッドほど怠惰ではありません。

じゃあ何をすればいいの?このメソッドを使用する場合、UnauthorizedAccessExceptionを抑制できますか、それとも事実ですか?

メソッドには、例外を処理するアクションを受け入れるオーバーロードが必要であるように思われます。

4

6 に答える 6

32

上記を機能させることができませんでしたが、これが私の実装です。「Win7」ボックスのc:\ usersでテストしました。これは、これらすべての「厄介な」dirがある場合に発生するためです。

SafeWalk.EnumerateFiles(@"C:\users", "*.jpg", SearchOption.AllDirectories).Take(10)

クラス:

public static class SafeWalk
{
    public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
    {   
        try
        {
            var dirFiles = Enumerable.Empty<string>();
            if(searchOpt == SearchOption.AllDirectories)
            {
                dirFiles = Directory.EnumerateDirectories(path)
                                    .SelectMany(x => EnumerateFiles(x, searchPattern, searchOpt));
            }
            return dirFiles.Concat(Directory.EnumerateFiles(path, searchPattern));
        }
        catch(UnauthorizedAccessException ex)
        {
            return Enumerable.Empty<string>();
        }
    }
}
于 2011-05-10T23:12:10.987 に答える
9

上記の回答に関する問題は、サブディレクトリの例外が処理されないことです。これは、これらの例外を処理するためのより良い方法であり、アクセス例外をスローしたものを除くすべてのサブディレクトリからすべてのファイルを取得します。

    /// <summary>
    /// A safe way to get all the files in a directory and sub directory without crashing on UnauthorizedException or PathTooLongException
    /// </summary>
    /// <param name="rootPath">Starting directory</param>
    /// <param name="patternMatch">Filename pattern match</param>
    /// <param name="searchOption">Search subdirectories or only top level directory for files</param>
    /// <returns>List of files</returns>
    public static IEnumerable<string> GetDirectoryFiles(string rootPath, string patternMatch, SearchOption searchOption)
    {
        var foundFiles = Enumerable.Empty<string>();

        if (searchOption == SearchOption.AllDirectories)
        {
            try
            {
                IEnumerable<string> subDirs = Directory.EnumerateDirectories(rootPath);
                foreach (string dir in subDirs)
                {
                    foundFiles = foundFiles.Concat(GetDirectoryFiles(dir, patternMatch, searchOption)); // Add files in subdirectories recursively to the list
                }
            }
            catch (UnauthorizedAccessException) { }
            catch (PathTooLongException) {}
        }

        try
        {
            foundFiles = foundFiles.Concat(Directory.EnumerateFiles(rootPath, patternMatch)); // Add files from the current directory
        }
        catch (UnauthorizedAccessException) { }

        return foundFiles;
    }
于 2013-12-21T13:58:56.707 に答える
6

MoveNext例外をスローする のはそれだと理解しています。

MoveNextシーケンスを安全にウォークし、例外を無視しようとするメソッドを作成しようとしました。ただしMoveNext、例外がスローされたときに位置を進めるかどうかはわかりません。したがって、これは無限ループである可能性もあります。実装の詳細に依存するため、これも悪い考えです。

しかし、それはとても楽しいです!

public static IEnumerable<T> SafeWalk<T> (this IEnumerable<T> source)
{
    var enumerator = source.GetEnumerator();
    bool? hasCurrent = null;

    do {
        try {
            hasCurrent = enumerator.MoveNext();
        } catch {
            hasCurrent = null; // we're not sure
        }

        if (hasCurrent ?? false) // if not sure, do not return value
            yield return enumerator.Current;

    } while (hasCurrent ?? true); // if not sure, continue walking
}

foreach (var file in Directory.EnumerateFiles("c:\\", "*", SearchOption.AllDirectories)
                              .SafeWalk())
{
    // ...
}

これは、このイテレータのフレームワークの実装について次の条件が当てはまる場合にのみ機能します(参照についてFileSystemEnumerableIterator<TSource>はReflectorを参照してください)。

  • MoveNext失敗するとその位置を進めます。
  • 最後の要素で失敗するMoveNextと、後続の呼び出しはfalse例外をスローする代わりに戻ります。
  • この動作は、.NETFrameworkのさまざまなバージョンで一貫しています。
  • ロジックや構文の間違いはありません。

動作する場合でも、本番環境では絶対に使用しないでください。
しかし、私は本当にそうなのだろうか。

于 2011-02-23T23:40:44.270 に答える
2

既存の回答を編集するどころか、コメントを追加する担当者がいないため、回答として投稿します。私の要件は、メモリ割り当てと冗長変数を最小限に抑え、システムにディレクトリの単一の列挙を実行させることでした。

static IEnumerable<string> FindFiles(string path, string filter = "*", bool recursive = false)
{
    IEnumerator<string> fEnum;
    try
    {
        fEnum = Directory.EnumerateFiles(path, filter, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).GetEnumerator();
    }
    catch (UnauthorizedAccessException) { yield break; }
    while (true)
    {
        try { if (!fEnum.MoveNext()) break; }
        catch (UnauthorizedAccessException) { continue; }
        yield return fEnum.Current;
    }
}

ダン・ベチャードはコメントで言及しています:

残念ながら、MoveNext()は、例外をスローしたときにその位置を進めません。

これは、.Netの新しいリリースまたはWindows 10ビルドのいずれかで修正された可能性がありますか?Windows10の.NET5.0ではこの問題は発生していません。システムドライブ全体を検索してテストしました。


VB.NETの場合:

Public Iterator Function FindFiles(path As String, Optional filter As String = "*", Optional recursive As Boolean = False) As IEnumerable(Of String)

    Dim fEnum As IEnumerator(Of String)
    Dim searchDepth = If(recursive, SearchOption.AllDirectories, SearchOption.TopDirectoryOnly)

    Try
        fEnum = Directory.EnumerateFiles(path, filter, searchDepth).GetEnumerator()
    Catch uae As UnauthorizedAccessException
        Return
    End Try

    Do While True
        Try
            If Not fEnum.MoveNext() Then Exit Do
            Yield fEnum.Current
        Catch uae As UnauthorizedAccessException

        End Try

    Loop

End Function
于 2020-11-29T11:29:07.917 に答える
1

私は遅れていますが、代わりに観察可能なパターンを使用することをお勧めします:

public class FileUtil
{
  private static void FindFiles_(string path, string pattern,
    SearchOption option, IObserver<string> obs, CancellationToken token)
  {
    try
    {
      foreach (var file in Directory.EnumerateFiles(path, pattern,
        SearchOption.TopDirectoryOnly))
      {
        if (token.IsCancellationRequested) break;
        obs.OnNext(file);
      }

      if (option != SearchOption.AllDirectories) return;

      foreach (var dir in Directory.EnumerateDirectories(path, "*", 
        SearchOption.TopDirectoryOnly))
      {
        FindFiles_(dir, pattern, option, obs, token);
      }
    }
    catch (UnauthorizedAccessException) { }
    catch (PathTooLongException) { }
    catch (IOException) { }
    catch (Exception err) { obs.OnError(err); }
  }

  public static IObservable<string> GetFiles(string root, string pattern,
    SearchOption option)
  {
    return Observable.Create<string>(
      (obs, token) =>
        Task.Factory.StartNew(
          () =>
          {
            FindFiles_(root, pattern, option, obs, token);
            obs.OnCompleted();
          },
          token));
  }
}
于 2018-06-19T14:54:34.127 に答える
0

以前の回答は私が望んでいたことをしていないようだったので、私はこれを回避するクラスの独自の実装を作成しました。これは、アクセスできないすべてのファイルとフォルダをスキップし、アクセスできるすべてのファイルを返すだけです。

public static class SafeWalk
{
    public static IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOpt)
    {
        if (searchOpt == SearchOption.TopDirectoryOnly)
        {
            return Directory.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        List<string> folders = new List<string>() { path };
        int folCount = 1;
        List<string> files = new List<string>() { };

        for (int i = 0; i < folCount; i++)
        {
            try
            {
                foreach (var newDir in Directory.EnumerateDirectories(folders[i], "*", SearchOption.TopDirectoryOnly))
                {
                    folders.Add(newDir);
                    folCount++;
                    try
                    {

                        foreach (var file in Directory.EnumerateFiles(newDir, searchPattern))
                        {
                            files.Add(file);
                        }
                    } catch (UnauthorizedAccessException)
                    {
                        // Failed to read a File, skipping it.
                    }
                }
            }
            catch (UnauthorizedAccessException)
            {
                // Failed to read a Folder, skipping it.
                continue;
            }
        }
        return files;
    }
}

通常のEnumerateFiles関数とまったく同じように使用でき、Dictionary.EnumerateFiles(...)の代わりにSafeWalk.EnumerateFiles(...)を使用するだけです。

于 2020-05-04T18:13:37.530 に答える