C#不可分配类型 - 泛型

6

我只是在研究一个状态机类型,想尝试一下Activator.CreateInstance方法,看看它是什么样子的,但我遇到了一个问题:我似乎无法像我想象中那样使用“where”子句。如果我很傻,每个人都会嘲笑我,我先提前道歉。我有两个小类。

public class TransitionContainer<TTransition, TStateTo> :
    ITransitionContainer<TTransition, TStateTo>
    where TTransition : ITransition
    where TStateTo : IState
{
    public TransitionContainer()
    {
        StateTo = typeof(TStateTo);
        Transition = Activator.CreateInstance<TTransition>();
    }

    public Type StateTo { get; private set; }

    public TTransition Transition { get; private set; }
}

除此之外

  public class StateContainer<T> : IStateContainer<T> where T : IState
{
    private Dictionary<Type, TransitionContainer<ITransition, IState>> _transitions =
        new Dictionary<Type, TransitionContainer<ITransition, IState>>();

    public StateContainer()
    {
        State = Activator.CreateInstance<T>();
    }

    public T State { get; private set; }

    public int TransitionCount
    {
        get { return _transitions.Count; }
    }


    public void AddTransition<TTransition, TStateTo>() where TTransition : ITransition, new()
        where TStateTo : IState, new()
    {
        var transitionContainer= new TransitionContainer<TTransition, TStateTo>();

        _transitions.Add(typeof(TTransition), transitionContainer);
    }

在代码行 _transitions.Add(typeof(TTransition), transitionContainer); 中,我收到了一个错误信息:无法将 TransitionContainer<TTransition,TStateTo> 表达式转换为类型 TransitionContainer<ITransition,IState>
如果我更改泛型参数为:
var transitionContainer= new TransitionContainer<ITransition, IState>();

它可以正常地工作,但我想使用新的继承类型(new()),以确保我可以实例化它们。

如果我做了什么极其错误的事情,请再次原谅我。我只是遇到了困境,我的搜索没有得到很好的指引。我没有包含任何其他接口或类,因为它们似乎不是问题的一部分,但如果需要,我可以附上它们. 谢谢您的帮助!

2个回答

4
这个问题的原因是:
1. `ITransitionContainer` 不是其类型参数的协变接口。 2. `AddTransition` 方法的泛型参数没有被约束引用类型。 3. `_transitions` 不是一个以`ITransitionContainer` 值为字典,所以即使我们正确地限制了协变转换,如果不将其更改为`Dictionary >`,我们仍然无法添加。

简化示例

考虑以下简化案例:
public interface ITransition
{

}

public class SomeTransition : ITransition
{

}

public interface ITest<TTransition>
    where TTransition : ITransition
{
    TTransition Value { get; }
}


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

它将在两者中失败

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

ITest<ITransition> item = new SomeTest<SomeTransition>();

如果您使 ITest 可变,则...
public interface ITest<out TTransition>

如果在泛型方法中,TTransition 是一个结构体,那么它只会失败。因为这里的协变/逆变不适用于值类型

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

但如果你将该方法限制为只引用类型, 那么它就可以在两种情况下工作:

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

将相同的原则(outclass)应用于您的两个泛型参数,它就会完成工作。


您特定情况的完整解决方案:

public interface IState
{    }

public interface ITransition
{    }

// !!!!! - Here we add out specifier
public interface ITransitionContainer<out TTransition, out TStateTo>
    where TTransition : ITransition
    where TStateTo : IState
{
    Type StateTo
    {
        get;
    }

    TTransition Transition
    {
        get;
    }
}

public interface IStateContainer<T> where T : IState
{
    T State
    {
        get;
    }
}


public class TransitionContainer<TTransition, TStateTo> : ITransitionContainer<TTransition, TStateTo>
    where TTransition : ITransition
    where TStateTo : IState
{
    public TransitionContainer()
    {
        StateTo = typeof(TStateTo);
        Transition = Activator.CreateInstance<TTransition>();
    }

    public Type StateTo { get; private set; }

    public TTransition Transition { get; private set; }
}


public class StateContainer<T> : IStateContainer<T> where T : IState
{
    private Dictionary<Type, ITransitionContainer<ITransition, IState>> _transitions =
        new Dictionary<Type, ITransitionContainer<ITransition, IState>>();

    public StateContainer()
    {
        State = Activator.CreateInstance<T>();
    }

    public T State { get; private set; }

    public int TransitionCount
    {
        get { return _transitions.Count; }
    }

    public void AddTransition<TTransition, TStateTo>()
        // !!!!!! - Here we add class constraints
        where TTransition : class, ITransition, new()
        where TStateTo : class, IState, new()
    {
        var transitionContainer = new TransitionContainer<TTransition, TStateTo>();

        _transitions.Add(typeof(TTransition), transitionContainer);
    }
}

那么他应该将 whereout 添加到 interface ITransitionContainer 的定义中,并将 class 添加到 AddTransition 方法中吗?我尝试过了,但仍然不起作用。 - Andrew
嘿,谢谢回答。就像安德鲁说的那样,我尝试过了,但仍然遇到了相同的错误。我一直在查看您链接的Stack Overflow链接和文档,所以我会尽力解决问题! - Travis Scott
@TravisScott 我提供了一个所谓的完整解决方案,在.NET 4.6.1和VS2015下编译和执行都很好-这个对你不起作用吗?也许您可以创建一个最小化且可验证的完整示例,以确保没有任何遗漏会影响结果? - Eugene Podskal
抱歉 @EugenePodskal,那是我的错,我现在明白了。我以为我已经仔细检查过了,但我需要将字典从TransitionContainer切换到ITransitionContainer。当我创建一个新项目并尽可能少地编写时,我注意到了这一点。很抱歉没有更仔细地跟进,最小完整可验证示例似乎完美地运行了。 :P 感谢您的所有帮助!! - Travis Scott
作为一个注意事项,当_transitions.Add(typeof(TTransition), transitionContainer);运行时,我会收到一个System.ArrayTypeMismatchException:无法将源数组类型分配给目标数组类型。 我会看看我能做什么。 - Travis Scott
@TravisScott关于使用接口替换具体类-额,那实际上是我的错-我忘记提到字典应该使用接口-否则方差会出问题。至于你的ArrayTypeMismatchException,你可能需要另外提问,只需最小化完整可验证示例 - Eugene Podskal

2
那会失败,因为泛型不是协变的。问题可以在这里看到:
TransitionContainer<ITransition, IState> value = new TransitionContainer<TTransition, TStateTo>();

这会给你相同的错误。即使是像下面这样简单的东西也会出现此错误:

List<IComparable> list = new List<DateTime>();

Visual Studio告诉您(基本上)以下内容:

Cannot implicitly convert type 'List<System.DateTime>' to 'List<System.IComparable>'

你需要做的是将对象转换。你可以创建一个返回TransitionContainer<ITransition, IState>Convert 方法,然后使用 .Add(typeof(TTransition), transitionContainer.Convert()) (或者你喜欢的其他名称)。
但是最简单的方式是为你的TransitionContainer<TTransition, TStateTo> 对象创建隐式转换,通过添加这个静态方法实现:
public static implicit operator TransitionContainer<ITransition, IState>(TransitionContainer<TTransition, TStateTo> value)
{
    return new TransitionContainer<ITransition, IState>() { StateTo = value.StateTo, Transition = value.Transition };
}

就是这样啦。:)

当然,你需要复制所有必需的内容才能使其正常工作,在这种情况下,似乎这两个对象就足够了。


哦,好的,很有趣。我一定会去看看! - Travis Scott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接