Search code examples
c#genericscovariancecontravariance

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?


Solution

  • 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;
        }
    }