How should I use properties when dealing with read-only List members

后端 未结 7 1498
栀梦
栀梦 2020-12-01 08:26

When I want to make a value type read-only outside of my class I do this:

public class myClassInt
{
    private int m_i;
    public int i {
        get { ret         


        
7条回答
  •  被撕碎了的回忆
    2020-12-01 09:06

    There is limited value in attempting to hide information to such an extent. The type of the property should tell users what they're allowed to do with it. If a user decides they want to abuse your API, they will find a way. Blocking them from casting doesn't stop them:

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

    With that one method, we can circumvent the three answers given on this question so far:

    List a = new List {1, 2, 3, 4, 5};
    
    IList b = a.AsReadOnly(); // block modification...
    
    IList c = b.AsWritable(); // ... but unblock it again
    
    c.Add(6);
    Debug.Assert(a.Count == 6); // we've modified the original
    
    IEnumerable d = a.Select(x => x); // okay, try this...
    
    IList e = d.AsWritable(); // no, can still get round it
    
    e.Add(7);
    Debug.Assert(a.Count == 7); // modified original again
    

    Also:

    public static class AlexeyR
    {
        public static IEnumerable AsReallyReadOnly(this IEnumerable source)
        {
            foreach (T t in source) yield return t;
        }
    }
    
    IEnumerable f = a.AsReallyReadOnly(); // really?
    
    IList g = f.AsWritable(); // apparently not!
    g.Add(8);
    Debug.Assert(a.Count == 8); // modified original again
    

    To reiterate... this kind of "arms race" can go on for as long as you like!

    The only way to stop this is to completely break the link with the source list, which means you have to make a complete copy of the original list. This is what the BCL does when it returns arrays. The downside of this is that you are imposing a potentially large cost on 99.9% of your users every time they want readonly access to some data, because you are worried about the hackery of 00.1% of users.

    Or you could just refuse to support uses of your API that circumvent the static type system.

    If you want a property to return a read-only list with random access, return something that implements:

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

    If (as is much more common) it only needs to be enumerable sequentially, just return IEnumerable:

    public class MyClassList
    {
        private List li = new List { 1, 2, 3 };
    
        public IEnumerable MyList
        {
            get { return li; }
        }
    }
    

    UPDATE Since I wrote this answer, C# 4.0 came out, so the above IReadOnlyList interface can take advantage of covariance:

    public interface IReadOnlyList
    

    And now .NET 4.5 has arrived and it has... guess what...

    IReadOnlyList interface

    So if you want to create a self-documenting API with a property that holds a read-only list, the answer is in the framework.

提交回复
热议问题