Reputation: 1286
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
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
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 Switch
es. But it makes perfect sense to get a Device
out of an IEnumerable<Switch>
.
Upvotes: 8
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
Reputation: 3127
You can declare it:
private List<DeviceCollection<Device>> _collections;
Upvotes: 0