<value>The path is empty.</value>
</data>
<data name="Arg_MustBeDriveLetterOrRootDir" xml:space="preserve">
- <value>Drive name must be a root directory (i.e. 'C:\') or a drive letter ('C').</value>
+ <value>Drive name must be a root directory (i.e. 'C:\') or a drive letter ('C').</value>
+ </data>
+ <data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
+ <value>Non-negative number required.</value>
</data>
</root>
public bool IgnoreInaccessible { get { throw null; } set { } }
public System.IO.MatchCasing MatchCasing { get { throw null; } set { } }
public System.IO.MatchType MatchType { get { throw null; } set { } }
+ public int MaxRecursionDepth { get { throw null; } set { } }
public bool RecurseSubdirectories { get { throw null; } set { } }
public bool ReturnSpecialDirectories { get { throw null; } set { } }
}
private string? _currentPath;
private IntPtr _directoryHandle;
private bool _lastEntryFound;
- private Queue<string>? _pending;
+ private Queue<(string Path, int RemainingDepth)>? _pending;
private Interop.Sys.DirectoryEntry _entry;
private TResult? _current;
if (isDirectory && !isSpecialDirectory)
{
- if (_options.RecurseSubdirectories && ShouldRecurseIntoEntry(ref entry))
+ if (_options.RecurseSubdirectories && _remainingRecursionDepth > 0 && ShouldRecurseIntoEntry(ref entry))
{
// Recursion is on and the directory was accepted, Queue it
if (_pending == null)
- _pending = new Queue<string>();
- _pending.Enqueue(Path.Join(_currentPath, entry.FileName));
+ _pending = new Queue<(string Path, int RemainingDepth)>();
+ _pending.Enqueue((Path.Join(_currentPath, entry.FileName), _remainingRecursionDepth - 1));
}
}
if (_pending == null || _pending.Count == 0)
return false;
- _currentPath = _pending.Dequeue();
+ (_currentPath, _remainingRecursionDepth) = _pending.Dequeue();
_directoryHandle = CreateDirectoryHandle(_currentPath, ignoreNotFound: true);
}
private IntPtr _directoryHandle;
private string? _currentPath;
private bool _lastEntryFound;
- private Queue<(IntPtr Handle, string Path)>? _pending;
+ private Queue<(IntPtr Handle, string Path, int RemainingDepth)>? _pending;
private void Init()
{
if (!_options.ReturnSpecialDirectories)
continue;
}
- else if (_options.RecurseSubdirectories && ShouldRecurseIntoEntry(ref entry))
+ else if (_options.RecurseSubdirectories && _remainingRecursionDepth > 0 && ShouldRecurseIntoEntry(ref entry))
{
// Recursion is on and the directory was accepted, Queue it
string subDirectory = Path.Join(_currentPath.AsSpan(), _entry->FileName);
try
{
if (_pending == null)
- _pending = new Queue<(IntPtr, string)>();
- _pending.Enqueue((subDirectoryHandle, subDirectory));
+ _pending = new Queue<(IntPtr, string, int)>();
+ _pending.Enqueue((subDirectoryHandle, subDirectory, _remainingRecursionDepth - 1));
}
catch
{
if (_pending == null || _pending.Count == 0)
return false;
- (_directoryHandle, _currentPath) = _pending.Dequeue();
+ (_directoryHandle, _currentPath, _remainingRecursionDepth) = _pending.Dequeue();
return true;
}
{
public unsafe abstract partial class FileSystemEnumerator<TResult> : CriticalFinalizerObject, IEnumerator<TResult>
{
+ private int _remainingRecursionDepth;
+
/// <summary>
/// Encapsulates a find operation.
/// </summary>
string path = isNormalized ? directory : Path.GetFullPath(directory);
_rootDirectory = Path.TrimEndingDirectorySeparator(path);
_options = options ?? EnumerationOptions.Default;
+ _remainingRecursionDepth = _options.MaxRecursionDepth;
Init();
}
{
public class EnumerationOptions
{
+ private int _maxRecursionDepth;
+
+ internal const int DefaultMaxRecursionDepth = int.MaxValue;
+
/// <summary>
/// For internal use. These are the options we want to use if calling the existing Directory/File APIs where you don't
/// explicitly specify EnumerationOptions.
{
IgnoreInaccessible = true;
AttributesToSkip = FileAttributes.Hidden | FileAttributes.System;
+ MaxRecursionDepth = DefaultMaxRecursionDepth;
}
/// <summary>
/// </remarks>
public MatchCasing MatchCasing { get; set; }
+ /// <summary>Gets or sets a value that indicates the maximum directory depth to recurse while enumerating, when <see cref="RecurseSubdirectories" /> is set to <see langword="true" />.</summary>
+ /// <value>A number that represents the maximum directory depth to recurse while enumerating. The default value is <see cref="int.MaxValue" />.</value>
+ /// <remarks>If <see cref="MaxRecursionDepth" /> is set to a negative number, the default value <see cref="int.MaxValue" /> is used.
+ /// If <see cref="MaxRecursionDepth" /> is set to zero, enumeration returns the contents of the initial directory.</remarks>
+ public int MaxRecursionDepth
+ {
+ get => _maxRecursionDepth;
+ set
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ _maxRecursionDepth = value;
+ }
+ }
+
/// <summary>
/// Set to true to return "." and ".." directory entries.
/// </summary>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.IO.Enumeration;
+using System.Linq;
+using Xunit;
+
+namespace System.IO.Tests.Enumeration
+{
+ public class RecursionDepthTests : FileSystemTest
+ {
+ public static IEnumerable<string> GetEntryNames(string directory, int depth)
+ {
+ return new FileSystemEnumerable<string>(
+ directory,
+ (ref FileSystemEntry entry) => entry.FileName.ToString(),
+ new EnumerationOptions() { RecurseSubdirectories = true, MaxRecursionDepth = depth });
+ }
+
+ [Theory,
+ InlineData(0, 2),
+ InlineData(1, 4),
+ InlineData(2, 5),
+ InlineData(3, 5),
+ InlineData(int.MaxValue, 5)
+ ]
+ public void EnumerateDirectory_WithSpecifedRecursionDepth(int depth, int expectedCount)
+ {
+ DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath());
+ DirectoryInfo testSubdirectory1 = Directory.CreateDirectory(Path.Combine(testDirectory.FullName, "Subdirectory1"));
+ DirectoryInfo testSubdirectory2 = Directory.CreateDirectory(Path.Combine(testSubdirectory1.FullName, "Subdirectory2"));
+ FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, "fileone.htm"));
+ FileInfo fileTwo = new FileInfo(Path.Combine(testSubdirectory1.FullName, "filetwo.html"));
+ FileInfo fileThree = new FileInfo(Path.Combine(testSubdirectory2.FullName, "filethree.doc"));
+
+ fileOne.Create().Dispose();
+ fileTwo.Create().Dispose();
+ fileThree.Create().Dispose();
+
+ string[] results = GetEntryNames(testDirectory.FullName, depth).ToArray();
+ Assert.Equal(expectedCount, results.Length);
+ }
+
+ [Fact]
+ public void NegativeRecursionDepth_ThrowsArgumentOutOfRangeException()
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => new EnumerationOptions() { MaxRecursionDepth = -1 });
+ }
+ }
+}
<Compile Include="FileStream\DisposeAsync.cs" />
<Compile Include="FileStream\ReadWriteSpan.cs" />
<Compile Include="Enumeration\ConstructionTests.cs" />
+ <Compile Include="Enumeration\RecursionDepthTests.cs" />
<Compile Include="Enumeration\SpecialDirectoryTests.cs" />
<Compile Include="Enumeration\SkipAttributeTests.cs" />
<Compile Include="Enumeration\FileSystemNameTests.cs" />