从实现的通用转换为接口类型的通用

时间:2012-03-05 12:17:06

标签: c# generics interface implementation

可能不费吹灰之力,但请查看以下类/接口:

public interface IChallenge
public interface IChallengeView<T> where T : IChallenge 
{
    T Challenge {get;set;}
}
public interface IChallengeHostView
{
    IChallengeView<IChallenge> ChallengeView { get; set; }
}

public class AdditionChallenge : IChallenge {}
public class AdditionChallengeView: IChallengeView<AdditionChallenge> {}

该场景是针对幼儿的教学应用。 我打算通过将主机(可能是任何图形环境)与要解决的挑战分开来保持应用程序的灵活性。这样我就可以使用相同的环境来主持加法,乘法,除法......

现在,当我想填补这个生命时,我会遇到转换问题:

HostView hostView = new HostView();  // implements IChallengeHostView
AdditionChallengeView challengeView = new AdditionChallengeView();
hostView.ChallengeView = challengeView;

当然,这不起作用。我明白为什么没有,但我不知道怎么解决这个问题。

有什么想法吗?

更新:我之前决定尽可能少地发布代码,但这让我陷入了向你们隐藏一个问题的麻烦:界面 IChallengeView 有一个可设置的属性(现在在上面的代码中可见),这使得协方差无法在这里应用 - 泛型类型参数在这种情况下只能是不变的。

rich.okelly给出的答案是正确的,但是基于错误的假设(再次,这是基于我在这里描述的详细程度不佳)。

我决定使代码稍微不那么实现类型粘合剂,如下:

public interface IChallenge
public interface IChallengeView
{
    IChallenge Challenge {get;set;}
}
public interface IChallengeHostView
{
    IChallengeView ChallengeView { get; set; }
}

public class AdditionChallenge : IChallenge {}
public class AdditionChallengeView: IChallengeView {}

这意味着我在AdditionChallengeView(以及所有其他实现类)中有更多的转换代码,但在我看来,这是当时唯一可行的方法。

2 个答案:

答案 0 :(得分:1)

如果您使用的是c#4(或更高版本),则可以利用差异。尝试声明您的IChallengeView<T>界面as covariant,如下所示:

public interface IChallengeView<out T> where T : IChallenge {}

答案 1 :(得分:0)

分离出以协变方式使用类型参数的接口部分和使用一种逆变型方式的接口部分通常很有用。这通常需要使用“SetProperty”方法而不是读写属性(无论出于何种原因,如果接口继承了包含只读属性Foo的接口,另一个实现了只读属性)属性Foo,编译器会说任何属性访问尝试都是“不明确的”,并且不允许读取或写入foo,尽管读取访问只能引用只读属性和对只写属性的写入访问。尽管如此,分离出接口的逆变和协变方面通常会允许在有用和有意义的情况下使用方差。此外,分离出接口的各个部分无论如何,读取对象通常都是有用的。

一个小注:我建议在使用界面时,使用以下术语来表示含义:

  • 一个“可读”的foo接口应该提供一种读取对象特征的方法,但是不应该使用其他一些方法来保证该对象是否可写。

  • “只读”foo接口不仅应该提供一种读取对象特征的方法,还应该承诺可以公开对任何合法实现的引用,而不会暴露写入的方法。宾语。但是,没有任何承诺,没有一些其他方法可以修改对象。

  • “不可变”的foo接口应该保证任何被观察到具有给定值的属性都将具有该值。

如果代码需要简单地读出对象中的内容,它可以请求“IReadableFoo”。如果代码使用对象引用来表示它想要暴露给其他代码的短期封装数据,但是不允许将对象本身暴露给可能修改它的任何东西,它应该将对象包装在只读包装器中除非它可以安全地直接暴露对象(这将由实现IReadOnlyFoo的对象指示。如果代码想要持久化引用作为在其中持久化数据快照的方法,它应该制作对象的副本如果它有可能发生变化,但如果对象总是相同的话,不应该打扰(由IImmutableFoo表示)。