Thomas Cook
Thomas Cook

Reputation: 4863

Contra/Covariance With Generics - Can't Assign to T

Code:

public interface IAssignable
{
    IAssignable AssignMe { get; set; }
}

public class GenericAssignExample<T> where T : IAssignable
{
    private T _assignable;

    public void Valid(T toAssign)
    {
        _assignable.AssignMe = toAssign;
    }

    public void AlsoValid(T toAssign)
    {
        _assignable.AssignMe = toAssign.AssignMe;
    }

    public void AlsoValidAsWell(T toAssign)
    {
        _assignable = toAssign;
    }

    public void Invalid(T toAssign)
    {
        _assignable = toAssign.AssignMe;
    }
}

Problem:

In final example method _assignable (which is T where T is IAssignable) cannot be assigned the value toAssign.AssignMe (which is of type IAssignable), the compiler throws

"Cannot implicitly convert type 'IAssignable' to 'T'".

Why is this? T is IAssignable so surely an instance of IAssignable can be assigned to it?

Upvotes: 2

Views: 115

Answers (3)

Luiso
Luiso

Reputation: 4113

I thinks I know where it is that you are confused, note here the difference

public interface IAssignable
{
    IAssignable AssignMe { get; set; }
}

public class GenericAssignExample
{
    private IAssignable _assignable;

    public void Valid(IAssignable toAssign)
    {
        _assignable.AssignMe = toAssign;
    }

    public void AlsoValid(IAssignable toAssign)
    {
        _assignable.AssignMe = toAssign.AssignMe;
    }

    public void AlsoValidAsWell(IAssignable toAssign)
    {
        _assignable = toAssign;
    }

    public void Invalid(IAssignable toAssign)
    {
        _assignable = toAssign.AssignMe;
    }
}

now it should work as you intended. Notice however that here you are always working with the interface type, when you make a generic interface/class with constraints all you're doing is narrowing the types that can be used along your class.

In your Invalid method what is happening is that you are using an IAssignable object and assigning it to a T object, and even though T is IAssignable there is no warranty that toAssign.AssignMe is the same type, hence your error. In the modifications I made above you loose generics and then the actual type you're using is IAssignable so it all works fine.

Hope that helps

Upvotes: 0

nvoigt
nvoigt

Reputation: 77364

Why is this? T is IAssignable so surely an instance of IAssignable can be assigned to it?

Ah, no? T is not IAssignable, if it were, you would not need Generics. T just implements IAssignable.

Just because Button and TextBox inherit from Control (or in a fictional example implement IControl) does not mean you can assign a TextBox to a Button or vice versa.


public interface IAssignable
{
    IAssignable AssignMe { get; set; }
}

public class A : IAssignable
{
    public IAssignable AssignMe { get; set; }
}

public class B : IAssignable
{
    public IAssignable AssignMe { get; set; }
}

// You will be able to instantiate this class with either A or B and both must be valid
public class GenericAssignExample<T> where T : IAssignable
{
    // here, T refers to either A or B.
    private T _assignable;

    public void Valid(T toAssign)
    {
        // assigning either an A or B to the interface... 
        // always valid, both implement it
        _assignable.AssignMe = toAssign;
    }

    public void AlsoValid(T toAssign)
    {
        // assigns interface to interface. Always valid.
        _assignable.AssignMe = toAssign.AssignMe;
    }

    public void AlsoValidAsWell(T toAssign)
    {
        // assigns either an A to an A
        // or a B to a B.
        // both always valid.
        _assignable = toAssign;
    }

    public void Invalid(T toAssign)
    {
        // this tries to assign an interface to either an A or a B
        // always invalid.
        _assignable = toAssign.AssignMe;
    }
}

Upvotes: 1

Adam Jachocki
Adam Jachocki

Reputation: 2124

Just cast it to IAssingable:

_assignable.AssignMe = (IAssignable)toAssign;

Upvotes: 0

Related Questions