diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SearchResultCollection.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SearchResultCollection.cs
index 32a0666bdb4e90..6369b190988025 100644
--- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SearchResultCollection.cs
+++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/SearchResultCollection.cs
@@ -47,7 +47,7 @@ private ArrayList InnerList
{
if (_innerList == null)
{
- _innerList = new ArrayList();
+ var eagerList = new ArrayList();
var enumerator = new ResultsEnumerator(
this,
_rootEntry.GetUsername(),
@@ -55,7 +55,9 @@ private ArrayList InnerList
_rootEntry.AuthenticationType);
while (enumerator.MoveNext())
- _innerList.Add(enumerator.Current);
+ eagerList.Add(enumerator.Current);
+
+ _innerList = eagerList;
}
return _innerList;
@@ -188,12 +190,10 @@ protected virtual void Dispose(bool disposing)
public IEnumerator GetEnumerator()
{
- // Two ResultsEnumerators can't exist at the same time over the
- // same object. Need to get a new handle, which means re-querying.
- return new ResultsEnumerator(this,
- _rootEntry.GetUsername(),
- _rootEntry.GetPassword(),
- _rootEntry.AuthenticationType);
+ if (_innerList != null)
+ return new AlreadyReadResultsEnumerator(_innerList);
+ else
+ return new ResultsEnumerator(this, _rootEntry.GetUsername(), _rootEntry.GetPassword(), _rootEntry.AuthenticationType);
}
public bool Contains(SearchResult result) => InnerList.Contains(result);
@@ -214,6 +214,49 @@ void ICollection.CopyTo(Array array, int index)
InnerList.CopyTo(array, index);
}
+ ///
+ /// Supports a simple type-specific wrapper for the underlying cached list
+ ///
+ private sealed class AlreadyReadResultsEnumerator : IEnumerator
+ {
+ private readonly IEnumerator _innerEnumerator;
+
+ internal AlreadyReadResultsEnumerator(ArrayList innerList)
+ {
+ _innerEnumerator = innerList.GetEnumerator();
+ }
+
+ ///
+ /// Gets the current element in the collection.
+ ///
+ public SearchResult Current
+ {
+ get
+ {
+ return (SearchResult)(_innerEnumerator.Current);
+ }
+ }
+
+ ///
+ /// Advances the enumerator to the next element of the collection
+ /// and returns a Boolean value indicating whether a valid element is available.
+ ///
+ public bool MoveNext()
+ {
+ return _innerEnumerator.MoveNext();
+ }
+
+ ///
+ /// Resets the enumerator back to its initial position before the first element in the collection.
+ ///
+ public void Reset()
+ {
+ _innerEnumerator.Reset();
+ }
+
+ object IEnumerator.Current => Current;
+ }
+
///
/// Supports a simple ForEach-style iteration over a collection.
///
@@ -310,6 +353,7 @@ public bool MoveNext()
return false;
_currentResult = null;
+
if (!_initialized)
{
int hr = _results.SearchObject.GetFirstRow(_results.Handle);
diff --git a/src/libraries/System.DirectoryServices/tests/System.DirectoryServices.Tests.csproj b/src/libraries/System.DirectoryServices/tests/System.DirectoryServices.Tests.csproj
index b553252d53f604..4dd407cbed0e47 100644
--- a/src/libraries/System.DirectoryServices/tests/System.DirectoryServices.Tests.csproj
+++ b/src/libraries/System.DirectoryServices/tests/System.DirectoryServices.Tests.csproj
@@ -9,6 +9,7 @@
+
@@ -20,8 +21,7 @@
-
+
diff --git a/src/libraries/System.DirectoryServices/tests/System/DirectoryServices/DirectorySearcherTests.cs b/src/libraries/System.DirectoryServices/tests/System/DirectoryServices/DirectorySearcherTests.cs
new file mode 100644
index 00000000000000..f78ab9b11344d5
--- /dev/null
+++ b/src/libraries/System.DirectoryServices/tests/System/DirectoryServices/DirectorySearcherTests.cs
@@ -0,0 +1,83 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.InteropServices;
+using System.Collections.Generic;
+using System.Collections;
+using Xunit;
+using Xunit.Sdk;
+using System.Reflection;
+
+namespace System.DirectoryServices.Tests
+{
+ public partial class DirectorySearcherTests
+ {
+ internal static bool IsLdapConfigurationExist => LdapConfiguration.Configuration != null;
+ internal static bool IsActiveDirectoryServer => IsLdapConfigurationExist && LdapConfiguration.Configuration.IsActiveDirectoryServer;
+
+ private const int ADS_SYSTEMFLAG_CR_NTDS_NC = 0x1;
+ private const int ADS_SYSTEMFLAG_CR_NTDS_DOMAIN = 0x2;
+
+ [ConditionalFact(nameof(IsLdapConfigurationExist))]
+ public void DirectorySearch_IteratesCorrectly_SimpleEnumeration()
+ {
+ var e = GetDomains();
+ Assert.NotNull(e);
+
+ foreach (var result in e)
+ {
+ Assert.NotNull(result);
+ }
+ }
+
+ [ConditionalFact(nameof(IsLdapConfigurationExist))]
+ public void DirectorySearch_IteratesCorrectly_AfterCount()
+ {
+ var e = GetDomains();
+ Assert.NotNull(e);
+ Assert.NotEqual(0, e.Count);
+
+ foreach (var result in e)
+ {
+ Assert.NotNull(result);
+ }
+ }
+
+
+ [ConditionalFact(nameof(IsLdapConfigurationExist))]
+ public void DirectorySearch_IteratesCorrectly_MixedCount()
+ {
+ var e = GetDomains();
+ Assert.NotNull(e);
+ Assert.NotEqual(0, e.Count);
+
+ foreach (var result in e)
+ {
+ Assert.NotEqual(0, e.Count);
+ Assert.NotNull(result);
+ }
+ }
+
+ private static SearchResultCollection GetDomains()
+ {
+ using var entry = new DirectoryEntry("LDAP://rootDSE");
+ var namingContext = entry.Properties["configurationNamingContext"][0]!.ToString();
+ using var searchRoot = new DirectoryEntry($"LDAP://CN=Partitions,{namingContext}");
+ using var ds = new DirectorySearcher(searchRoot)
+ {
+ PageSize = 1000,
+ CacheResults = false
+ };
+ ds.SearchScope = SearchScope.OneLevel;
+ ds.PropertiesToLoad.Add("distinguishedName");
+ ds.PropertiesToLoad.Add("nETBIOSName");
+ ds.PropertiesToLoad.Add("nCName");
+ ds.PropertiesToLoad.Add("dnsRoot");
+ ds.PropertiesToLoad.Add("trustParent");
+ ds.PropertiesToLoad.Add("objectSid");
+ ds.Filter = string.Format("(&(objectCategory=crossRef)(systemFlags={0}))", ADS_SYSTEMFLAG_CR_NTDS_DOMAIN | ADS_SYSTEMFLAG_CR_NTDS_NC);
+
+ return ds.FindAll();
+ }
+ }
+}