【问题标题】:Why doesn't generic methods work with inheritance and interfaces?为什么泛型方法不适用于继承和接口?
【发布时间】:2017-06-29 16:07:46
【问题描述】:

好的。我会简短。为什么这不起作用?

//Find<T> returns the T object with a given name
//Not a true method, but that's simplier for the example.
Warrior conan = Find<Warrior> ("Conan");
//Debug.Log is just printing the results
Debug.Log (conan is Warrior); //True
Debug.Log (conan is Character); //True, Warrior extends Character
Character other = Find<Character> ("Conan"); // Null, Why ?

我猜想通用 c# 方法在 IL 中是完全不同的,这就是为什么它不起作用。但这很烦人。我做错什么了吗?有没有办法绕过这个?

编辑:

其实我的方法有点不同。我正在使用MVC,我想找到与模型对应的视图。

public ElementView<E> Find<E> (E model) {

    ElementView<E>[] tab = gameBoardView.transform.GetComponentsInChildren<ElementView<E>> ();
    foreach (ElementView<E> el in tab)
        if (EqualityComparer<E>.Default.Equals(el.model, model))
            return el;
    return null;
}

我是这样使用它的:

ElementView<Warrior> w = Find<Warrior> (myCharacter); // good if my character is a warrior
ElementView<Character> c = Find<Character> (myCharacter); // null

【问题讨论】:

  • 添加 Find 的代码会有所帮助。
  • 请显示Find方法的来源。
  • 我敢打赌 conan.getType() == "Warrior" 和 Find 打破了这一点。看起来 Find 需要使用相同的 is 检查您的代码在此处执行的操作
  • 你知道什么是协变和逆变吗?如果没有,请先学习。关键的见解是:一碗苹果不能当一碗水果:你可以把一根香蕉放在一碗水果里,但不能放在一碗苹果里。一碗水果不能当成一碗苹果,因为你可以从一碗水果中取出一根香蕉,但不能从一碗苹果中取出。因此,碗既不逆变也不协变。在 C# 中,接口和委托可能是协变或逆变的,但类和结构从不是。
  • @F.Morival:阅读我的评论。如果您了解协变和逆变,那么您将知道问题的答案。借此机会了解一下,因为这是您问题的症结所在。

标签: c# generics inheritance interface


【解决方案1】:

如 cmets 中所述,如果您想研究泛型类型参数的变化,您可能可以自己回答这个问题。推荐阅读包括:

In C#, why can't a List object be stored in a List variable
C# variance problem: Assigning List as List
Contravariance explained
Difference between Covariance & Contra-variance
Eric Lippert 的Wizards and warriors 系列(此链接指向第 1 部分)

也就是说,为了解决您的紧迫问题并可能提供一些实用的、可操作的信息……

你的方法在这里中断:

ElementView<E>[] tab = gameBoardView.transform.GetComponentsInChildren<ElementView<E>> ();

具体来说,model 的类型是Warrior,它将在GetComponentsInChildren&lt;T&gt;() 搜索的集合中拥有ElementView&lt;Warrior&gt; 的实例。当你调用Find&lt;Character&gt;() 时,你调用了泛型方法GetComponentsInChildren&lt;Character&gt;(),它将搜索ElementView&lt;Character&gt; 的实例。

由于ElementView&lt;Warrior&gt; 不是ElementView&lt;Character&gt;——也就是说,它不继承该类型——它被排除在搜索之外。搜索将返回ElementView&lt;Character&gt; 的实例,其中不包括相关模型的视图。 (返回的集合实际上可能是空的,如果 Character 只是所有真实类型使用的基类……只有当 components 集合中有实际的 ElementView&lt;Character&gt; 对象时,您才能获得该方法返回的任何对象.)

您是否可以对此做任何事情取决于ElementView&lt;T&gt; 的类型以及您是否能够在代码中引入接口类型来表示ElementView&lt;T&gt; 对象。也就是说,当您请求 ElementView&lt;Character&gt; 类型的对象时,GetComponentsInChildren&lt;T&gt;() 返回 ElementView&lt;Warrior&gt; 类型的对象是合法的,前提是:

  1. 您可以更改代码以返回接口类型的集合而不是 ElementView&lt;T&gt; 类型(例如 IElementView&lt;T&gt;),并且
  2. 接口的类型参数T可以声明为协变;也就是说,使用out 关键字,表示接口的所有成员只返回 类型为T 的对象,而不接受它们作为参数。

如果这些都是真的,那么您可以声明适当的接口,确保ElementView&lt;T&gt; 实现该接口,然后可以将ElementView&lt;Warrior&gt; 类型的对象强制转换为接口类型IElementView&lt;Character&gt;。由于接口类型参数将是Character,并且接口实现只能返回Character 类型的对象,因此对象实际上返回Warrior 类型的对象就可以了。 Warrior (大概)是一个Character,因此返回一个Warrior 满足接口的返回Character 值的声明。

如果你能满足这些要求,那么GetComponentsInChildren&lt;T&gt;() 方法可以返回一个IElementView&lt;T&gt;[] 类型的数组,它实际上可以包含你感兴趣的对象,它实际上是ElementView&lt;U&gt; 类型,其中U 继承T (即GetComponentsInChildren&lt;Character&gt;() 将返回IElementView&lt;Character&gt;[],在其中找到IElementView&lt;Warrior&gt; 的实例是有效的)。

当然,您还需要更改使用这些类型的代码,包括GetComponentsInChildren&lt;T&gt;()(取决于其实现)。这不是一种简单的“抛出开关”类型的更改来支持泛型类型的变化。但假设您的类型与变体声明兼容,这可能是值得的。

我无法就如何进行这些更改提供任何具体建议,或者即使它们是可能的,因为您的问题不包括一个好的Minimal, Complete, and Verifiable code example。如果您想进行这些更改,我建议您先研究方差,然后自己努力更改代码。如果在那之后您仍然遇到问题,请发布一个新问题,确保包含一个良好的 MCVE,清楚地显示您正在尝试做什么,并准确解释您仍然遇到的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-05
    • 2013-01-11
    • 1970-01-01
    • 1970-01-01
    • 2023-02-21
    相关资源
    最近更新 更多