web-dev-qa-db-ja.com

ハッシュセットを事前に割り当てることができないのはなぜですか<T>

hashset<T>を事前に割り当てることができないのはなぜですか?

それに多くの要素を追加している可能性があり、サイズ変更を排除したい場合があります。

49
Ryan Lee

以下の回答は2011年に作成されました。現在は.NET4.7.2および.NETCore2.0になっています。 .NET Standard2.1になります。


これが不可能であるという技術的な理由はありません。Microsoftは、初期容量のコンストラクターを公開することを選択していません。

IEnumerable<T>を取り、ICollection<T>の実装を使用するコンストラクターを呼び出すことができれば、コレクションのサイズを初期の最小容量として使用すると思います。これは実装の詳細です。容量は、すべての個別の要素を格納するのに十分な大きさである必要があります...

編集:容量が必要以上に大きいことが判明した場合、コンストラクターは、そこにある個別の要素の数を調べ終えたときに、余分な要素をトリミングすると思います本当に

とにかく、もしあなたがhaveあなたがHashSet<T>に追加しようとしているコレクションそしてそれはICollection<T>を実装し、代わりにそれをコンストラクターに渡すなら要素を1つずつ追加することは、基本的には勝利になります:)

編集:1つの回避策は、Dictionary<TKey, TValue>の代わりにHashSet<T>を使用し、値を使用しないことです。ただし、HashSet<T>と同じインターフェイスが提供されないため、すべての場合に機能するとは限りません。

29
Jon Skeet

ジョンスキートの答えはほぼ完全なものです。 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;
}

documentation :で説明されているように、Clearの後、HashSetはトリミングされないため、このトリックは機能します。

容量は、TrimExcessが呼び出されるまで変更されません。

9
BartoszKP

このコードを使用して、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;
    }
}
8
Alex Zhukovskiy

この機能は 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.
3
David Wohlferd

HashSetを初期容量で初期化する唯一の方法は、List<T>を実装するICollection<T>などのクラスのインスタンスを使用してHashSetを構築することです。 ICollection<T>でCountを呼び出し、コレクションを保持するのに十分なスペースを割り当て、再割り当てせずにすべての要素をHashSetに追加します。

0
chuckj