使用IDictionary和IEnumerable的嵌套泛型

4
在一个用于内部可重复使用库的通用C#类中,我想传递一个“映射到其他事物列表的东西”的引用。传入的数据类型不应该被库知道。此外,它们存储的方式也不应该被知道,即今天保存在内存中的列表,可能会变成以后按需从数据库表中读取的内容。因此,我认为我应该编写这个库类:
class GenericClass<T, U>
{
    public void Foo(IDictionary<T, IEnumerable<U>> bar)
    {
        // do something
    }
}

这段代码可以编译通过,但是尝试传入具体的实现时会出现问题:

class UsingClass
{
    public static void Main(string[] args)
    {
        var c = new GenericClass<string, string>();
        c.Foo(new Dictionary<string, List<string>>());
    }
}

我遇到了以下两个语法错误:

Filename.cs(46,13): error CS1502: The best overloaded method match for 'GenericClass<string,string>.Foo(System.Collections.Generic.IDictionary<string,System.Collections.Generic.IEnumerable<string>>)' has some invalid arguments
Filename.cs(46,19): error CS1503: Argument 1: cannot convert from 'System.Collections.Generic.Dictionary<string,System.Collections.Generic.List<string>>' to 'System.Collections.Generic.IDictionary<string,System.Collections.Generic.IEnumerable<string>>'

Foo() 声明中的 IEnumerable 替换为 List 可以解决问题,但这显然不是我想要的。

这真的不被 C# (4.0) 支持吗?还是我只是忽略了一些明显的东西?你会建议什么样的解决方法呢?(我相信这已经被广泛讨论过了,所以也可以提供链接到很好的描述。)

是的,我应该能够为此编写自己的辅助类,但为什么我必须这样做呢?

1个回答

9

是的,这确实不被支持。想象一下你的Foo方法看起来像这样:

public void Foo(IDictionary<T, IEnumerable<U>> bar)
{
    T key = GetKeyFromSomewhere();
    bar[key] = new U[10]; // Create an array
}

看起来还不错,对吧?我们可以从U[]转换为IEnumerable<U>

然而,从调用者的角度来看,情况并不太好 - 突然间,字典中出现了一个string[]引用值,而所有的值都应该是List<string>引用!类型安全性瞬间消失。

你可以将方法改写为:

public void Foo<TValue>(IDictionary<T, TValue> bar)
    where TValue : IEnumerable<U>

这将使您可以从字典中获取值并将其隐式转换为IEnumerable<U>,但您只能将完全正确类型的值放入字典中,而您无法仅从U值构建它。

从版本4开始,C#在受限情况下支持泛型变异。因此,例如,在C# 4中(当针对.NET 4时),这将起作用,但以前不会:

List<string> strings = new List<string>();
IEnumerable<object> objects = strings;

关于泛型协变性的更多内容,可以参考Eric Lippert对此主题的博客系列。准备好你的大脑可能会时不时地爆炸。


嗯,我不知道我是否同意。对于调用者实际上存在问题,他们必须做出破坏性的假设,即他们将从字典中获得一个 List<string>,而函数只承诺 IEnumerable<string> - siride
@siride:但是调用者传递了一个Dictionary<string,List<string>>。该类型保证其值始终与List<string>兼容。如果该方法尝试将错误类型的值放入该字典中,则确实应该在某种程度上失败,最好在编译时失败。或者您认为泛型集合内的类型安全性不重要吗?;) - Jon Skeet
无论如何,这正是我正在寻找的,现在我的代码已经编译并按预期工作。谢谢,Jon! - Henrik Heimbuerger
1
@siride:我宁愿被编译时限制所困扰,也不愿意遇到执行时类型安全问题 :) - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接