我正在寻找一种方法,可以轻松地从c#类中公开我的容器,而不会泄露不必要的实现细节。我正在学习C#,但我对其他OO语言很有经验,所以我知道在可能的情况下,最好是公开基类/接口而不是具体类型。我正在做的是在我的班级中存储密钥集合(对象集合)。我发现我可以很容易地将它公开为IDictionary(键,对象的集合),但我想要的是IDictionary(key,IEnumerable(object))。希望一个简单的例子可以使它更清晰。
class AddressBook
{
public IDictionary<string, List<string>> Name2Addresses // ok, but I want IDictionary<string, IEnumerable<string>>
{
get { return name2Addresses; }
}
public IEnumerable<string> GetAddresses(string name)// ok, but I really just want the convenience of exposing container as a "one-liner"
{
return name2Addresses[name];
}
public AddressBook()
{
name2Addresses = new Dictionary<string,List<string>>();
List<string> addresses = new List<string>(); // I'd prefer IList<string> addresses = ...
name2Addresses.Add("Anne Best", addresses);
addresses.Add("Home address");
addresses.Add("Work address");
}
Dictionary<string, List<string>> name2Addresses;
}
我知道暴露出作为base的容器派生的容器在OO中是一个棘手的问题,因为,例如,你可以通过容器基类添加不同的派生。但希望因为我想通过IEnumerable将容器公开为只读,所以可能有一种简单的方法可以做到这一点。这是我想要的代码。
public IDictionary<string, IEnumerable<string>> Name2Addresses
{
get { return name2Addresses; }
}
我尝试添加一个演员,因为编译器抱怨没有一个
public IDictionary<string, IEnumerable<string>> Name2Addresses
{
get { return (IDictionary<string, IEnumerable<string>>) name2Addresses; }
}
然后我得到了一个例外:
附加信息:无法投射类型的对象 'System.Collections.Generic.Dictionary
2[System.String,System.Collections.Generic.IList
1 [System.String]]' 输入 'System.Collections.Generic.IDictionary2[System.String,System.Collections.Generic.IEnumerable
1 [System.String]]'。
有什么想法吗?我希望可能有一种简单/优雅的方式来做到这一点,因为总的来说,从较低级别的OO语言转移到C#是一种快乐,它可以更快更容易地将我想要的内容付诸实践。这不是一个显示阻止,因为我可以只显示List而不是IEnum并相信自己不要滥用List,但我想做正确的事情,特别是因为我希望有一天其他人使用我的代码
PS我记得在某处读过C#最近改进了对这类事情的支持。如果这有任何不同,我在VS2010上。
修改的
我在询问之前做了一些搜索,但经过一些搜索之后,我发现之前已经问过这个问题,在一个稍微不同的背景下Why can't Dictionary<T1, List<T2>> be cast to Dictionary<T1, IEnumerable<T2>>?。所以我的问题可能会减少是否有一种只读IDictionary的方法(另见Is there a read-only generic dictionary available in .NET?)
另一个编辑
正如Alex G / code4life建议的那样,这可行:如果没有滚动我自己的只读字典类,我可以创建一个新的字典。 E.g。
public IDictionary<string, IEnumerable<string>> Name2AddressesNewDictionary
{
get
{
return name2Addresses.ToDictionary(na=>na.Key, na=>na.Value.AsEnumerable());
}
}
每次访问时都会导致性能损失+创建新键值对(和dict本身)的空间惩罚。加上额外的开发时间。一般情况下,如果我正在使用一个属性(特别是当工作正在进行中)时,似乎没有比公开List更好。但作为一种方法/在其他情况下会很好。
第三次编辑
正如Jens Kloster建议的那样,更改容器的定义以使用IEnumerable作为值
Dictionary<string, IEnumerable<string>> name2Addresses;
然后在需要执行时进行强制转换。
((IList<string>)name2Addresses["Anne Best"]).Add("Alternative address");
答案 0 :(得分:4)
由于covariance/contravariance,您无法强制转换,但您可以使用LINQ以您希望的格式重新创建字典:
using System.Linq;
...
public IDictionary<string, IEnumerable<string>> Name2Addresses
{
get
{
return name2Addresses.ToDictionary<string, IEnumerable<string>>(
kvp => kvp.Key,
kvp => kvp.Value.AsEnumerable());
}
}
注意:这会创建一个新的字典对象!
在更一般的设计相关说明中:每当您发现自己以这种方式组合数据结构时,通常有必要定义自己的数据结构以提供清晰度并公开您想要遍历结构的必要方法,并执行常规操作。
答案 1 :(得分:3)
尝试更改字段以使用IEnumerable而不是List。
Dictionary<string, IEnumerable<string>> name2Addresses;
那么你应该没有像这样的属性的问题:
public IDictionary<string, IEnumerable<string>> Name2Addresses
{
get { return name2Addresses; }
}
你仍然可以这样做:
public AddressBook()
{
name2Addresses = new Dictionary<string,IEnumerable<string>>(); //changed ere
List<string> addresses = new List<string>();
name2Addresses.Add("Anne Best", addresses);
addresses.Add("Home address");
addresses.Add("Work address");
}
另外 - 查看this帖子
答案 2 :(得分:1)
您可以查看System.Linq提供的.AsEnumerable()扩展方法:
答案 3 :(得分:1)
事实上,最好的出路是一个只读协变字典界面,但没有一个。但是,您可以轻松编写IDictionary
的只读实现,它将包装基础字典并将元素转换为IEnumerable<T>
。
答案 4 :(得分:1)
我认为你只需再次使用LINQ-ify字典。
public IDictionary<string, IEnumerable<string>> Name2Addresses
{
get
{
return name2Addresses.ToDictionary(d => d.Key, d => d.Value.AsEnumerable());
}
}
相反,重新定义字典以使用IEnumerable
- 这可能是最好的方法。:
Dictionary<string, IEnumerable<string>> name2Addresses;