IDictionary是IEnumerable <valuetype>而不是KeyValue?</valuetype>

时间:2012-01-27 22:07:09

标签: c# collections

我希望能够在相同的情况下通过键查找值,并在其他情况下遍历集合。

以前我这样做:

public class Company
{
  public string Name { get; set; }
  public ICollection<Department> Departments { get; set; }

}

public class Department
{
  public string Name       { get; set; } //display name
  public string UniqueName { get; set; } //like a code value
  public bool   IsSelected { get; set; }
}

foreach(var d in someCompany.Departments) ...

var departments = someCompany.Departments.Select( d => some projection stuff ...

现在我有几个地方我还需要迭代一些其他集合,比如带有UniqueName匹配字符串的复选框列表,所以为了提高效率,我将Departments的声明更改为IDictionary,但是这使得其他用例更麻烦,总是要求我深入研究价值或价值观。所以现在Departments属性不再是Department类的集合,而是KeyValue对的集合。

foreach(ListItem item in someCheckBoxes.Items)
{
  someCompany.Departments[item.Value].Selected = true;
}

foreach(var d in someCompany.Departments.Values) ...

var departments = someCompany.Departments.Values.Select( d => some projection stuff ...

每当我初始化这些列表时,我也不必关心将Lists转换为IDictionary或添加KeyValue。

理想情况下,我会有一个集合,其行为类似于ICollection,但也有一个索引运算符,并包含一个内部访问字典的函数。

或者我会有两个这样的属性,保持同步:

public Company(string uniqueName, string name, ICollection<Department> departments)
{
  Name = name;
  UniqueName = uniqueName;
  DepartmentsByUniqueName = departments.ToDictionary<Department, string>(p => p.UniqueName);
}

public IDictionary<string,Department> DepartmentsByUniqueName { get; set;}

public ICollection<Department> Departments { get { return DepartmentsByUniqueName.Values; } }

public void AddDepartment(Department department)
{ 
  DepartmentsByUniqueName.Add(department,department.UniqueName)
}

这里的问题是有人可以通过Departments属性获取Values集合并向其添加/删除项目,而不是重新调整它们确实需要添加到字典中。(实现集合并不能解决这个问题,因为它们得到了通过get进行cllection,然后可以添加项目。)

尝试不重新编写太多代码,但继承自Dictionary会自动使其继承IEnumerable。即使我对接口进行了显式实现,如果用户将其强制转换为该基接口,也可能会出现问题。

基本上我想要一个实现IEnumerable的类,但也有一个Contains和[]运算符,它们在内部使用字典来提高效率。

或者能够创建另外的DepartmentByUniqueName字典属性,该属性与Departments集合保持同步。

3 个答案:

答案 0 :(得分:3)

您可能想要做的是继承KeyedCollection并对其进行定义:

public class DepartmentCollection : KeyedCollection<String, Department> {
    protected override String GetKeyForItem(Department item)
    {
        // EDIT: For your use case, this should work
        return item.UniqueName;
    }
}

并在公司类中将其用作Departments属性:

public class Company
{
  public string Name { get; set; }
  public DepartmentCollection Departments { get; set; }
}

然后可以按名称或索引使用KeyedCollection:

var departments = new DepartmentCollection();
departments.Add(new Department( ... ));
var accounting = departments["Accounting"];
foreach (var department in departments) { .... } 
var accountingExists = departments.Contains("accounting");
// etc

答案 1 :(得分:1)

这就是你所追求的(我想!)你也可以实现ICollection<T>并将调用委托给内部_storage字典。

public class HybridLookup<TKey, TValue> : IEnumerable<TValue>
{
    private readonly IDictionary<TKey, TValue> _storage;

    public HybridLookup()
    {
        _storage = new Dictionary<TKey, TValue>();
    }

    public TValue this[TKey key]
    {
        get { return _storage[key]; }
    }

    public Boolean Contains(TKey key)
    {
        return _storage.ContainsKey(key);
    }
    public IEnumerator<TValue> GetEnumerator()
    {
        return _storage.Values.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

}

答案 2 :(得分:1)

我认为您在上一段代码中建议的解决方案运行正常,但AddDepartment方法除外,需要进行小的修正:

public void AddDepartment(Department department)
{ 
    DepartmentsByUniqueName.Add(department.UniqueName, department);
}
Contains上已经存在

Dictionary<TKey, TValue>.ValueCollection,您可以使用LINQ ElementAt方法而不是索引器(尽管它确实效率不高)。

您不必担心消费者会更改您的Dictionary<TKey,TValue>.Values集合,因为任何此类尝试都会引发NotSupportedException:“不允许对从字典派生的值集合进行变异。”

以下是该课程相关代码的摘录:

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback
{
    public Dictionary<TKey, TValue>.ValueCollection Values { get; }

    public sealed class ValueCollection : ICollection<TValue>, IEnumerable<TValue>, ICollection, IEnumerable
    {
        void ICollection<TValue>.Add(TValue item)
        {
            ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
        }
    }
}