类层次结构问题(具有泛型差异!)
问题:
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
算法(再次!),因为它很容易出错并且会导致代码重复。也许我可以定义一个抽象链,然后从那里实现 TasksChain
和 StatesChain
?或者也许实现一个 Chain
类?
你会如何处理这种情况?
详细信息: 我定义了一个 ITask
接口:
public interface ITask {
bool Run();
ITask FailureTask { get; }
}
以及一个继承自 ITask
的 IState
接口:
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
,它继承自 IState
和 IHasStateList
(并且这也实现了 >ITask
和 IHasTaskList
,作为显式接口实现),我希望它也隐藏 IHasTaskList
的 Tasks
并仅显示 < code>IHasStateList 的 States
。 (“问题”部分中包含的内容确实应该在这之后,但我认为将其放在前面会更方便读者阅读)。
(pff..长文本) 谢谢!
The problem:
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>.
}
}
Assuming the IList
returned will be read-only, I know that what I'm trying to achieve is safe (or is it not?). Is there any way I can accomplish what I'm trying? I wouldn't want to try to implement myself the TasksChain
algorithm (again!), as it would be error prone and would lead to code duplication. Maybe I could just define an abstract Chain and then implement both TasksChain
and StatesChain
from there? Or maybe implementing a Chain<T>
class?
How would you approach this situation?
The Details:
I have defined an ITask
interface:
public interface ITask {
bool Run();
ITask FailureTask { get; }
}
and a IState
interface that inherits from ITask
:
public interface IState : ITask {
IState FailureState { get; }
}
I have also defined an IHasTasksList
interface:
interface IHasTasksList {
List<Tasks> Tasks { get; }
}
and an IHasStatesList
:
interface IHasTasksList {
List<Tasks> States { get; }
}
Now, I have defined a TasksChain
, that is a class that has some code logic that will manipulate a chain of tasks (beware that TasksChain
is itself a kind of ITask
!):
class TasksChain : ITask, IHasTasksList {
IList<ITask> tasks = new List<ITask>();
...
public List<ITask> Tasks { get { return _tasks; } }
...
}
I am implementing a State
the following way:
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; }
}
}
which, as you can see, makes use of explicit interface implementations to "hide" FailureTask
and instead show FailureState
property.
The problem comes from the fact that I also want to define a StatesChain
, that inherits both from IState
and IHasStateList
(and that also imples ITask
and IHasTaskList
, implemented as explicit interfaces) and I want it to also hide IHasTaskList
's Tasks
and only show IHasStateList
's States
. (What is contained in "The problem" section should really be after this, but I thought puting it first would be way more reader friendly).
(pff..long text) Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
简而言之,不,它不安全,因为“只读”
IList<>
不存在(合约方面)。只有实现会拒绝条目,但这已经太晚了,因为调用本身需要接口类型参数同时是协变和逆变的。不过,您可以返回一个
IEnumerable<>
,它在 C# 4 中是协变的。由于这足以使用 LINQ,因此这不应该是太大的缺点,并且表示了可读性:只有自然更好。In short, no it is not safe, since a "read-only"
IList<>
doesn't exist (contract-wise). It's only the implementation which will reject entries, but that's too late, as the call itself would require the interface type parameter to be both co- and contravariant at the same time.You could, however, return an
IEnumerable<>
instead, which is covariant in C# 4. Since this is enough to use LINQ, that shouldn't be too much of a drawback and expresses the read-only nature better.在出现错误的行上,您尝试返回
IList
,就好像它是IList
类型的实例一样。这不会自动工作,因为两种类型不同(无论通用参数是否相关)。在 C# 3.0 或更早版本中,无法自动实现这一点。 C# 4.0 添加了对协变和逆变的支持,这正是这个目的。但正如您所指出的,这仅在返回的集合为只读时才有效。
IList
类型不能保证这一点,因此它在 .NET 4.0 中不会被注释为协变。要使用 C# 4.0 完成此工作,您需要使用真正的只读类型,它在框架中具有协变注释 - 您的情况的最佳选择是
IEnumerable< /code> (尽管您可以使用
out T
修饰符定义自己的)。要添加更多详细信息,在 C# 4.0 中,您可以将接口声明为协变或逆变。第一种情况意味着编译器将允许您执行示例中所需的转换(另一种情况对于只写类很有用)。这是通过向接口声明添加显式注释来完成的(这些注释已可用于 .NET 4.0 类型)。例如,
IEnumerable
的声明具有out
注释,这意味着它支持协变:现在,编译器将允许您编写:
On the line where you get an error, you're trying to return
IList<IStates>
as if it was an instance of typeIList<ITask>
. This doesn't work automtaically, because the two types are different (no matter that the generic parameters are related).In C# 3.0 or older, there is no way to achieve that automatically. C# 4.0 adds support for covariance and contravariance, which serves exactly this purpose. But as you noted, this works only when the returned collection is read-only. The
IList<T>
type doesn't guarantee that, so it isn't annotated as covariant in .NET 4.0.To make this work using C# 4.0, you'll need to use truly read-only type, which has covariant annotation in the framework - the best option in your case is
IEnumerable<T>
(although you could define your own using theout T
modifier).To add more details, in C# 4.0, you can declare an interface as either covariant or contra-variant. The first case means that the compiler will allow you to perform the conversion you needed in your example (the other case is useful for write-only classes). This is done by adding explicit annotations to the interface declaration (these are already available for .NET 4.0 types). For example, the declaration of
IEnumerable<T>
has theout
annotation meaning that it supports covariance:Now, the compiler will allow you to write: