C# 类层次结构问题(具有泛型';的方差!)

C# 类层次结构问题(具有泛型';的方差!),c#,generics,oop,generic-variance,C#,Generics,Oop,Generic Variance,问题: class StatesChain : IState, IHasStateList { private TasksChain tasks = new TasksChain(); ... public IList<IState> States { get { return _taskChain.Tasks; } } IList<ITask> IHasTasksCollection.Tasks {

问题:

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>.
    }
}
以及从ITask继承的
IState
接口:

public interface IState : ITask {
    IState FailureState { get; }
}
我还定义了一个
IHasTasksList
接口:

public interface ITask {
    bool Run();
    ITask FailureTask { get; }
}
interface IHasTasksList {
    List<Tasks> Tasks { get; }
}
现在,我定义了一个
TasksChain
,它是一个具有一些代码逻辑的类,可以操作一系列任务(注意
TasksChain
本身就是一种
ITask
!):

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

问题来自这样一个事实:我还想定义一个
StatesChain
,它继承了
IState
IHasStateList
(这也意味着
ITask
IHasTaskList
,实现为显式接口)我希望它也隐藏
IHasTaskList
任务
,只显示
ihastatelist
状态
。(在“问题”一节中包含的内容实际上应该在这之后,但我认为先把它放在前面会更方便读者)


(pff..长文本)谢谢

简而言之,不,这是不安全的,因为“只读”IList不存在(合同方面)。只有实现会拒绝条目,但那太晚了,因为调用本身将要求接口类型参数同时为协变量和反变量


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

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

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

要使用C#4.0实现这一点,您需要使用真正的只读类型,它在框架中具有协变注释-在您的情况下,最好的选择是
IEnumerable
(尽管您可以使用
out T
修饰符定义自己的类型)

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

public interface IEnumerable<out T> : IEnumerable { /* ... */ }
公共接口IEnumerable:IEnumerable{/*…*/}
现在,编译器将允许您编写:

IEnumerable<IState> states = ...
IEnumerable<ITask> tasks = states;
IEnumerable状态=。。。
IEnumerable任务=状态;

谢谢你的回答。在C#眼中,什么是真正的只读类型?允许IEnumerable而不允许IList的机制是什么?它是显式的(在类型参数上定义
in
out
符号),而不是隐式的。谷歌为下面的查询返回了一堆很棒的结果:对不起,我不明白你的意思。这对C#4.0来说是新事物吗?谢天谢地,较旧的C#版本除了数组和类型引用之外,不允许在任何东西上使用协变和/或逆变(甚至据说自从泛型V2引入以来,.NET Framework运行时就一直支持它)。我添加了更多的细节-我不确定您是否已经在谈论C#4.0(因为你的问题明确提到了差异——这通常是一个非常陌生的概念!)长期以来,我一直希望
集合的层次结构。通用的
接口包括只读变量。协方差是另一个很好的原因。但由于几乎所有集合接口都是可写的,我们无法获得协方差。Gah!…@romkyns,还有其他几个“缺失”BCL中的接口,如数值原语上的
IArithmetic
(通过使用接口作为类型约束来启用通用数学运算),
iCarSequence
(或类似的接口,以启用任何字符序列,例如用作
Regex
StringReader
源而不是字符串)等等。。。
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; }
    }
}
public interface IEnumerable<out T> : IEnumerable { /* ... */ }
IEnumerable<IState> states = ...
IEnumerable<ITask> tasks = states;