Почему я не могу предварительно выделить хэш-сет<T>

Почему я не могу предварительно выделить hashset<T>?

Есть моменты, когда я могу добавить много элементов к нему, и я хочу устранить изменение размера.

5 ответов

Решение

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

Если вы можете вызвать конструктор, который принимает IEnumerable<T> и использовать реализацию ICollection<T>Я считаю, что будет использовать размер коллекции в качестве начальной минимальной емкости. Обратите внимание, это деталь реализации. Емкость должна быть достаточно большой, чтобы хранить все отдельные элементы...

РЕДАКТИРОВАТЬ: Я считаю, что если емкость оказывается намного больше, чем нужно, конструктор будет обрезать избыток, когда он закончит, чтобы узнать, сколько на самом деле различных элементов.

В любом случае, если у вас есть коллекция, которую вы собираетесь добавить в HashSet<T> и это реализует ICollection<T>то передача его конструктору вместо добавления элементов один за другим будет в принципе победой:)

РЕДАКТИРОВАТЬ: один из обходных путей будет использовать Dictionary<TKey, TValue> вместо HashSet<T>и просто не использовать значения. Это не будет работать во всех случаях, так как это не даст вам тот же интерфейс, что и HashSet<T>,

Ответ Джона Скита почти полный. Чтобы решить эту проблему с HashSet<int> Я должен был сделать следующее:

public class ClassUsingHashSet
{
    private static readonly List<int> PreallocationList
        = Enumerable.Range(0, 10000).ToList();

    public ClassUsingHashSet()
    {
        this.hashSet = new HashSet<int>(PreallocationList);
        this.hashSet.Clear();
    }

    public void Add(int item)
    {
        this.hashSet.Add(item);
    }

    private HashSet<int> hashSet;
}

Этот трюк работает, потому что после Clear HashSet не обрезается, как описано в документации:

Емкость остается неизменной до вызова TrimExcess сделан.

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

public static class HashSetExtensions
{
    private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
    public static HashSet<T> SetCapacity<T>(this HashSet<T> hs, int capacity)
    {
        var initialize = hs.GetType().GetMethod("Initialize", Flags);
        initialize.Invoke(hs, new object[] { capacity });
        return hs;
    }

    public static HashSet<T> GetHashSet<T>(int capacity)
    {
        return new HashSet<T>().SetCapacity(capacity);
    }
}

UPD. 04 июля

Этот код также может быть улучшен за счет использования кэширования отражения. Вот так:

public static class HashSetExtensions
{
    private static class HashSetDelegateHolder<T>
    {
        private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
        public static MethodInfo InitializeMethod { get; } = typeof(HashSet<T>).GetMethod("Initialize", Flags);
    }

    public static void SetCapacity<T>(this HashSet<T> hs, int capacity)
    {
        HashSetDelegateHolder<T>.InitializeMethod.Invoke(hs, new object[] { capacity });
    }

    public static HashSet<T> GetHashSet<T>(int capacity)
    {
        var hashSet = new HashSet<T>();
        hashSet.SetCapacity(capacity);
        return hashSet;
    }
}

Эта возможность была добавлена ​​в 4.7.2:

HashSet<T>(Int32)

Initializes a new instance of the HashSet<T> class that is empty, 
but has reserved space for capacity items and uses the default 
equality comparer for the set type.

Единственный способ инициализировать HashSet с начальной емкостью - это создать его с помощью экземпляра класса, такого как List<T>, что реализует ICollection<T>, Это назовет граф на ICollection<T> выделить достаточно места для хранения коллекции и добавить все элементы в HashSet без перераспределения.

Другие вопросы по тегам