I'm getting an error when trying to add generic object to a List<>.
Its probably related to Covariance and Contravariance but I not sure how to work around this. I've tried to restrict my generic types using where T: IRegister.
I have an interface to represent a register and then two classes which represent a ByteRegister and a DoubleWordRegister.
public interface IRegister
{
string Name {get;set;}
}
public class ByteRegister : IRegister
{
...
}
public class DoubleWordRegister : IRegister
{
...
}
I then have another class which represents a block of these registers all of the same type.
public class RegisterBlock<T> where T:IRegister
{
private IList<T> _registers;
... constructors, properties etc
public void AddRegister(T register)
{
_registers.Add(register);
}
}
And finally I have a RegisterMap class which is used to define the list of register blocks and each register within the block.
public class RegisterMap
{
private List<RegisterBlock<IRegister>> _blocks;
public RegisterMap()
{
_blocks = new List<RegisterBlock<IRegister>>();
RegisterBlock<ByteRegister> block1= new RegisterBlock<ByteRegister>("Block1", 0);
block1.AddRegister(new ByteRegister("Reg1"));
block1.AddRegister(new ByteRegister("Reg2"));
_blocks.Add(block1);
RegisterBlock<DoubleWordRegister> block2= new RegisterBlock<DoubleWordRegister>("Block2", 10);
block2.AddRegister(new DoubleWordRegister("Reg3"));
block2.AddRegister(new DoubleWordRegister("Reg4"));
block2.AddRegister(new DoubleWordRegister("Reg5"));
_blocks.Add(block2);
}
}
However I'm getting the following error:
Error 20 Argument '1': cannot convert from 'RegisterBlock<ByteRegister>' to 'RegisterBlock<IRegister>'
on the line _blocks.Add(block1) and similarly on _blocks.Add(block2);
I notice that you forgot to ask a question. You merely stated a bunch of facts. I'm going to assume your question is "why does the compiler produce this error?"
The compiler produces that error because to not produce that error would lead to a crash at runtime. Suppose we allowed:
List<RegisterBlock<IRegister> _blocks = new List<RegisterBlock<IRegister>>();
RegisterBlock<ByteRegister> block1= new RegisterBlock<ByteRegister>();
_blocks.Add(block1); // Illegal, but suppose it was legal.
Now what stops this?
RegisterBlock<IRegister> block1Again = _blocks[0];
Nothing. _blocks
is a list of RegisterBlock<IRegister>
, so of course _blocks[0]
is of type RegisterBlock<IRegister>
. But remember that of course the first item in the list is actually a RegisterBlock<ByteRegister>
.
Now what stops this?
block1Again.AddRegister(new DoubleWordRegister())?
Nothing. block1Again
is of type RegisterBlock<IRegister>
, which has a method AddRegister(IRegister)
, and DoubleWordRegister
implements IRegister
.
So you just put a double word register into a block that can only contain byte registers.
Clearly that is not safe. The only place where that can be made illegal at compile time is in the first step; the covariant conversion is not legal in the first place.
Incidentally, your question is often asked several times a day here. Twice so far this morning:
Implementing nested generic Interfaces
This is indeed a **variance problem. You will need another interface for your RegisterBlock
class, maybe IRegisterBlock
:
public class RegisterBlock<T> : IRegisterBlock
where T : IRegister
Then you can create a list of IRegisterBlock
:
private List<IRegisterBlock> _blocks;
I actually had a similar situation in our code-base last week, and this is exactly how I resolved it.
Only interfaces can be covariant or contravariant in C#, so you can't explicitly mark your RegisterBlock<>
covariant on T
the way you want.
However, you don't really need covariance in this case, you just need to declare your two collection objects as:
RegisterBlock<IRegister> block1= new RegisterBlock<IRegister>
Since both ByteRegister
and DoubleWordRegister
implement IRegister
you can add either of them to a RegisterBlock<IRegister>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With