Iterator variable doesn't exist in IEnumerable from which it came?

Viewed 118

I have a method which looks like

private Component[] AssociateComponentsWithParametersAndValues(
            IEnumerable<Component> components,
            IEnumerable<ComponentParameter> parameters,
            IEnumerable<ComponentParameterValue> values
            )
        {
            var componentsDictionary = new Dictionary<string, Component>();
            var parametersDictionary = new Dictionary<string, ComponentParameter>();
            var valuesDictionary = new Dictionary<string, ComponentParameterValue>();
            foreach (Component c in components)
            {
                bool componentMatch = components.Any(co => co == c);
                bool identifierMatch = components.Any(co => co.Identifier == c.Identifier);
                if (!componentsDictionary.ContainsKey(c.Identifier))
                    componentsDictionary.Add(c.Identifier, c);
            }
            // Do a bunch of stuff to mutate the components
            return components.ToArray();
        }

You would think that componentMatch and identifierMatch would both be true each time right? Instead, componentMatch is always false and identifierMatch is always true. Also, the identifier is (nearly, occasionally there's some bad data) always unique, so it's not like it can be finding another component with the same identifier.

So, there must be something weird with the Component class. Well, here's what it looks like

public class Component : ConfigurationObject
    {
        public string Parent { get; set; }
        public string Type { get; set; }
        public string Module { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string TypeName { get; set; }
        public bool? Enabled { get; set; }
        public string DBIdentifier { get; set; }
        public Dictionary<string, ComponentParameterAndValues> ParametersAndValues { get; set; }
        public override string Identifier => DBIdentifier;
    }

And here's the class it implements

public abstract class ConfigurationObject
    {
        public abstract string Identifier { get; }
    }

Why is this happening?

1 Answers

The only way I could see this break, is if IEnumerable<Component> components is a lazily evaluated enumerable, returning new iterator objects every time. This works:

var list = new List<Component>
{
    new Component { Identifier = "Foo" },
    new Component { Identifier = "Bar" },
    new Component { Identifier = "Baz" },
};

foreach (Component c in list)
{
    bool componentMatch = list.Any(co => co == c);
    Console.WriteLine($"Component {c.Identifier} match: {componentMatch}");
}

Because == checks reference equality (unless Component overrides it, but it doesn't look like it does). However, if it's not a list, but a new result each iteration:

IEnumerable<Component> list = GetList();

foreach (Component c in list)
{
    bool componentMatch = list.Any(co => co == c);
    Console.WriteLine($"Component {c.Identifier} match: {componentMatch}");
}

private static IEnumerable<Component> GetList()
{
    yield return new Component { Identifier = "Foo" };
    yield return new Component { Identifier = "Bar" };
    yield return new Component { Identifier = "Baz" };
}

Then it prints false, because foreach() and Any() each get a new collection of new objects, so their references don't match.

The solution would be to enumerate once, and store the components once, materialized in a list, and use that:

var localComponents = components.ToList();

foreach (Component c in localComponents)
{
    // ...
}
Related