Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

Ответ ниже был написан в 2011 году. Теперь он в .NET 4.7.2 и .NET Core 2.0; это будет в .NET Standard 2.1.


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

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

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

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

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

Ответ 2

Ответ Джона Скита почти полный. Чтобы решить эту проблему с помощью 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.

Ответ 3

Я использую этот код для установки начальной емкости для 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);
    }
}

обн. 04 jule

Этот код может быть также усилен с помощью кэширования отражений. Здесь мы идем:

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

Эта возможность была добавлена в 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.

Ответ 5

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