【问题标题】:How do I assign an IList<T1> to an IList<T2> where T1 is a subtype of T2?如何将 IList<T1> 分配给 T1 是 T2 的子类型的 IList<T2>?
【发布时间】:2014-08-03 00:30:36
【问题描述】:

前言:

我在问题 C#'s equivalent of Java's <? extends Base> in generics 中阅读了有关协变和逆变的内容,但将我的项目切换到 .net 4.0 似乎没有奏效。


考虑以下类构造函数:

public Matrix(IList<RowVector> rows) {
    if (!Vector.AreDimensionsEqual(rows)) {
        throw new ArgumentException("foo");
    }

    for (int i = 0; i < rows.Length; ++i) {
        for (int j = 0; j < rows[0].Length; ++j) {
            Components[i][j] = rows[i].Components[j];
        }
    }
}

和方法AreDimensionsEqual(IList&lt;Vector&gt; vectors):

public static bool AreDimensionsEqual(IList<Vector> vectors) {
    int dimensions = vectors[0].Dimension;

    for (int i = 1; i < vectors.Count; ++i) {
        if (vectors[i].Dimension != dimensions) {
            return false;
        }
    }

    return true;
}

这里,Matrix 构造函数中的rowsRowVector 对象的列表,我试图将其用作需要Vector 对象列表的方法调用的参数。 RowVectorVector 的子类型。

我最熟悉Java,所以如果我在那里编程,我会将AreDimensionsEqual 的方法签名定义为AreDimensionsEqual(IList&lt;? extends Vector&gt; vectors),但这种语法在C# 中不可用。

我还有什么遗漏的吗?

【问题讨论】:

  • 你不能。您必须将其转换为其他类型的列表。
  • 如何使用IEnumerable&lt;Vector&gt; 作为AreDimensionsEqual 方法的参数(然后在其主体中使用foreach 循环或Linq 表达式...)?
  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。
  • Java 和 C# 的差异以不同的方式完成。 Java 有使用点差异,而 C# 有声明点差异。两者不等价。 Use-site variance is strictly more powerful than declaration-site variance, 所以会有一些模式可以在 Java 中使用,但在 C# 中没有等效形式。

标签: c# generics inheritance interface


【解决方案1】:

IList 中的泛型类型参数T 不是协变的。

但是IEnumerable<out T> 中的泛型类型参数T 是协变的(注意out keyword)。

因此,您可以更改您的 AreDimensionsEqual 方法以使用 IEnumerable 参数:

    public static bool AreDimensionsEqual(IEnumerable<Vector> vectors)
    {
        int dimensions = vectors.First().Dimension;
        return vectors.All(v => v.Dimension == dimensions);
    }

给出的代码在功能上等同于问题中所示的 AreDimensionsEqual 方法。

【讨论】:

  • 这是一个非常有趣的解决方案。我知道 LINQ,但还没有花时间去尝试理解它。不过,我注意到的一件事(我之前在另一种情况下使用 IEnumerable 时遇到过这种情况)是我收到了有关“可能对 IEnumerable 进行多重枚举”的警告,可能来自 Resharper。
  • @agent154,我的答案代码中的 LINQ 表达式在功能上等同于您问题中的代码。但我认为您的意图是检查任何两个向量是否具有相同的维度(或者您确实只想检查任何向量是否与 first 向量具有相同的维度?)
  • Resharper 会在您错误地使用 Linq 导致应用程序变慢时发出警告。有时警告是错误的,因为 Resharper 无法真正理解程序上下文 - 但无论如何都应该仔细检查这个警告是否得到证实或只是一个误报...... :)
  • 你的假设是正确的。我想确保所有向量都具有相同的维度。如果不是,则失败。
  • @agent154 R# 会就此发出警告,因为IEnumerable&lt;T&gt; 可以是任何东西。例如,枚举它可能会执行 SQL 查询以产生结果。 ReSharper 不知道可枚举的背后是什么,只是假设您不应该在默认情况下多次枚举它。在其上调用myEnumerable = myEnumerable.ToList(); 枚举一次,然后重复使用。请注意,ICollection&lt;T&gt;/IReadOnlyCollection&lt;T&gt; 可以安全地枚举多次。
【解决方案2】:

您不能这样做,因为IList 在其通用参数中是不变的。作为为什么不能这样做的示例,请考虑以下类层次结构:

class A
{
}

class B : A
{
}

class C : A
{
}

现在想象以下工作:

IList<A> listOfA = new List<B>();
listOfA.Add( new C() );

我们刚刚将C 类型的项目添加到真正 类型为List&lt;B&gt; 的集合中。我们不能那样做。但据listOfA.Add 所知,这只是A 的列表,而C 添加到A 的列表中是完全合理的。

【讨论】:

  • 我不明白为什么这不可能合理...如果 C 扩展了 B,而 B 扩展了 A,那么人们会期望,如果您列出 A 对象,那么无论使用什么方法你调用的 A 应该同时存在于 B 和 C 中。
  • @agent154 在他的例子中listOfA的类型实际上是List&lt;B&gt;
猜你喜欢
  • 2018-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-07
  • 1970-01-01
  • 2014-08-26
相关资源
最近更新 更多