【问题标题】:Having trouble converting .NET generics lists to other generic lists将 .NET 泛型列表转换为其他泛型列表时遇到问题
【发布时间】:2014-04-13 05:25:20
【问题描述】:

我正在尝试模拟一个lastminute.com.au 克隆。当我尝试创建IProductSearchServices 的集合时,我得到所有关于(我猜)方差与协方差等的奇怪错误。

我认为如果任何具体类型实现相同的接口并且集合被强类型化到相同的接口,我可以任何具体类型(集合)。

这是我正在尝试做的代码。

private static void Main(string[] args)
{
    // Expected: 3 services in this list.
    var productServices = CreateProductServices();

    // Return all Hotel services.
    // Expected: 2 items in this list.
    var hotelServices = productServices.OfType<??????>();

    // for each hotel service, call SearchAsync.
    // wait for all the finish before continuing....

    Console.WriteLine("Found {0} products.", ....);
}

// Full code found in gist.... 

这是我的主要错误信息:

这里是full repo code in a Gist

那么问题出在哪里?

  1. 尝试将 3 个搜索服务添加到集合中。
  2. 鉴于该列表,我不知道该怎么说:给我所有的酒店服务,这样我就可以SearchAsync 他们。

注意:我不想使用dynamic 等。我想尝试将其用作强类型集合等。

