1

Consider the following classes:

public abstract class Token
{
    private List<Token> _Tokens { get; set; }

    // ReadOnly public is mandatory. How to give protected add-only, index-based access?
    public ReadOnlyCollection<Token> Tokens { get { return (this._Tokens.AsReadOnly()); } }

    // AddOnly for derived classes.
    protected Token AddToken (Token token) { this._Tokens.Add(token); return (token); }
}

public class ParenthesesToken: Token
{
    // This method is called frequently from public code.
    public void Parse ()
    {
        // Good enough.
        base.AddToken(...);

        // Is a call to List<T>.AsReadOnly() necessary?
        // Add-only, indexed-based access is mandatory here. IEnumerable<T> will not do.
        foreach (var token in this.Tokens) { /* Do something... */ }
    }
}

Is there something about the interfaces implemented by List and ReadOnlyCollection that would allow one-way type casting rather than recreating of the list to other concrete implementations?

The objective is to allow public read-only access but also protected add-only, indexed-based access to derived classes.

Raheel Khan
  • 14,205
  • 13
  • 80
  • 168
  • Why are you translating `Tokens` back into a `List` with `ToList()`? If it's just to `ForEach`, then surely you can just use the `foreach` statement. – Adam Houldsworth Jan 02 '14 at 10:35
  • @AdamHouldsworth: If you are referring to the Consumer class, this is just rough code. The real question is how to give the derived class add-only, indexed access to the List in the base class. I'll update the question to clarify the same, thanks. – Raheel Khan Jan 02 '14 at 10:44

1 Answers1

3
public abstract class Token
{
    private List<Token> _tokens { get; set; }

    public IEnumerable<Token> Tokens 
    { 
       get { return _tokens; } 
    }

    protected Token AddToken (Token token) 
    { 
       _tokens.Add(token); 
       return token; 
    }

    protected Token GetTokenAt(int index)
    {
        return _tokens[index];
    }
}

I don't like returning read-only collections, because IEnumerable is a read-only interface which hides implementation from consumer. Some can argue, that consumer may cast IEnumerable to list and add new items, but that means two things:

  • consumer violates API you provided
  • you can't stop consumer from reflection usage
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • 1
    The *only* issue with this - is that I can cast .Tokens to a List<> and then I have full access to the underlying list - but that's a weird case to worry about. You could solve that, by implementing a yield/return guy to return the values. – Dave Bish Jan 02 '14 at 10:53
  • 1
    @DaveBish agree, but see my comments added to answer – Sergey Berezovskiy Jan 02 '14 at 10:54
  • 1
    You could even do { return _tokens.Select(t => t); } – Dave Bish Jan 02 '14 at 10:55
  • Hahahaaa... I absolutely detest the `GetByIndex` approach and subconsciously did not even go there.And as you and @DaveBish both said, being able to publicly cast to List is not an option. Reflection, etc. cannot be my problem as an API developer. But managed goof ups like that are certainly my responsibility. – Raheel Khan Jan 02 '14 at 10:56
  • 1
    @DaveBish yes, vaery nice approachm, though I usually don't deal with criminals (i.e. who violates API of my classes), because you can't stop them anyway. – Sergey Berezovskiy Jan 02 '14 at 10:57
  • @DaveBish: I'll have to look up LINQ to see what happens here. I'm not sure if a separate collection is created for the same references. That would be the same as calling List.AsReadOnly() I'm guessing. – Raheel Khan Jan 02 '14 at 10:57
  • @RaheelKhan `_tokens.Select(t => t)` will create enumerator class which allows only enumeration of collection – Sergey Berezovskiy Jan 02 '14 at 10:58
  • @SergeyBerezovskiy - Yeah - i agree - this isn't really something to worry about - just something top be aware of. – Dave Bish Jan 02 '14 at 11:00
  • AsReadOnly() does a better job of preventing the cast from working. – Hans Passant Jan 02 '14 at 11:00
  • Just for my knowledge, wouldn't `Select(t => t)` and `List.AsReadyOnly` so the same thing under the covers? In other words, am I benefiting at all by using LINQ? – Raheel Khan Jan 02 '14 at 11:00
  • @HansPassant can it prevent reflection? – Sergey Berezovskiy Jan 02 '14 at 11:02
  • Nothing but code access security will prevent reflection but I would be very interested in knowing how AsReadOnly does a better job. Always good to be aware of these things. – Raheel Khan Jan 02 '14 at 11:03
  • 2
    Reflection can get to the private "list" variable. If it is really important then you can stop Reflection with [ReflectionPermission]. – Hans Passant Jan 02 '14 at 11:07
  • 1
    @SergeyBerezovskiy: Thank you. I ended up using the `protected GetByIndex` approach. All aches and groans but it seems obvious there isn't a better alternative. – Raheel Khan Jan 02 '14 at 11:15
  • @HansPassant: Thank you for pointing that out. The objective in my scenario is to discourage careless consumption rather than fight off malicious intent but this will make very good reading for projects to come. – Raheel Khan Jan 02 '14 at 11:19