Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot convert from generic type to interface

Tags:

c#

generics

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);

like image 727
canice Avatar asked Apr 27 '12 15:04

canice


3 Answers

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

like image 51
Eric Lippert Avatar answered Nov 13 '22 05:11

Eric Lippert


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.

like image 40
mellamokb Avatar answered Nov 13 '22 07:11

mellamokb


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>

like image 2
Michael Edenfield Avatar answered Nov 13 '22 06:11

Michael Edenfield