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;