Можно ли использовать int для ключа в KeyedCollection - PullRequest
11 голосов
/ 14 октября 2008

Часто мне нужна коллекция непоследовательных объектов с числовыми идентификаторами. Мне нравится использовать KeyedCollection для этого, но я думаю, что есть серьезный недостаток. Если вы используете int для ключа, вы больше не сможете обращаться к членам коллекции по их индексу (collection [index] теперь действительно collection [key]). Это достаточно серьезная проблема, чтобы не использовать int в качестве ключа? Какой будет предпочтительная альтернатива? (может быть int.ToString ()?)

Я делал это раньше без каких-либо серьезных проблем, но недавно я столкнулся с неприятной ошибкой, когда сериализация XML для KeyedCollection не работает, если ключ является целым, из-за ошибки в .NET .

Ответы [ 4 ]

7 голосов
/ 14 октября 2008

В основном вам нужно решить, могут ли пользователи класса быть смущены тем фактом, что они не могут, например, сделать:

for(int i=0; i=< myCollection.Count; i++)
{
    ... myCollection[i] ...
}

хотя они могут, конечно, использовать foreach или использовать приведение:

for(int i=0; i=< myCollection.Count; i++)
{
    ... ((Collection<MyType>)myCollection)[i] ...
}

Это не легкое решение, так как оно может легко привести к появлению гейзенгов. Я решил разрешить это в одном из моих приложений, где доступ от пользователей класса был почти исключительно по ключу.

Я не уверен, что сделал бы это для общей библиотеки классов, хотя: в общем, я бы избегал выставлять KeyedCollection в публичном API: вместо этого я бы выставлял IList в публичном API, и потребителям API, которому необходим доступ по ключу, может определить свой собственный внутренний KeyedCollection с помощью конструктора, который принимает IEnumerable и заполняет коллекцию им. Это означает, что вы можете легко создать новую коллекцию KeyedCollection из списка, полученного из API.

Что касается сериализации, существует также проблема с производительностью , о которой я сообщил в Microsoft Connect : KeyedCollection поддерживает внутренний словарь и список, а также сериализует оба - достаточно сериализовать список как словарь может быть легко воссоздан при десериализации.

По этой причине, а также из-за ошибки XmlSerialization, я бы рекомендовал вам не сериализовать KeyedCollection - вместо этого только сериализуйте список KeyedCollection.Items.

Мне не нравится предложение обернуть ваш ключ int в другой тип . Мне кажется неправильным добавлять сложность просто так, чтобы тип мог использоваться как элемент в KeyedCollection. Я бы использовал строковый ключ (ToString) вместо того, чтобы делать это - это скорее похоже на класс VB6 Collection.

FWIW, я задал тот же вопрос некоторое время назад на форумах MSDN. Есть ответ от члена команды FxCop, но нет убедительных рекомендаций.

3 голосов
/ 14 октября 2008

Простое решение может заключаться в переносе int в другой тип, чтобы создать отдельный тип для разрешения перегрузки. Если вы используете struct, у этой обертки нет никаких дополнительных затрат:

struct Id {
    public int Value;

    public Id(int value) { Value = value; }

    override int GetHashCode() { return Value.GetHashCode(); }

    // … Equals method.
}
2 голосов
/ 14 октября 2008

Лучше всего добавить метод GetById(int) к типу коллекции. Collection<T> можно использовать вместо этого, если вам не нужен какой-либо другой ключ для доступа к содержащимся объектам:

public class FooCollection : Collection<Foo>
 { Dictionary<int,Foo> dict = new Dictionary<int,Foo>();

   public Foo GetById(int id) { return dict[id]; }

   public bool Contains(int id) { return  dict.Containskey(id);}

   protected override void InsertItem(Foo f)
    { dict[f.Id] = f;
      base.InsertItem(f);
    }

   protected override void ClearItems()
    { dict.Clear();
      base.ClearItems();
    }

   protected override void RemoveItem(int index)
    { dict.Remove(base.Items[index].Id);
      base.RemoveItem(index);
    }

   protected override void SetItem(int index, Foo item)
    { dict.Remove(base.Items[index].Id);
      dict[item.Id] = item;
      base.SetItem(index, item);
    }
 }









 }
1 голос
/ 14 октября 2008

Ключ в KeyedCollection должен быть уникальным и быстро извлекаемым из собираемого объекта. Например, для данного класса лица это может быть свойство SSN или, возможно, даже конкатенация свойств FirstName и LastName (если известно, что результат уникален). Если идентификатор является законным полем собираемого объекта, то он является действительным кандидатом на ключ. Но, возможно, вместо этого попробуйте привести его как строку, чтобы избежать столкновения.

...