【问题标题】:Class hierarchy problem (with generic's variance!)类层次结构问题(具有泛型的方差!)
【发布时间】:2010-04-26 23:31:06
【问题描述】:

问题:

class StatesChain : IState, IHasStateList {
    private TasksChain tasks = new TasksChain();

     ...

    public IList<IState> States {
        get { return _taskChain.Tasks; }
    }

    IList<ITask> IHasTasksCollection.Tasks {
        get { return _taskChain.Tasks; } <-- ERROR! You can't do this in C#!
                                             I want to return an IList<ITask> from
                                             an IList<IStates>.
    }
}

假设返回的IList 是只读的,我知道我想要实现的是安全的(或者不是?)。有什么办法可以完成我正在尝试的事情吗?我不想尝试自己实现TasksChain 算法(再次!),因为它容易出错并且会导致代码重复。也许我可以定义一个抽象链,然后从那里实现TasksChainStatesChain?或者也许实现一个Chain&lt;T&gt; 类?

你会如何处理这种情况?

详情: 我已经定义了一个ITask 接口:

public interface ITask {
    bool Run();
    ITask FailureTask { get; }
}

还有一个继承自ITaskIState 接口:

public interface IState : ITask {
    IState FailureState { get; }
}

我还定义了一个IHasTasksList 接口:

interface IHasTasksList {
    List<Tasks> Tasks { get; }
}

还有一个IHasStatesList

interface IHasTasksList {
    List<Tasks> States { get; }
}

现在,我定义了一个TasksChain,这是一个具有一些代码逻辑的类,可以操作一系列任务(注意TasksChain 本身就是一种ITask!):

class TasksChain : ITask, IHasTasksList {
    IList<ITask> tasks = new List<ITask>();

    ...

    public List<ITask> Tasks { get { return _tasks; } }

    ...
}

我正在通过以下方式实现State

public class State : IState {
    private readonly TaskChain _taskChain = new TaskChain();

    public State(Precondition precondition, Execution execution) {
        _taskChain.Tasks.Add(precondition);
        _taskChain.Tasks.Add(execution);
    }

    public bool Run() {
        return _taskChain.Run();
    }

    public IState FailureState {
        get { return (IState)_taskChain.Tasks[0].FailureTask; }
    }

    ITask ITask.FailureTask {
        get { return FailureState; }
    }
}

如您所见,它使用显式接口实现来“隐藏”FailureTask,而是显示FailureState 属性。

问题在于我还想定义一个StatesChain,它继承自IStateIHasStateList(并且还实现了ITaskIHasTaskList,实现为显式接口)和我希望它也隐藏IHasTaskListTasks 并且只显示IHasStateListStates(“问题”部分包含的内容应该在此之后,但我认为把它放在第一位会更便于读者阅读)。

(pff..长文本)谢谢!

【问题讨论】:

    标签: c# generics oop generic-variance


    【解决方案1】:

    简而言之,不,这不安全,因为“只读”IList&lt;&gt; 不存在(合同方面)。只有实现会拒绝条目,但为时已晚,因为调用本身需要接口类型参数同时是协变和逆变的。

    但是,您可以返回一个 IEnumerable&lt;&gt;,它在 C# 4 中是协变的。因为这足以使用 LINQ,所以这不应该是太大的缺点,并且可以更好地表达只读性质。

    【讨论】:

    • 我一直希望Collections.Generic 接口的层次结构包含只读变体。协方差是另一个很好的原因。但是因为几乎所有的集合接口都是可写的,所以我们无法获得协方差。啊!...
    • @romkyns,BCL 中还有其他几个“缺失”接口,例如数字基元上的IArithmetic&lt;&gt;(通过使用接口作为类型约束来启用通用数学运算),ICharSequence (或类似于启用任何字符序列,例如用作RegexStringReader 源而不是字符串)等等...
    【解决方案2】:

    在出现错误的那一行,您尝试返回IList&lt;IStates&gt;,就好像它是IList&lt;ITask&gt; 类型的实例一样。这不会自动工作,因为这两种类型是不同的(无论泛型参数是否相关)。

    在 C# 3.0 或更早版本中,无法自动实现这一点。 C# 4.0 增加了对协变和逆变的支持,正是为了达到这个目的。但正如您所指出的,这仅在返回的集合是只读的时才有效。 IList&lt;T&gt; 类型不能保证这一点,因此在 .NET 4.0 中它没有被注释为 covariant

    要使用 C# 4.0 完成这项工作,您需要使用 真正的只读类型,它在框架中具有协变注释 - 在您的情况下,最好的选择是 IEnumerable&lt;T&gt;(尽管您可以使用out T 修饰符定义自己的)。

    要添加更多细节,在 C# 4.0 中,您可以将接口声明为协变或逆变。第一种情况意味着编译器将允许您执行示例中所需的转换(另一种情况对只写类很有用)。这是通过向接口声明添加显式注释来完成的(这些已经可用于 .NET 4.0 类型)。比如IEnumerable&lt;T&gt;的声明有out注解意味着它支持协方差:

    public interface IEnumerable<out T> : IEnumerable { /* ... */ }
    

    现在,编译器将允许您编写:

    IEnumerable<IState> states = ...
    IEnumerable<ITask> tasks = states;
    

    【讨论】:

    • 感谢您的回答。什么定义了 C# 眼中真正的只读类型?允许 IEnumerable 而不允许 IList 背后的机制是什么?
    • 它是显式的(在类型参数上定义 inout 符号),而不是隐式的。谷歌为以下查询返回了一堆很好的结果:google.ch/search?q=covariant+contravariant+in+out
    • 对不起,我不明白你的意思。它是 C# 4.0 的新内容吗?谢谢
    • 是的,较旧的 C# 版本除了数组和类型引用外,不允许对任何东西进行协变和/或逆变(即使是强硬的 .NET Framework 运行时据说自引入泛型 V2 以来就一直支持它)。
    • 我添加了更多细节——我不确定你是否已经在谈论 C# 4.0(因为你的问题明确提到了方差——这通常是一个非常陌生的概念!)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-14
    • 2013-03-20
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多