【问题讨论】:

  • 这里的一个问题是Task&lt;T&gt; 是一个类,而不是一个接口。协方差只能在接口上实现,因此Task&lt;Derived&gt; 不能转换为Task&lt;Base&gt;。您可以通过使用IObservable 而不是Task 来解决此问题,Task 是协变的,并提供类似的功能。此外,IList 不是协变的,因此您可以改用IEnumerable
  • 然而,最大的问题是TSearchOptions,它是逆变的IProductSearchService&lt;T, HotelSearchOptions&gt; 可以分配给IProductSearchService&lt;T, ISearchOptions&gt; 是不正确的,因为前者需要SearchAsync 中的更多 特定参数,而不是不太特定的参数。因此,IProductSearchService 的列表没有意义。
  • 嗨@RuudvA - 谢谢你看这个。不过,我不明白你在说什么 :( 我并不是说你错了——我只是不明白。我认为我的 derivedHotelProductSearchService1 是两个泛型两者都是派生的。因此,如果两者都是派生的,那么两者都应该自动转换为它们的基数?我很困惑。有什么代码可以帮助吗?
  • 其他人已经提到协方差等。如果您需要在两种类型的列表之间进行转换,一种快速而肮脏的方法是使用“productServices.Cast()”() 可以抛出无效的强制转换异常,但不应该。

标签: c# .net interface


【解决方案1】:

由于 cmets 票价太有限,我将在答案中发布此内容。它可能无法解决您的问题,但我希望它能让您深入了解代码无法编译的原因。假设您有以下结构:

class Animal
{
  public void Eat();
}

class Cow : Animal
{
  public void Moo();
}

牛是more specific而不是动物。这意味着在任何需要动物的地方,您也可以插入牛。但是,当需要牛时,您不能插入动物,因为动物可能不是牛,并且不知道如何哞哞。

什么是协方差?

没有差异,IEnumerable&lt;Animal&gt;IEnumerable&lt;Cow&gt; 类型没有任何关系,但事实证明它们是:在需要 IEnumerable&lt;Animal&gt; 的地方,我们也可以插入 IEnumerable&lt;Cow&gt;。这是为什么?因为我们对IEnumerable&lt;Animal&gt; 唯一能做的就是从中提取动物。这就是协方差的语法为out 的原因,动物仅由IEnumerable&lt;Animal&gt; 返回。我们刚刚得出结论,我们应该能够在任何地方使用 Cow 而不是 Animal,所以如果可枚举返回 Cows 就可以了。从这个意义上说,我们期望IEnumerable&lt;Cow&gt; 派生自IEnumerable&lt;Animal&gt;:我们应该能够在需要IEnumerable&lt;Animal&gt; 的地方插入IEnumerable&lt;Cow&gt;。这就是协方差的作用。

什么是逆变?

同样,在没有差异的情况下,Action&lt;Animal&gt;Action&lt;Cow&gt; 类型不相关。和以前一样,事实证明它们是,因为一个接受 Animal 的方法也可以给一个 Cow。反过来说,一般情况下不能给带有 Cow 的方法赋予 Animal,所以在这种情况下,关系是相反的:Action&lt;Animal&gt; 应该是 Action&lt;Cow&gt; 的子类型,因为采用Animal 也是采用 Cow 的方法,但采用 Cow 的方法不是采用 Animal 的方法:它可能想在 Cow 上调用 Moo,而这对于 Animal 是不可能的。逆变的语法是in,因为逆变类型被用作参数;无法退货。

首先,考虑具有正确方差的IProductSearchService 的更简单版本:

public interface IProductSearchService<out TProduct, in TSearchOptions>
  where TProduct : IProduct
  where TSearchOptions : ISearchOptions
{
  TProduct SearchAsync(TSearchOptions searchOptions);
}

很明显TProduct 应该是协变的,因为它是返回的。 这意味着IProductSearchService&lt;HotelProduct, T&gt;IProductSearchService&lt;IProduct, T&gt;,但不是相反。 因此,IEnumerable&lt;IProductSearchService&lt;HotelProduct, T&gt;&gt;IEnumerable&lt;IProductSearchService&lt;IProduct, T&gt;&gt;,但反之则不然。

TSearchOptions 是逆变的,因为它是一个参数。 这意味着IProductSearchService&lt;T, ISearchOptions&gt;IProductSearchService&lt;T, HotelSearchOptions&gt;,但反之则不然。 因此,IEnumerable&lt;IProductSearchService&lt;T, ISearchOptions&gt;&gt;IEnumerable&lt;IProductSearchService&lt;T, HotelSearchOptions&gt;&gt;,但反之则不然。 我们可以枚举前者,并在结果上调用SearchAsyncHotelSearchOptions。但是如果我们枚举后者,SearchAsync 期望HotelSearchOptions,所以你不能插入ISearchOptions

如您所见,这些类型的方差是不兼容的,可以将IEnumerable&lt;IProductSearchService&lt;HotelProduct, ISearchOptions&gt;&gt; 分配给IEnumerable&lt;IProductSearchService&lt;IProduct, HotelSearchOptions&gt;&gt;,但反过来就没有意义。

嵌套协方差

IProductSearchServiceout参数如何修复?您必须确保所有嵌套的泛型都是协变的。仅接口支持方差,因此您必须将ProductResult 替换为IProductResult。另外,Task 不是协变的(因为它是一个类),所以你不能使用TaskIObservable 提供了类似的功能,并且这个接口协变的。

public interface IProductResult<out T> where T : IProduct { ... }

public interface IProductSearchService<out TProduct, in TSearchOptions>
  where TProduct : IProduct
  where TSearchOptions : ISearchOptions
{
  IObservable<IProductResult<TProduct>> SearchAsync(TSearchOptions searchOptions);
}

【讨论】:

  • 哦,哇!一个很棒的答案,我真的可以摸索!好的 - 这 - 真的 - 现在帮助我理解差异。呸!谢谢你。所以我尝试了你所说的..我仍然得到一个编译时错误。错误截图:i.imgur.com/NQF9vkq.png :~~(
  • 那是因为IList&lt;T&gt; 中的T 不是协变的。 IList&lt;Cow&gt; 不是 IList&lt;Animal&gt;,因为在 IList&lt;Cow&gt; 上使用 Animal 调用 Add 是不可能的。您必须使用IEnumerable。此外,该属性必须是只能获取的。这又是因为out 类型只能返回。您仍然可以在接口的实现中使用 setter。
  • 我没有得到最后一点不起作用。试图将 2x 酒店 + 1x 航班服务添加到集合中。该集合是List&lt;IProductSearchService&lt;IProduct, ISearchOptions&gt;&gt; 的列表,对我来说是List&lt;Animal&gt; 所以.. 因此.. 我应该能够在该集合中添加酒店(即牛)或航班(即狗)。该系列(对我而言)只是在说:我想拥有任何至少IAnimal 的东西。 CowsDogs 都是 IAnimals 所以可以进入同一个桶/集合...(续)..
  • (..cont) 当然,我们唯一的共同点就是Eat(),除非你特别指定它。但这不是编译 :( 所以我想我还是不明白 :( 帮助?
  • 不,IProductSearchService&lt;IProduct, HotelSearchOptions&gt; 不是 IProductSearchService&lt;IProduct, ISearchOptions&gt;:你不能在SearchAsync 中的每个ISearchOption 都传递它,它必须是HotelSearchOptions 的子类型,而ISearchOptions 是一个超类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多