从实现的泛型转换为接口类型的泛型

本文关键字:泛型 接口类型 转换 实现 | 更新日期: 2023-09-27 18:26:37

可能是一个很容易的问题,但请查看以下类/接口:

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;

当然,这是行不通的。我明白为什么没有,但我完全不知道如何绕过它。

有什么想法吗?

UPDATE:我之前决定发布尽可能少的代码,但这给我带来了向你们隐瞒一个问题的麻烦:接口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(以及所有其他实现类)中还有更多的强制转换代码,但在我看来,这是目前唯一可行的方法。

从实现的泛型转换为接口类型的泛型

如果您使用c#4(或更高版本),您可以利用方差。尝试将IChallengeView<T>接口声明为协变,如下所示:

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

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

一个小提示:我建议在使用接口时,使用以下术语来具有所示含义:

  • 一个"可读"的foo接口应该提供一种读取对象特征的方法,但不应该承诺该对象是否可以使用其他方法写入。

  • "只读"foo接口不仅应该提供读取对象特征的方法,还应该承诺可以公开对任何合法实现的引用,而不公开对对象的写入方法。然而,没有任何承诺,即没有其他方法可以修改对象。

  • 一个"不可变"的foo接口应该承诺,任何被观察到具有给定值的属性都将始终具有该值。

如果代码需要简单地读出对象中的内容,它可以请求一个"IReadableFoo"。如果代码对短期封装数据使用对象引用,它想将该数据公开给其他代码,但不允许将对象本身公开给任何可能修改它的东西,它应该用只读包装器包装对象,除非它可以安全地直接公开对象(这将由实现IReadOnlyFoo的对象指示)。如果代码想持久化引用作为在其中持久化数据快照的一种方式,它应该在对象可能发生更改的情况下复制该对象,但如果对象始终相同,则不必担心(由IImmutableFoo指示)。