Можно ли использовать int для ключа в KeyedCollection

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

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

Ответ 1

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

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 <T> в публичном API, а потребители API, которым нужен доступ с ключами, могут определить свой собственный внутренний KeyedCollection с помощью конструктора, который принимает IEnumerable <TItem> и заполняет коллекцию им. Это означает, что вы можете легко создать новый KeyedCollection из списка, полученного из API.

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

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

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

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

Ответ 2

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

struct Id {
    public int Value;

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

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

    // … Equals method.
}

Ответ 3

Лучше всего добавить метод 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);
    }
 }









 }

Ответ 4

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