【问题标题】:How to cast a generic collection to an generic collection of ancestors?如何将通用集合转换为祖先的通用集合?
【发布时间】:2012-01-10 20:53:19
【问题描述】:

考虑Label的列表:

Collection<Label> labels = new Collection<Label>();

现在我想将其转换为 Controls 的集合:

public void ScaleControls(ICollection<Control> controls) {}

我会尝试调用:

ScaleControls(labels);

但这不会编译。

ScaleControls((ICollection<Control>)labels);

编译,但在运行时崩溃。

ICollection<Control> controls = (ICollection<Control>)labels;
ScaleControls(c);

编译,但在运行时崩溃。

有没有办法传递对象的通用列表?


替代方法是放弃通用列表,并使用类型列表:

public class ControlList : Collection<Control>
{
}

pubic void InvalidateControls(ControlList controls)
{
}

但这意味着所有使用泛型的代码都必须进行改造。

【问题讨论】:

  • 您使用什么版本的 .Net?
  • 集合是否必须最初是 Label 类型,还是因为 label 继承自 Control,您可以摆脱 Control?
  • 您应该阅读协变和逆变,这可以解决您的问题。可悲的是,如果有任何内置集合正在使用它,那么很少。
  • @JoachimIsaksson 我对它们是什么有一个模糊的理解;但仅在“由于...协变和逆变,CLR 尚不支持您想要做的事情”的上下文中。 “解决我的问题”是不是意味着我会意识到我不能做我想做的事——所以没问题。或者如果我阅读更多关于协变和逆变的信息,有没有办法解决这个问题。
  • @Ian 我认为一些关于协变和逆变的材料解释了为什么从类型安全的角度来看你不应该能够做你要求做的事情。要点是,如果您被允许按照您的要求进行操作,那么您可能会编写代码来将 TextBox 控件添加到 List

标签: c# generics


【解决方案1】:

你不能施放它;你必须自己转换它。

InvalidateControls(new List<Control>(labels));  //C# 4 or later

您的问题是 ICollection&lt;T&gt; 不是协变的。 ICollection 不是协变的原因是为了防止这样的方法是邪恶的:

void AddControl(ICollection<Control> controls, Control control)
{
    controls.Add(control);
}

为什么会是邪恶的?因为如果 ICollection 是协变的,则该方法将允许您将 TextBoxes 添加到标签列表中:

AddControl(new List<Label>(), new TextBox());

List&lt;Control&gt; 有一个接受IEnumerable&lt;Control&gt; 的构造函数。为什么我们可以传入labels?因为IEnumerable&lt;T&gt; 是协变的:你不能把任何东西放入IEnumerable&lt;T&gt;;你只能把东西拿出来。因此,您知道可以将从 IEnumerable&lt;T&gt; 检索到的任何内容视为 T(当然)或其任何基本类型

编辑

我刚刚注意到您使用的是 .Net 3.5。在这种情况下,IEnumerable&lt;T&gt; 不是协变的,您需要更多代码来转换集合。像这样的:

ICollection<T> ConvertCollection<T, U>(ICollection<U> collection) where U : T
{
    var result = new List<T>(collection.Count);
    foreach (var item in collection)
        result.Add(item);
}

【讨论】:

  • Cast 返回一个IEnumerable,而不是这个假设示例所需的ICollection
  • 我不明白为什么这不是一个可解决的问题:controls.Add(new HttpWebRequst()); ArgumentException - 无法将类型 HttpWebRequest 添加到 ICollection&lt;Label&gt;
  • @IanBoyd 是的,我注意到关于 IEnumerable 的问题,并已更正。
  • 我认为关键在于用编译时间错误换取运行时错误并不是做事的“C#方式”。
  • @IanBoyd 谁来验证文档是否正确?我还没有参与过一个所有文档都正确且所有文档正确的方法都没有错误的项目
【解决方案2】:

问题的快速回答:

labels.Cast<Control>().ToList()

这将创建一个全新的、单独的列表,因此如果您要将新集合传递给为其添加控件的方法,则该新控件将不会反映在原始集合中,labels

另一种方法是查看您将集合传递给的方法。假设您有如下方法:

    void AddControl(List<Control> controls, string controlName)
    {
        Control ctrl = this.FindControlByName(controlName);

        controls.Add(ctrl);
    }

您不能将List&lt;Label&gt; 对象传递给此方法,但如果您将其重写为泛型方法,则可以,如下所示:

    void AddControl<T>(List<T> controls, string controlName)
        where T : Control
    {
        Control ctrl = this.FindControlByName(controlName);

        controls.Add((T)ctrl); // a cast is required
    }

当然,根据您的情况,上述建议可能不可行或不可取。

正如您自己的回答中所指出的,另一种可能性是利用非通用接口。这是一种完全有效的方法;我认为自从 .NET 2.0 中出现泛型以来,我们通常会反对强制转换,认为它在某种程度上“不好”,但有时在处理多态性时强制强制转换是必要的。

【讨论】:

    【解决方案3】:

    我找到的解决方案——停止使用Collections

    List<Label> labels = new List<Label>();
    

    然后方法变成:

    public void ScaleControls(IList controls) {}
    

    我得到了通用列表的好处,没有 .NET 抱怨它不能做显而易见的事情。

    如果有人打电话:

    controls.Add(new System.Xml.XmlDocument());
    

    然后我得到错误:

    ArgumentException
    值“System.Xml.XmlDocument”不是“System.Windows.Forms.Label”类型,不能在这个通用集合中使用。

    完全符合我的预期。

    【讨论】:

    • @L.B.我知道我知道。我的问题是“如何铸造祖先”。我必须接受的答案是“你不能”。我可以问一个单独的问题,“我怎么能做我想做的事,因为这是不可能的” - 但是当答案在这里时,不需要打开一个全新的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-16
    • 1970-01-01
    • 2017-11-28
    • 1970-01-01
    • 2018-12-26
    • 2010-12-15
    相关资源
    最近更新 更多