返回一个子类型列表作为C#中的超类型列表

时间:2013-06-11 19:12:29

标签: c# generics

我想“以某种方式”做以下工作:

public class GenericsTest<T> where T: ISomeInterface
{
    private List<T> someList = new List<T>();

    public List<ISomeInterface> GetList()
    {
        return someList; //of course this doesn't even compile
    }
}

有没有办法做到这一点(比如Java中的通配符)而我错过了它?或者它只是无法完成?

修改 首先,感谢大家的关注和回答/评论。其次,确定有办法做到这一点,并且可能最简单和最有效的(虽然不确定)是创建一个正确类型的新列表并迭代地添加另一个列表的元素(在我们的例子中为someList) 。问题是所有那些新的差异事物,“ins”和“out”如果有办法做“泛型方式”。在Java中可能是:

public class GenericsTest<T extends SomeInterface> {
    private List<T> someList = new ArrayList<>();

    public List<? extends SomeInterface> getList() {
        return someList;
    }
}

如果C#中存在? extends等价物,我就会徘徊。我可以接受一个“不”作为一个简短的回答,我只是想先问一下。

编辑2: 一些用户认为,有些人坚持认为这是与铸造有关的问题的重复。由于我标记了最适合我的答案,为了清楚起见我解释。伙计们,我不想投光。我只是在寻找一个等效的Java通配符 extends约束。当然,Java的编译时只有泛型,可能会产生差异。如果C#中的等价物完成了铸造,但是我知道“在Cat列表中有一个Dog对象”的问题,所以我的问题是不同的。

5 个答案:

答案 0 :(得分:3)

我相信这会解决问题:

public List<ISomeInterface> GetList()
{
    return someList.Cast<ISomeInterface>().ToList();
}

你也可以这样做:

public List<ISomeInterface> GetList()
{
    return someList.OfType<ISomeInterface>().ToList(); 
}

不同之处在于第一个选项将执行强制转换,这意味着如果其中一个元素未实现ISomeInterface,则将抛出异常。第二个版本只返回实现ISomeInterface的实例,并且只会跳过没有实例的实例。

在您的示例中,您可以安全地使用Cast选项,因为您可以保证所有实例都实现ISomeInterface

答案 1 :(得分:3)

您可以使用OfType<T>

public class GenericsTest<T> where T : ISomeInterface
{
    private List<T> someList = new List<T>();

    public List<ISomeInterface> GetList()
    {
        return someList.OfType<ISomeInterface>().ToList(); 
    }
}

答案 2 :(得分:1)

在C#中,泛型方差与Java中的方式不同。如果您想利用它,您需要使用该变体的接口(或委托)。此类接口包括IEnumerable<T>IReadOnlyList<T>(.Net 4.5中的新增内容),但不包括IList<T>,因为这不是类型安全的。

这意味着你可以这样做:

public class GenericsTest<T> where T: ISomeInterface
{
    private List<T> someList = new List<T>();

    public IEnumerable<ISomeInterface> GetList()
    {
        return (IEnumerable<ISomeInterface>)someList;
    }
}

答案 3 :(得分:0)

Java和.NET的一个主要缺点是,用于封装可变对象的身份的可变类型引用与用于封装其的身份之间没有区别。 状态的。特别是,如果代码调用getList然后尝试修改返回的列表,则不清楚这样的尝试是否应该修改GenericsTest<T>所持有的列表,或者它是否应该只是修改由该方法创建的列表(仅保留原始列表)。还不清楚getList返回的列表是否会受到GenericsList<T>所持有的项目集的任何未来更改的影响。

如果您的目标是允许调用者拥有一个新列表,该列表中填充了原始列表中的项目,但它可以根据需要进行操作,我建议您使用AsList方法或者定义一个接口

interface ICopyableToNewList<out T> {
    // List<T> ToList(); // Can't be an actual interace member--must use extension method
    int Count {get;}
}

实现一个ToList()扩展方法,该方法作用于该接口:

public static List<T> ToList<T>(this ICopyableToNewList<T> src)

让您的班级实施ICopyableToNewList<T>。这样的声明将允许代码通过将T强制转换为GenericTest<T>来获取任何类型为ICopyableToNewList<desiredType>的新类型的新列表。

答案 4 :(得分:0)

这是问题所在。我假装这个代码会编译:

class foo: ISomeInterface {}

class bar: ISomeInterface {}

GenericsTest<foo> testobject = GenericsTest<foo>();
List<ISomeinterface> alist = testobject.GetList();

内部testobject确实有一个foo列表,但允许我们转换为List<ISomeinterface>,如果我们无法知道调用者将会发生不好的事情尝试在列表中插入bar。这就是不允许这样做的原因,也很容易被允许。

解决问题的方法是:

  1. 在内部使用List<ISomeInterface>(如果异构的集合正常)
  2. 如果正在读取的列表正常,则创建具有所需类型的列表副本。如果您需要在某些情况下改变列表,可以在GenericTest类上添加类型安全方法来实现此目的。