3

我正在寻找是否有一种方法可以轻松地从 c# 类中公开我的容器,而不会泄露不必要的实现细节。我正在学习 C#,但我在其他 OO 语言方面经验丰富,所以我知道在可能的情况下,最好公开基类/接口而不是具体类型。我正在做的是在我的班级中存储密钥集合(对象集合)。我发现我可以轻松地将其公开为 IDictionary(key, 对象集合),但我想要的是 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;
}

我知道在 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 1[System.String]]”类型的对象转换为“System.Collections.Generic.IDictionary 2[System.String,System.Collections.Generic.IList1[System.String]]”类型2[System.String,System.Collections.Generic.IEnumerable

有任何想法吗?我希望有一种简单/优雅的方式来做到这一点,因为总的来说,从较低级别的 OO 语言迁移到 C# 是一种乐趣,在 C# 中可以更快、更容易地将我想要的东西付诸实践。这无论如何都不是一个阻碍,因为我可以只显示 List 而不是 IEnum 并相信自己不会滥用 List,但我想正确地做事,特别是因为我希望有一天其他人使用我的代码.

PS 我记得在某处读到 C# 最近改进了对这类事情的支持。如果这有什么不同,我在 VS2010 上。

编辑

我在询问之前做了一些搜索,但经过更多搜索后,我发现以前有人问过这个问题,在稍微不同的上下文中,为什么不能将 Dictionary<T1, List<T2>> 强制转换为 Dictionary<T1, IEnumerable<T2 >>? . 所以我的问题可能会归结为是否有一种只读 IDictionary 的方法(另请参阅Is there a read-only generic dictionary available in .NET?

另一个编辑

正如 Alex G / code4life 所建议的那样,这将起作用:无需滚动我自己的只读字典类,我就可以创建一个新字典。例如

    public IDictionary<string, IEnumerable<string>> Name2AddressesNewDictionary
    {
        get
        {
            return name2Addresses.ToDictionary(na=>na.Key, na=>na.Value.AsEnumerable());
        }
    }

这会导致每次访问时性能损失+创建新键值对(以及字典本身)的空间损失。加上额外的开发时间。一般来说,如果我正在使用一个属性(尤其是在幕后进行的工作),这似乎并不比公开一个列表更好。但作为一种方法/在其他情况下会很好。

第三次编辑

正如 Jens Kloster 建议的那样,将容器的定义更改为使用 IEnumerable 作为值

    Dictionary<string, IEnumerable<string>> name2Addresses;

然后在你需要的时候强制执行。

        ((IList<string>)name2Addresses["Anne Best"]).Add("Alternative address");
4

5 回答 5

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());
    }
}

注意:这会创建一个新的字典对象!

在更一般的设计相关说明上:任何时候你发现自己以这种方式组合数据结构时,通常值得定义自己的数据结构以提供清晰性并公开遍历结构和执行常见操作所需的必要方法操作。

于 2013-04-18T12:39:41.150 回答
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");
    }

也 - 看看这篇文章

于 2013-04-18T12:41:57.320 回答
1

我认为您只需要再次对字典进行 LINQ 化。

public IDictionary<string, IEnumerable<string>> Name2Addresses    
{
  get
  {
    return name2Addresses.ToDictionary(d => d.Key, d => d.Value.AsEnumerable());
  }
}

相反,重新定义要使用的字典IEnumerable- 这可能是最好的方法。:

Dictionary<string, IEnumerable<string>> name2Addresses;
于 2013-04-18T12:46:57.537 回答
1

您可以查看可通过 System.Linq 获得的 .AsEnumerable() 扩展方法:

http://msdn.microsoft.com/en-us/library/bb335435.aspx

于 2013-04-18T12:21:59.843 回答
1

事实上,最好的出路是只读协变字典接口,但没有。但是,您可以轻松编写一个只读实现,IDictionary该实现将包装底层字典并将元素转换为IEnumerable<T>.

于 2013-04-18T12:38:30.863 回答