【问题标题】:C#/.NET 3.5: Casting objects that extend generic listsC#/.NET 3.5:转换扩展通用列表的对象
【发布时间】:2012-02-19 19:45:44
【问题描述】:

我通过具有返回接口类型的方法的外观公开 API,并且在 C#/.NET 3.5 中遇到了泛型列表和继承问题。

我有两个接口:
IMyList(实现 IList
IMyItem

还有三个班级:
MyList(实现 IMyList,扩展 List
MyOtherList(实现 IMyList,扩展 ObservableCollection
MyItem(实现 IMyItem)

但这似乎不可能。我应该如何只公开必要的内容,但仍然调用方法的正确实现(例如在 MyList 和 MyOtherList 之间可能会有所不同)?

编辑:
我的外观是一个看起来像这样的工厂:

public static class Facade {
    public static IMyList<IMyItem> CreateList() {
        return new MyList<MyItem>();
    }

    public static IMyItem CreateItem() {
        return new MyItem();
    }

    public static IConfiguration CreateConfiguration() {
        return new Configuration();
    }
} 

用法:

var list = Facade.CreateList();
list.DoSomethingOnChildren();

现在我期待在 MyList 中实现的 DoSomethingOnChildren() 可以在一系列 MyItem 对象上执行。如果我要改变返回的方法:

public static IMyList<IMyItem> CreateList() {
    return new MyOtherList<MyOtherItem>();
}

我希望在 MyOtherList 中实现的 DoSomethingOnChildren() 能够在一系列 MyOtherItem 对象上执行。

【问题讨论】:

  • 使用“return new MyList();”这是您尝试解决的协方差问题,C# 不支持。
  • 但是如果我有一个 MyList 例如 MyOtherItem 实现了 IMyItem。如何正确投射和返回此列表?
  • 创建一个 MyList,然后用 MyItem 或 MyOtherItem 的实例填充它。如果这不可能,那么您将需要使用 LINQ .Cast (或功能等效项)。这将类似于 "new MyList.Cast().ToList()" 请注意,这是遍历该列表中的每个项目并对其进行转换,这是您需要做的,因为协方差不是支持。
  • 说 MyOtherList 有某些方法依赖于仅在 MyOtherItem 中公开的方法,那我该怎么做呢?我的想法是使用 MyOtherList 中的 MyOtherItem 独有的方法,然后将其作为 IMyList 返回,但也许这从根本上是错误的?

标签: c# generics inheritance .net-3.5 polymorphism


【解决方案1】:

仅仅因为MyItem 实现IMyItem 并不意味着MyList&lt;MyItem&gt; 实现IMyList&lt;IMyItem&gt;。这么说吧,假设你有:

IList<Shape> s = new List<Rectangle>();

如果允许这样做,则会导致大量问题,因为它会让你这样做:

s.Add(new Circle());

因为Circle 也是Shape。一些泛型接口支持协方差,因为泛型类型参数仅以inout 方式使用,因为在IList&lt;T&gt; 中,T 参数同时用于in 和@987654334 @ 位置,这很困难,C# 不支持。

因此,您无法转换引用,但您可以通过其他方式(LINQ 等)将 MyList&lt;MyItem&gt; 的成员加载到 IMyList&lt;IMyItem&gt; 并返回列表的新实例。

如果你想支持这两个接口,同时又允许一个特定的实现,你可以使用显式接口实现。

更新

所以,如果你想做类似的事情,你可以这样做。让你的接口返回更通用的实现,然后让你的实现类有一个显式的实现,只返回一个通用的实现,以及一个特定实现的重载。

比如:

// purely for illustrative purposes
public interface IShape { }
public class Rectangle : IShape { }

// represents your more "generic" interface
public interface ShapeMaker
{
    List<IShape> GetShapes();
}

// Your specific implementation
public class RectangleMaker : ShapeMaker
{
    // the explicit implementation of the interface satisfies the 
    // original, and behaves like original when called from an ShapeMaker 
    // interface reference
    List<IShape> ShapeMaker.GetShapes()
    {
        return new List<IShape>();
    }

    // but, we also provide an overload that returns a more specific version
    // when used with a reference to our subclass.  This gives us more 
    // functionality.
    public List<Rectangle> GetShapes()
    {
        return new List<Rectangle>();
    }
}

您只能通过显式接口实现来做到这一点,因为您仍然必须满足原始接口(必须具有相同的返回类型)。但是,它也允许我们说如果从子类引用中使用,它可以使用更具体的方法来代替。

【讨论】:

  • 好吧,我明白了,这会导致列表不一致...所以简而言之,我想我必须将所有内容存储为 IMyItem,然后在子类中转换我需要操作的项目到预期的类型?或者您认为还有其他方法可以解决这个问题吗?
  • 有很多方法可以做到这一点,很大程度上取决于你想要做什么,看起来你列出的情况是你只是返回一个新实例,这个列表是否在类中?更多代码可能会帮助我微调我的答案。
  • 是的,它是空的。我只想通过某种实现来公开我的列表的一部分。如果我使用 MyList 填充 IMyList,它将如何知道要调用的方法的哪些实现?接口如何知道在哪里可以找到我的意思的方法实现。这样的演员阵容会如何?我尝试了以下方法,但没有成功: return (IMyList)new MyList().Cast().ToList(); [虽然这感觉有点奇怪,因为它将从 List 转换为更具体的实现]。
  • 目标是使用更详细的类型在列表中工作,但仅公开列表和列表项通过其接口公开的内容。
  • 如果您所做的只是返回列表的一个新实例,那么没有好方法可以随心所欲地做到这一点。您可以返回MyList&lt;IMyItem&gt;——即返回列表的具体实现,但不返回项目的具体实现——或者您需要为每个子类创建一个新方法。没有好的方法可以将新的MyList&lt;MyItem&gt; 作为IMyList&lt;IMyItem&gt; 返回,因为它们不一致。
【解决方案2】:

随便写

function IMyList<MyItem> GetList() {
     return new MyList<MyItem>();
} 

function IMyList<IMyItem> GetList() {
     return new MyList<IMyItem>();
} 

这是为什么呢?假设您有 IMyItem 的两个实现:MyItemAMyItemB

如果IMyList&lt;IMyItem&gt;IMyList&lt;MyItemX&gt; 兼容,则GetList 可以为创建为new MyList&lt;MyItemA&gt;() 的列表返回IMyList&lt;IMyItem&gt;

function IMyList<IMyItem> GetList() {
     return new MyList<MyItemA>();
} 

那你就可以了

IMyList<IMyItem> result = GetList();
result[i] = new MyItemB();

但是列表是MyItemA 对象的列表!因此这是被禁止的。


编辑:

请注意,不幸的是数组允许这样做

string[] strings = { "a", "b", "c" };
object[] objects = strings;
objects[0] = 5;

这将编译。但是,它会产生这个运行时错误:

test.exe 中发生了“System.ArrayTypeMismatchException”类型的未处理异常

附加信息:试图以与数组不兼容的类型访问元素。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    • 1970-01-01
    • 2010-10-15
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多