我正在处理一个代码库,它通过类类型大量使用泛型。我一直在推动使用接口(出于各种原因)。在我慢慢改变事物的工作中,我遇到了以下情况,我无法超越它。
简单地(并且最低限度地),我面临的问题如下:
private static IList<IDictionary<int, string>> exampleFunc()
{
//The following produces a compile error of:
// Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.Dictionary<int,string>>' to
// 'System.Collections.Generic.IList<System.Collections.Generic.IDictionary<int,string>>'.
// An explicit conversion exists (are you missing a cast?)
return new List<Dictionary<int, string>>();
}
正如评论中所说,它会产生编译时错误。
如何生成一个符合可返回界面的对象?
一般来说,我如何转换或转换“内部”类型?
更新:我的例子可能会产生误导。我的情况并不特定于包含Dictionary元素的List。有一些例子,但它也可能是IList<IVehicle> vehicleList = new List<Car>();
我的问题的关键是如何处理错误匹配的通用类型。
答案 0 :(得分:5)
您正在尝试将IList
视为协变,这意味着您希望能够将泛型参数的所有实例视为实际上属于较少派生类型的实例。这不起作用,因为IList
不变。 (想象一下如果有人在您返回的列表中添加了SomeOtherTypeOfDictionary<int,string>
会发生什么。您现在已经将Dictionary<int,string>
之内的内容放入List<Dictionary<int, string>>
。
您可以返回 协变的IEnumerable<Dictionary<int, string>>
。
或者,如果您实际上没有已填充的列表,只需从头开始创建适当类型(List<IDictionary<int, string>>
)的列表。
另一种选择是创建一个 new 列表并复制其中的所有元素return new List<Dictionary<int, string>>().Cast<IDictionary<int, string>>>().ToList();
,尽管这个通常不是好设计,特别是你还没有用数据填充列表。
所有这一切都假设你有一个真正令人信服的理由让IList<IDictionary<int,string>>
开始。我声称确实没有令人信服的理由将List
和Dictionary
限制在相应的接口中,并且返回List<Dictionary<int,string>>
没有任何问题,除非你确实有用于返回非List IList或非Dictionary字典的用例。它使您的代码变得复杂,为调用者删除了有价值的功能,如果您不打算返回该接口的不同实现,则不会真正增加价值。
答案 1 :(得分:4)
您的代码无法正常运行,因为IList<T>
不是covariant/contravariant。
如果您自己创建列表,可以为其开头提供兼容类型:
private static IList<IDictionary<int, string>> exampleFunc()
{
var list = new List<IDictionary<int, string>>();
list.Add(new Dictionary<int, string>());
return list;
}
如果这不是一个选项,例如因为你可以从外部来源获得它,你可以投射它。
private static IList<IDictionary<int, string>> exampleFunc()
{
var list = new List<Dictionary<int, string>>();
list.Add(new Dictionary<int, string>());
return list.Cast<IDictionary<int, string>>().ToList();
}
答案 2 :(得分:1)
List<T>
实施IList<T>
。因此,我们可以这样写:
IList<int> xs = new List<int>();
现在假设接口I
和类C
,其中C
实现I
。我们可以这样写:
I x = new C();
将这两个事实结合起来,认为这应该是可能的,这是一个常见的错误:
IList<I> xs = new List<C>();
为什么这是非法的?有两个原因:
首先,List<C>
未实现IList<I>
。它实现了IList<C>
。
其次,将List<C>
视为IList<I>
可以打破类型安全。例如,考虑类D
,它也实现接口I
。如果我们可以将List<C>
分配给IList<I>
,那么我们可以这样做:
IList<C> cs = new List<C>();
IList<I> eyes = cs; //hypothetically legal for this thought experiment
eyes.Add(new D()); //legal because D implements I
C c = cs[0]; //legal, but results in a C variable pointing to a D object!
为了防止这种情况,编译器不允许第二行代码。
如果这是合法的,那么List<T>.Add(T)
方法必须检查其参数的类型,这首先否定了通用系统的一个主要好处。
您的问题的解决方案是使用通过接口类型参数实例化的具体集合类型:
private static IList<I> exampleFunc()
{
return new List<I>();
}
如果您从其他地方获取列表,则有两种选择:
编写一个实现IList<I>
的包装器类,但在将它们添加到内部列表之前,保存对IList<C>
的引用,转换对象(可能引发异常)。
将数据复制到List<I>
。
使用第一个解决方案的唯一原因是有点麻烦,如果您需要更改列表以反映在应用程序的其他部分中,该部分引用了原始List<C>
。< / p>
正如其他海报所提到的,这个问题与泛型类型参数协方差和逆变的概念有关,它允许你写IEnumerable<I> xs = new List<C>();
,因为IEnumerable<T>
在T中是协变的。如果你想了解这个更好,以及为什么List<T>
在T中不能协变,你可能想从Eric Lippert开始就这个主题进行优秀系列,从http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx
答案 3 :(得分:0)
我已经找到了一种方法来完成界面转换。再次出现这种情况,回到我的词典列表的示例。 (可以是任何数据类型 - 内置或自定义)。
class Program
{
private static List<Dictionary<string, Guid>> GetList()
{
var myList = new List<Dictionary<string, Guid>>();
for (var i = 0; i < 25; i++)
{
var newDict = new Dictionary<string, Guid>();
for (var j = 0; j < 25; j++)
{
newDict.Add(j.ToString(), Guid.NewGuid());
}
myList.Add(newDict);
}
return myList;
}
//Really, I want the following to use interfaces; lets not worry about why there is a middle-man function.
private static IList<Dictionary<string, Guid>> MyIntermediateFuncForAReason()
{
var originalList = GetList();
//whatever processing.
return originalList;
}
static void Main(string[] args)
{
var t = MyIntermediateFuncForAReason();
// whatever...
}
}
修改MyIntermediateFuncForAReason
以转换为所需的类型,例如:
private static IList<IDictionary<string, Guid>> MyIntermediateFuncForAReason()
{
var originalList = GetList();
//whatever processing.
return originalList.Select((IDictionary<string, Guid> i) => i).ToList();
}