IEnumerable not enumerating in foreach

2019-04-13 09:54发布

问题:

I'm encountering a problem with one of my IEnumerable's that I haven't seen before.

I have a collection:

IEnumerable<IDependency> dependencies;

that's being used in a foreach loop.

foreach (var dependency in dependencies)

For some reason this foreach doesn't iterate over my IEnumerable and simply skips to the end.

If I change my foreach to loop through a a list however it seems to work fine:

foreach (var dependency in dependencies.ToList())

What could I be doing that's causing this behaviour? I haven't experienced this with IEnumerable before.

Update:

Here's the entire code of my foreach that's running in my method GenerateDotString:

foreach (var dependency in dependencies)
{
    var dependentResource = dependency.Resource;
    var lineColor = (dependency.Type == DependencyTypeEnum.DependencyType.Hard) ? "blue" : "red";

    output += labelFormat.FormatWith(dependentResource.Name.MakeDotsafeString(), dependentResource.Name, dependentResource.ResourceType);
    output += relationshipFormat.FormatWith(dependentResource.Name.MakeDotsafeString(), currentName, lineColor);

    if (dependentResource.DependentResources != null)
    {
        output += GenerateDotString(dependentResource, dependentResource.DependentResources, searchDirection);
    }
}

return output;

Update 2:

Here's the signature of the method containing this foreach (incase it helps).

private static string GenerateDotString(IResource resource, IEnumerable<IDependency> dependencies, SearchEnums.SearchDirection searchDirection)

Update 3:

Here's the method GetAllRelatedResourcesByParentGuidWithoutCacheCheck:

private IEnumerable<IDependency> GetAllRelatedResourcesByParentGuidWithoutCacheCheck(Guid parentCiGuid, Func<Guid, IEnumerable<IDependency>> getResources)
{
    if (!_itemsCheckedForRelations.Contains(parentCiGuid)) // Have we already got related resources for this CI?;
    {
        var relatedResources = getResources(parentCiGuid);
        _itemsCheckedForRelations.Add(parentCiGuid);

        if (relatedResources.Count() > 0)
        {
            foreach (var relatedResource in relatedResources)
            {
                relatedResource.Resource.DependentResources = GetAllRelatedResourcesByParentGuidWithoutCacheCheck(relatedResource.Resource.Id, getResources);
                yield return relatedResource;
            }
        }
    }
}

Update 4:

I'm adding the methods in the chain here to be clear on how we're getting the collection of dependencies.

The above method GetAllRelatedResourcesByParentGuidWithoutCacheCheck accepts a delegate which in this case is:

private IEnumerable<IDependency> GetAllSupportsResources(Guid resourceId)
{
    var hardDependents = GetSupportsHardByParentGuid(resourceId);
    var softDependents = GetSupportsSoftByParentGuid(resourceId);
    var allresources = hardDependents.Union(softDependents);
    return allresources;
}

which is calling:

private IEnumerable<IDependency> GetSupportsHardByParentGuid(Guid parentCiGuid)
{
    XmlNode ciXmlNode = _reportManagementService.RunReportWithParameters(Res.SupportsHardReportGuid, Res.DependentCiReportCiParamName + "=" + parentCiGuid);
    return GetResourcesFromXmlNode(ciXmlNode, DependencyTypeEnum.DependencyType.Hard);
}

and returns:

private IEnumerable<IDependency> GetResourcesFromXmlNode(XmlNode ciXmlNode, DependencyTypeEnum.DependencyType dependencyType)
{
    var allResources = GetAllResources();

    foreach (var nodeItem in ciXmlNode.SelectNodes(Res.WebServiceXmlRootNode).Cast<XmlNode>())
    {
        Guid resourceGuid;
        var isValidGuid = Guid.TryParse(nodeItem.SelectSingleNode("ResourceGuid").InnerText, out resourceGuid);

        var copyOfResource = allResources.Where(m => m.Id == resourceGuid).SingleOrDefault();
        if (isValidGuid && copyOfResource != null)
        {
            yield return new Dependency
            {
                Resource = copyOfResource,
                Type = dependencyType
            };
        }
    }
}

which is where the concrete type is returned.

回答1:

So it looks like the problem was to do with the dependencies collection infinately depending on itself.

It seems from my debugging that iterating the IEnumerable causes a timeout and so the foreach simply skips execution of its contents where as ToList() returns as much as it can before timing out.

I may not be correct about that but it's what seems to be the case as far as I can tell.

To give a bit of background as to how this all came about I'll explain the code changes I made yesterday.

The first thing the application does is build up a collection of all resources which are filtered by resource type. These are being brought in from our CMDB via a web service call.

What I was then doing is for each resource that was selected (via autocomplete in this case) I'd make a web service call and get the dependents for the resource based on its Guid. (recursively)

I changed this yesterday so that we didn't need to obtain the full resource information in this second web service call, rather, simply obtain a list of Guids in the web service call and grab the resources from our resources collection.

What I forgot was that the web service call for dependents wasn't filtered by type and so it was returning results that didn't exist in the original resources collection.

I need to look a bit further but it seems that at this point, the new collection of dependent resources was becoming dependent on itself and thus, causing the IEnumerable<IDependents> collection later on to timeout.

This is where I've got to today, if I find anything else I'll be sure to note it here.

To summarise this:

If infinite recursion occurs in an IEnumerable it'll simply timeout when attempting to enumerate in a foreach.

Using ToList() on the IEnumerable seems to return as much data as it can before timing out.