Julian Gold
Julian Gold

Reputation: 1286

C# Generics - Lists of collections

I have a hierarchy of classes based on an abstract Device class. The repository of all devices looks a bit like this:

class Hardware
{
    public readonly DeviceCollection<Switch> Switches = ...;
    public readonly DeviceCollection<Light> Lights = ...;
}

where DeviceCollection implements IEnumerable<T> where T : Device.

I need to enumerate across all devices, and my currently crappy code does this

    protected override IEnumerator<Device> enumerate()
    {
        foreach (var light in Lights)
        {
            yield return light;
        }

        foreach (var @switch in Switches)
        {
            yield return @switch;
        }
    }

This is not robust as sometimes I add a new bit of hardware, a new DeviceCollection and it's easy to forget to add a new iteration in the above. So I figured a bit of reflection would help - lazily build a list of the DeviceCollection fields and run through that. But what would the declaration of that list look like?

private List<DeviceCollection<T>> _collections;

doesn't compile. Neither does

private List<DeviceCollection> _collections;

How do I declare this list?


Corollary: Tim S's answer - that IEnumerable is covariant - solves my immediate problem. One little glitch left (which I'm sure has a simpler solution!) is how to do the reflection. Here is my ugly ugly ugly hack:

_collections = new List<IEnumerable<Device>>();
var fields = GetType().GetFields( BindingFlags.Instance | BindingFlags.Public );
foreach (var field in fields)
{
    if (field.FieldType.Name.Contains( "DeviceCollection" ))
    {
        _collections.Add( (IEnumerable<Device>)field.GetValue(this) );
    }
}

This, because the test

if (field.FieldType == typeof(DeviceCollection<>)

doesn't work.

Upvotes: 2

Views: 231

Answers (4)

Mike Perrenoud
Mike Perrenoud

Reputation: 67898

I would contend that you just need a single list:

public DeviceCollection<Device> Devices { get; private set; }

and then you can return specific types with Switches for example:

public IEnumerable<Switch> Switches
{
    get
    {
        return this.Devices.OfType<Switch>();
    }
}

and so now enumerate just looks like this:

protected override IEnumerator<Device> enumerate()
{
    foreach (var d in Devices)
    {
        yield return d;
    }
}

Upvotes: 2

Tim S.
Tim S.

Reputation: 56536

The declaration would be:

private List<IEnumerable<Device>> _collections;

And you could use it (after setting it up, which you already seem to have a good idea how to do) as easily as:

protected override IEnumerator<Device> enumerate()
{
    return _collections.SelectMany(x => x).GetEnumerator();
}

This works because the IEnumerable<T> interface is covariant, meaning that, e.g. an IEnumerable<Switch> (which DeviceCollection<Switch> implements) can be used as an IEnumerable<Device>.

The reason that a DeviceCollection<Switch> can't be used as an DeviceCollection<Device> is that classes and collections can't be covariant - it wouldn't make sense to let you try to Add a Device to an ICollection<Switch>, because it should only contain Switches. But it makes perfect sense to get a Device out of an IEnumerable<Switch>.

Upvotes: 8

Dax Fohl
Dax Fohl

Reputation: 10781

Why do you need a member variable? I'd think you could do

protected override IEnumerable<Device> enumerate()
{
    ... reflect to get properties of type IEnumerable<Device>
    foreach (var prop in properties) 
    {
        foreach (var device in (IEnumerable<Device>)prop.GetValue(this))
        {
            yield return device;
        }
    }
}

Per the comments on efficiency, while I disagree with them, and also disagree with the solutions that propose using a single List and OfType, if reflection is too slow/dangerous, you could simplify your original code:

public IEnumerable<Device> GetAll() {
    return from list in new IEnumerable<Device>[] {Switches, Lights}
           from device in list
           select device;
} 

Upvotes: 0

Ari
Ari

Reputation: 3127

You can declare it:

private List<DeviceCollection<Device>> _collections;

Upvotes: 0

Related Questions