【问题标题】:Create an instance of a List where the type derives from a base class and an interface创建 List 的实例,其中类型派生自基类和接口
【发布时间】:2013-12-13 06:21:15
【问题描述】:

我有一个类,它将存储从基类派生并实现接口的对象。在这个例子中,所有类型都派生自UIElement,并实现了我的接口IDropTarget

因此,在我的课堂上,我可以以一种奇妙的方式使用泛型类型推断来要求这一点,而无需将所有内容都限制在特定的基类中

public void AddDropTarget<TTarget>(TTarget target)
    where TTarget : UIElement, IDropTarget
{
    target.DoSomethingAsUIElement();
    target.DoSomethingAsIDropTraget();

    // Now I want to save it for another method
    list.Add(target);
}

public void DoStuff()
{
    foreach(var item in list)
    {
        item.MoreUIElementAndDropStuff();
    }
}

不幸的是,我似乎无法存储此 TTarget 受限列表。我不能将它限制为特定类型,因为我已经有多个从 UIElement 派生的类(矩形、按钮、网格等),而且我无法让所有这些对象都从基类型派生。

我的下一个解决方案是存储每种类型的列表。我需要弄清楚这种开销是否值得与每次使用它们时投射对象相比。

【问题讨论】:

  • 你为什么不做list = new List&lt;UIElement&gt;();
  • 在您的情况下,您可以将UIElement AsUIElement { get; } 添加到您的界面。然后你可以有一个IDropTarget 的列表并做类似foreach(var item in list) { item.AsUIElement.Focus(); }
  • 托尼:我不能使用 List 因为我还需要访问 IDropTarget。
  • Kramer:这是一个有趣的解决方案,有点类似于下面 Paolo 的解决方案。我更改了答案以将其标记为最佳答案。

标签: c# inheritance generic-list


【解决方案1】:

如果您不想强制转换,您可以使用装饰器模式的变体创建您需要的公共基类。

//our new base type
public interface IElementAndDropTarget : IDropTarget
{
    void DoSomethingAsUIElement();
    void MoreUIElementAndDropStuff();
}

// our decorator. We need to store UIElement and IDropTarget
// as different fields. The static "Make" is giving you
// the compile-time guarantee that they both refer
// to the same class
public class UIElementDecorator : IElementAndDropTarget
{
    private readonly UIElement t;
    private readonly IDropTarget dt;

    private UIElementDecorator(UIElement t, IDropTarget dt)
    {   
        this.t=t;
        this.dt=dt;
    }

    public static UIElementDecorator Make<T>(T t) where T: UIElement, IDropTarget
    {
        return new UIElementDecorator(t,t);
    }

    public void DoSomethingAsUIElement() {t.DoSomethingAsUIElement();}
    public void MoreUIElementAndDropStuff(){t.MoreUIElementAndDropStuff();}
    public void DoSomethingAsIDropTarget() {dt.DoSomethingAsIDropTarget();}
}

有了这个,你的列表就可以了

public List<IElementAndDropTarget> list = new List<IElementAndDropTarget>(); 

AddDropTarget 中,您可以将装饰类填充为:

list.Add(UIElementDecorator.Make(target));

就是这样。您的其余代码可以保持不变。

【讨论】:

  • 我知道它有效,我只是不知道你为什么会经历这种痛苦,特别是如果你需要从 UIElement/DropTarget 获得比一两个方法更多的表面积。它变得笨拙快速。只需暴露基本的UIElementDropTarget 属性,并去掉接口和装饰器上的方法。然后你也可以摆脱界面......现在我们不需要它了。因此,您所拥有的只是一个简单的类,其中包含两个预制公共属性中的对象,以及一个用于创建新实例的静态工厂方法。
  • @AndrewBacker 很公平。如果有 10 个或更多方法要委托,那么额外的封装可能不值得所有手动方法委托。
【解决方案2】:

您不能以这种方式将两种不同的类型添加到列表中。您基本上是在说您希望它们的泛型类型为 either XY。想象一下当您执行foreach 时会发生什么,编译器将不知道您想要UIElement 还是IDragDrop

您只需要手动维护类型安全。使用私人列表(就像您正在做的那样,我假设),然后手动进行演员表。这取决于您要使用哪种类型或任何类型。

public class MyClass {

    private _list = new List<object>();  // don't store any type info for now

    public void AddDropTarget.... /* this is already correct */

    public void RunUiStuff() {
        // get all items that are of type UIElement.  Items of wrong type will be ignored
        foreach(var e in _list.OfType<UIElement>()) {

        }
    }

    public void RunDropStuff() {
        // cast works as well, but will throw if any object is not the correct type
        foreach(var e in _list.Cast<IDropTarget>()) {
        }
    }
}

【讨论】:

  • 泛型推断适用于方法,因此在编译时它可以验证参数是否来自 UIElement,并实现 IDropTarget。需要明确的是,我不想要一个可以是 X 或 Y 的类型,我想要两者,就像 TTarget 定义被限制为一样。编译器知道参数必须是两者,因此我可以在同一个变量上使用两种类型的方法。我认为没有办法获得相同的列表,但值得一问。而且由于泛型强制执行类型安全,我不必担心.Cast&lt;&gt; 无论如何都会抛出。
  • 当然 :) 它们是两种不同的类型,如果没有新的基类就无法组合它们。
  • 我认为你误解了这个问题。在我上面AddDropTraget&lt;TTarget&gt; 的示例中,TTarget 实际上是两种类型。它既是 UIElement 又是 IDropTarget。泛型类型限制确保了这一点。 TTarget 是指从 UIElement 继承并实现 IDropTarget 的任何类型。所以我可以创建一个名为MyDropButton : Button, IDropTarget 的类和另一个名为MyDropTextBlock : TextBlock, IDropTarget 的类。这两个类都符合 TTarget 的限制,但我无法像 AddDropTarget 那样创建一个可以包含这两个类的 List
  • 没错。对不起,如果我没有清楚地沟通。所有控件都需要一个通用基类(如MyButtonBase: UIElement, IDropTarget)。您可以限制为多种类型,但 List 等类不受限制。如果您编写自定义列表类,则可以进行约束。
  • 正确,如果我有一个通用的基类,那么我将能够做类似的事情。但是我使用的是 WPF 中的类,所以我无法创建一个通用的基类来解决问题。
猜你喜欢
  • 2014-09-29
  • 1970-01-01
  • 2011-11-05
  • 1970-01-01
  • 2013-11-16
  • 2023-03-19
  • 2023-02-22
  • 2021-02-28
  • 2011-11-02
相关资源
最近更新 更多