How to create a nested list of derived classes
I am writing an application to work with WMI objects. I have a class Computer
with a property List<Component>
that is a Component
base class abtract, from which come all derived classes, such as Monitor
, ComputerUnit
, Printer
, etc.
Given the wide variety of WMI objects, I decided to use custom .NET generators for the first time (I'm a newbie) and that was the code - just the relevant lines:
public class Computer
{
public List<Component> ListOfComponents { get; set; }
}
public class Component
{
public NameSpaceBase[] WMI_ClassArray { get; set; }
}
public class Monitor : Component
{ }
public class ComputerUnit : Component
{ }
public static void Main()
{
Computer computer = new Computer(hostName);
Monitor monitor = computer.Get<Monitor>(new NameSpaceBase[] {
new WMI_Monitor() });
ComputerUnit computerUnit = computer.Get<ComputerUnit>(
new NameSpaceBase[] {
new WMI_Bios(),
new WMI_ComputerSystem() });
computer.ListOfComponents.Add(monitor);
computer.ListOfComponents.Add(computerUnit);
}
It worked fine until I realized that I wanted to treat each component as a List itself, because I needed to split multiple monitors and / or other multiple devices, so I changed my properties and methods accordingly:
public class Computer
{
public List<List<Component>> ListOfComponents { get; set; }
}
public static void Main()
{
Computer computer = new Computer(hostName);
List<Monitor> monitor = computer.Get<Monitor>(new NameSpaceBase[] {
new WMI_Monitor() });
List<ComputerUnit> computerUnit = computer.Get<ComputerUnit>(
new NameSpaceBase[] {
new WMI_Bios(),
new WMI_ComputerSystem() });
computer.ListOfComponents.Add(monitor);
computer.ListOfComponents.Add(computerUnit);
}
but now I am assigned error CS1503: Argument 1: cannot convert from 'System.Collections.Generic.List<Machine.Components.Monitor>' to 'System.Collections.Generic.List<Machine.Components.Component>'
in the last two lines.
I can't figure out where the error is, because if I comment out the last two lines, I see that the List and List objects are correctly created, of the correct type, and filled with data.
Bottom line of history: I don't understand why I can't add an object List<Monitor>
to List<List<Component>>
, whereas I can add an object Monitor
to List<Component>
.
source to share
To specifically answer your question:
I don't understand why I cannot add a List <Monitor> object to a List <List <Component -> while I can add a Monitor object to a List <Component>.
This is the same as asking why you cannot assign List<Derived>
List<Base>
where Derived : Base
.
Some languages โโallow this, but C # does not. Let's see why.
Consider these classes:
class Animal
{
}
class Cat : Animal
{
public void Meow() {}
}
class Dog : Animal
{
public void Bark() { }
}
Now, suppose you have a list of dogs:
List<Dog> dogs = new List<Dog> {new Dog()};
You are not allowed to do the following:
List<Animal> animals = dogs; // Not allowed - let pretend it is!
Ok, let's pretend the above line is compiling. After executing it, the list animals
will be a link to dogs
which is List<Dog>
. Remember this important fact!
Now let's do the following:
animals.Add(new Cat());
Seems okay? Nope. We just added Cat
to dogs
, which now contains two elements; a Cat
and a Dog
.
Now what happens if we do dogs[1].Bark();
?
The answer is that the program will explode during execution because cats cannot bark! Of course this cannot happen because you are not allowed to do it List<Animal> animals = dogs;
.
Possible Solution?
There's an IReadOnlyList<T>
interface you can use instead IList<T>
:
IReadOnlyList<Animal> animals = dogs; // Compiles OK.
This is allowed because it is declared like this:
public interface IReadOnlyList<out T> : IReadOnlyCollection<T>,
IEnumerable<T>, IEnumerable
out T
indicates that the covariant .
Since IReadOnlyList
it cannot be modified, it can support covariance.
source to share
One way to get around your requirement of a list of lists is to simply use List and then filter every time you need all components of a specific type.
monitors = computer.ListOfComponents.OfType<Monitor>();
This design also makes it easier to modify this collection, since you don't have to deal with the ability to add a specific type of component the first time, in which case you will need to create and add subscriptions too.
source to share