14

クラス外で値型を読み取り専用にしたい場合は、次のようにします。

public class myClassInt
{
    private int m_i;
    public int i {
        get { return m_i; }
    }

    public myClassInt(int i)
    {
        m_i = i;
    }
}

List<T>クラス外で型を読み取り専用にする (要素を追加/削除できないようにする)にはどうすればよいですか? 今、私はそれを公開すると宣言します:

public class myClassList
{
    public List<int> li;
    public  myClassList()
    {
        li = new List<int>();
        li.Add(1);
        li.Add(2);
        li.Add(3);
    }
}
4

7 に答える 7

21

AsReadOnlyを公開できます。つまり、読み取り専用IList<T>ラッパーを返します。例えば ​​...

public ReadOnlyCollection<int> List
{
    get { return _lst.AsReadOnly(); }
}

を返すだけIEnumerable<T>では不十分です。例えば ​​...

void Main()
{
    var el = new ExposeList();
    var lst = el.ListEnumerator;
    var oops = (IList<int>)lst;
    oops.Add( 4 );  // mutates list

    var rol = el.ReadOnly;
    var oops2 = (IList<int>)rol;

    oops2.Add( 5 );  // raises exception
}

class ExposeList
{
  private List<int> _lst = new List<int>() { 1, 2, 3 };

  public IEnumerable<int> ListEnumerator
  {
     get { return _lst; }
  }

  public ReadOnlyCollection<int> ReadOnly
  {
     get { return _lst.AsReadOnly(); }
  }
}

スティーブの答えには、キャストを回避する賢い方法もあります。

于 2009-08-04T22:55:35.580 に答える
11

このような範囲で情報を隠そうとすることには、限られた価値しかありません。プロパティのタイプは、ユーザーがそれを使って何ができるかをユーザーに伝える必要があります。ユーザーがAPIを悪用したいと判断した場合、その方法が見つかります。それらのキャストをブロックしても、それらは停止しません。

public static class Circumventions
{
    public static IList<T> AsWritable<T>(this IEnumerable<T> source)
    {
        return source.GetType()
            .GetFields(BindingFlags.Public |
                       BindingFlags.NonPublic | 
                       BindingFlags.Instance)
            .Select(f => f.GetValue(source))
            .OfType<IList<T>>()
            .First();
    }
}

その1つの方法で、これまでにこの質問で与えられた3つの答えを回避できます。

List<int> a = new List<int> {1, 2, 3, 4, 5};

IList<int> b = a.AsReadOnly(); // block modification...

IList<int> c = b.AsWritable(); // ... but unblock it again

c.Add(6);
Debug.Assert(a.Count == 6); // we've modified the original

IEnumerable<int> d = a.Select(x => x); // okay, try this...

IList<int> e = d.AsWritable(); // no, can still get round it

e.Add(7);
Debug.Assert(a.Count == 7); // modified original again

また:

public static class AlexeyR
{
    public static IEnumerable<T> AsReallyReadOnly<T>(this IEnumerable<T> source)
    {
        foreach (T t in source) yield return t;
    }
}

IEnumerable<int> f = a.AsReallyReadOnly(); // really?

IList<int> g = f.AsWritable(); // apparently not!
g.Add(8);
Debug.Assert(a.Count == 8); // modified original again

繰り返しになりますが...この種の「軍拡競争」は好きなだけ続けることができます!

これを停止する唯一の方法は、ソースリストとのリンクを完全に解除することです。つまり、元のリストの完全なコピーを作成する必要があります。これは、BCLが配列を返すときに行うことです。これの欠点は、ユーザーの00.1%のハッカリーについて心配しているため、一部のデータへの読み取り専用アクセスを希望するたびに、ユーザーの99.9%に潜在的に大きなコストを課していることです。

または、静的型システムを回避するAPIの使用をサポートすることを拒否することもできます。

プロパティがランダムアクセスで読み取り専用リストを返すようにしたい場合は、以下を実装するものを返します。

public interface IReadOnlyList<T> : IEnumerable<T>
{
    int Count { get; }
    T this[int index] { get; }
}

(はるかに一般的ですが)順番に列挙可能である必要がある場合は、次を返しIEnumerableます。

public class MyClassList
{
    private List<int> li = new List<int> { 1, 2, 3 };

    public IEnumerable<int> MyList
    {
        get { return li; }
    }
}

更新この回答を書いたので、C#4.0が出てきたので、上記のIReadOnlyListインターフェイスは共分散を利用できます。

public interface IReadOnlyList<out T>

そして今、.NET 4.5が到着しました、そしてそれは...何を推測します...

IReadOnlyListインターフェイス

したがって、読み取り専用リストを保持するプロパティを使用して自己文書化APIを作成する場合、答えはフレームワークにあります。

于 2009-08-05T10:15:18.847 に答える
6

戻ることに関するJPの答えIEnumerable<int>は正しいです(リストにダウンキャストできます)が、ダウンキャストを防ぐテクニックは次のとおりです。

class ExposeList
{
  private List<int> _lst = new List<int>() { 1, 2, 3 };

  public IEnumerable<int> ListEnumerator
  {
     get { return _lst.Select(x => x); }  // Identity transformation.
  }

  public ReadOnlyCollection<int> ReadOnly
  {
     get { return _lst.AsReadOnly(); }
  }
}

列挙中の恒等変換により、コンパイラによって生成されたイテレータ (まったく関係のない新しい型) が効果的に作成さ_lstれます。

于 2009-08-04T23:22:45.477 に答える
1

Eric Lippert は、彼のブログで C# の不変性に関する一連の記事を公開しています。

シリーズの最初の記事はここにあります。

同様の質問に対する Jon Skeet の回答も役に立つかもしれません。

于 2009-08-04T22:55:49.743 に答える
0
public List<int> li;

パブリック フィールドを宣言しないでください。一般的に悪い習慣と見なされます...代わりにプロパティでラップします。

コレクションを ReadOnlyCollection として公開できます。

private List<int> li;
public ReadOnlyCollection<int> List
{
    get { return li.AsReadOnly(); }
}
于 2009-08-04T23:00:49.390 に答える
0
public static IEnumerable<T> AsReallyReadOnly<T>(this IEnumerable<T> source)
{
    foreach (T t in source) yield return t;
}

Earwickerの例に追加すると

...
IEnumerable<int> f = a.AsReallyReadOnly();
IList<int> g = f.AsWritable(); // finally can't get around it

g.Add(8);
Debug.Assert(a.Count == 78);

私は得るInvalidOperationException: Sequence contains no matching element

于 2009-08-05T11:03:27.237 に答える
0
public class MyClassList
{
    private List<int> _lst = new List<int>() { 1, 2, 3 };

    public IEnumerable<int> ListEnumerator
    {
        get { return _lst.AsReadOnly(); }
    }

}

確認するには

    MyClassList  myClassList = new MyClassList();
    var lst= (IList<int>)myClassList.ListEnumerator  ;
    lst.Add(4); //At this point ypu will get exception Collection is read-only.
于 2009-08-05T09:50:16.293 に答える