web-dev-qa-db-ja.com

要素をコピーせずにHashSetからReadOnlyCollectionを作成するにはどうすればよいですか?

呼び出し元がコレクションを変更できないように読み取り専用コレクションを返す必要がある読み取り専用プロパティのバッキングフィールドであるプライベートHashSet<string>があります。だから私はしようとしました:

public class MyClass
{
    private readonly HashSet<string> _referencedColumns;

    public ICollection<string> ReferencedColumns { 
        get { return new ReadOnlyCollection<string>(_referencedColumns); }
    }

ReadOnlyCollectionIList<T>によって実装されていないHashSet<T>を受け入れるため、これはコンパイルされません。アイテムのコピーを防ぐために使用できる別のラッパーはありますか?私の目的では、ICollection<T>によって実装されるIList<T>を実装するもの(HashSet<T>の代わりに)を返すだけで十分です。

11
Dejan

代わりに、プロパティをタイプ_IReadOnlyCollection<>_として公開することを検討してください。これにより、_HashSet<>_の読み取り専用ビューが提供されます。プロパティゲッターは基になるコレクションのコピーを必要としないため、これはこれを実装する効率的な方法です。

これは、誰かがプロパティを_HashSet<>_にキャストして変更することを妨げるものではありません。これに関心がある場合は、プロパティゲッターのreturn _referencedColumns.ToList()を検討してください。これにより、基になるセットのコピーが作成されます。

14
Bas

次のデコレータを使用して、ハッシュセットをラップし、読み取り専用のICollection<T>を返すことができます(IsReadOnlyプロパティはtrueを返し、変更メソッドはで指定されているようにNotSupportedExceptionをスローします。 ICollection<T>)の契約:

public class MyReadOnlyCollection<T> : ICollection<T>
{
    private readonly ICollection<T> decoratedCollection;

    public MyReadOnlyCollection(ICollection<T> decorated_collection)
    {
        decoratedCollection = decorated_collection;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return decoratedCollection.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable) decoratedCollection).GetEnumerator();
    }

    public void Add(T item)
    {
        throw new NotSupportedException();
    }

    public void Clear()
    {
        throw new NotSupportedException();
    }

    public bool Contains(T item)
    {
        return decoratedCollection.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        decoratedCollection.CopyTo(array, arrayIndex);
    }

    public bool Remove(T item)
    {
        throw new NotSupportedException();
    }

    public int Count
    {
        get { return decoratedCollection.Count; }
    }

    public bool IsReadOnly
    {
        get { return true; }
    }
}

そして、あなたはそれをこのように使うことができます:

public class MyClass
{
    private readonly HashSet<string> _referencedColumns;

    public ICollection<string> ReferencedColumns { 
        get { return new MyReadOnlyCollection<string>(_referencedColumns); }
    }
    //...

このソリューションはHashSetのスナップショットを取得せず、代わりにHashSetへの参照を保持することに注意してください。これは、返されるコレクションにHashSetのライブバージョンが含まれることを意味します。つまり、HashSetが変更された場合、変更前に読み取り専用コレクションを取得したコンシューマーは変更を確認できます。

6
Yacoub Massad

読み取り専用ではありませんの間に、MicrosoftはSystem.Collections.Immutableを拡張するImmutableHashSet<T>を実装するIImmutableSet<T>を含むIReadOnlyCollection<T>と呼ばれるnugetパッケージをリリースしました。

クイック使用サンプル:

public class TrackedPropertiesBuilder : ITrackedPropertiesBuilder
{
    private ImmutableHashSet<string>.Builder trackedPropertiesBuilder;

    public TrackedPropertiesBuilder()
    {
        this.trackedPropertiesBuilder = ImmutableHashSet.CreateBuilder<string>();
    }

    public ITrackedPropertiesBuilder Add(string propertyName)
    {
        this.trackedPropertiesBuilder.Add(propertyName);
        return this;
    }

    public IImmutableSet<string> Build() 
        => this.trackedPropertiesBuilder.ToImmutable();
}
5
Uwy

これは非常に単純で、Microsoftがこれを提供しなかった理由はわかりませんが、完全を期すための拡張メソッドとともに、IReadOnlyCollection<T>に基づいて実装を投稿します。

public class MyClass {
    private readonly HashSet<string> _referencedColumns;

    public IReadonlyHashSet<string> ReferencedColumns => _referencedColumns.AsReadOnly();
}

/// <summary>Represents hash set which don't allow for items addition.</summary>
/// <typeparam name="T">Type of items int he set.</typeparam>
public interface IReadonlyHashSet<T> : IReadOnlyCollection<T> {
    /// <summary>Returns true if the set contains given item.</summary>
    public bool Contains(T i);
}

/// <summary>Wrapper for a <see cref="HashSet{T}"/> which allows only for lookup.</summary>
/// <typeparam name="T">Type of items in the set.</typeparam>
public class ReadonlyHashSet<T> : IReadonlyHashSet<T> {
    /// <inheritdoc/>
    public int Count => set.Count;
    private HashSet<T> set;

    /// <summary>Creates new wrapper instance for given hash set.</summary>
    public ReadonlyHashSet(HashSet<T> set) => this.set = set;

    /// <inheritdoc/>
    public bool Contains(T i) => set.Contains(i);

    /// <inheritdoc/>
    public IEnumerator<T> GetEnumerator() => set.GetEnumerator();
    /// <inheritdoc/>
    IEnumerator IEnumerable.GetEnumerator() => set.GetEnumerator();
}

/// <summary>Extension methods for the <see cref="HashSet{T}"/> class.</summary>
public static class HasSetExtensions {
    /// <summary>Returns read-only wrapper for the set.</summary>
    public static ReadonlyHashSet<T> AsReadOnly<T>(this HashSet<T> s)
        => new ReadonlyHashSet<T>(s);
}
0
Pawcio