C# Is Not Assignable Type - Generics

后端 未结 2 954
粉色の甜心
粉色の甜心 2021-01-18 13:42

So I\'m just hacking around with a state machine type I was working on and mostly wanting to just try out the Activator.CreateInstance method to see what it was like, and I

2条回答
  •  有刺的猬
    2021-01-18 13:54

    This issue happens because:

    1. ITransitionContainer is not a covariant interface over its type arguments.
    2. AddTransition method generic arguments are not constrained to be reference types.
    3. _transitions is not a dictionary with ITransitionContainer values, so without changing it to Dictionary> we still won't be able to add even properly resticted covariant transtions.

    Simplified example

    Consider the following simplified case:

    public interface ITransition
    {
    
    }
    
    public class SomeTransition : ITransition
    {
    
    }
    
    public interface ITest
        where TTransition : ITransition
    {
        TTransition Value { get; }
    }
    
    
    public class SomeTest : ITest
        where TTransition : ITransition
    {
        public TTransition Value
        {
            get
            {
                throw new NotImplementedException();
            }
        }
    }
    

    It will fail in both

    public static void Do()
        where TTransition : ITransition
    {
        ITest item = new SomeTest();
    }
    

    and

    ITest item = new SomeTest();
    

    If you make ITest covariant

    public interface ITest
    

    , then it will fail only in generic method. Because here TTransition can be a struct and co/(contra)variance doesn't work with value types:

    public static void Do()
        where TTransition : ITransition
    {
        ITest item = new SomeTest();
    }
    

    But if you make that method constrained to only reference types, then it will work in both cases:

    public static void Do()
        where TTransition : class, ITransition
    {
        ITest item = new SomeTest();
    }
    

    Apply the same principle(out and class) to your two generic arguments and it will do the job.


    Full solution for your specific case:

    public interface IState
    {    }
    
    public interface ITransition
    {    }
    
    // !!!!! - Here we add out specifier
    public interface ITransitionContainer
        where TTransition : ITransition
        where TStateTo : IState
    {
        Type StateTo
        {
            get;
        }
    
        TTransition Transition
        {
            get;
        }
    }
    
    public interface IStateContainer where T : IState
    {
        T State
        {
            get;
        }
    }
    
    
    public class TransitionContainer : ITransitionContainer
        where TTransition : ITransition
        where TStateTo : IState
    {
        public TransitionContainer()
        {
            StateTo = typeof(TStateTo);
            Transition = Activator.CreateInstance();
        }
    
        public Type StateTo { get; private set; }
    
        public TTransition Transition { get; private set; }
    }
    
    
    public class StateContainer : IStateContainer where T : IState
    {
        private Dictionary> _transitions =
            new Dictionary>();
    
        public StateContainer()
        {
            State = Activator.CreateInstance();
        }
    
        public T State { get; private set; }
    
        public int TransitionCount
        {
            get { return _transitions.Count; }
        }
    
        public void AddTransition()
            // !!!!!! - Here we add class constraints
            where TTransition : class, ITransition, new()
            where TStateTo : class, IState, new()
        {
            var transitionContainer = new TransitionContainer();
    
            _transitions.Add(typeof(TTransition), transitionContainer);
        }
    }
    

提交回复
热议问题