5

I'm trying to write an abstract base class for read-only collections which implement IList. Such a base class should implement the set-indexer to throw a NotSupportedException, but leave the get-indexer as abstract. Does C# allow such a situation? Here's what I have so far:

public abstract class ReadOnlyList : IList {

    public bool IsReadOnly { get { return true; } }

    public object this[int index] {
        get {
            // there is nothing to put here! Ideally it would be left abstract.
            throw new NotImplementedException();
        }
        set {
            throw new NotSupportedException("The collection is read-only.");
        }
    }

    // other members of IList ...
}

Ideally ReadOnlyList would be able to implement the setter but leave the getter abstract. Is there any syntax which allows this?

Wesley Hill
  • 1,789
  • 1
  • 16
  • 27
  • 5
    In case this is not just an exercise, you have ReadOnlyCollection in the framework – Sebastian Piu Dec 15 '10 at 08:28
  • Thanks; I usually use ReadOnlyCollection, though this specific case called for a custom implementation of IList. I do tend to simplify my scenarios to the minimum needed to ask a question. – Wesley Hill Dec 15 '10 at 10:18

7 Answers7

8

Delegate the work to protected members which you can then mark as abstract or virtual depending on the desired behaviour. Try something like this:

 // implementor MUST override
 protected abstract object IndexerGet(int index);

 // implementor can override if he wants..
 protected virtual void IndexerSet(int index, object value) 
 {
   throw new NotSupportedException("The collection is read-only.");
 }

 public object this[int index] {
   get {
     return IndexerGet(index);
   }
   set {
     IndexerSet(index, value);
   }
 }
m0sa
  • 10,712
  • 4
  • 44
  • 91
  • IndexerSet should *probably* not be virtual - indeed, just throwing `NotSupportedException` in the setter itself is fine. – Eamon Nerbonne Dec 15 '10 at 10:01
  • 1
    Thanks m0sa. I've accepted this as the answer as it's the only answer to achieve the goal of implementing the setter and forcing derived classes to implement the getter. Not to mention it's pretty much the only answer so far that would compile! – Wesley Hill Dec 15 '10 at 10:14
2

No, you cannot have a property with a member abstract and the other implemented. What I would do is to set the whole property as abstract and redefine it at the inheritors to do whatever you want.

Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
  • Thanks SoMoS; although I accepted m0sa's answer as the one which answered the question, I've ultimately gone with the approach you suggested, to avoid an extra level of indirection. – Wesley Hill Dec 15 '10 at 10:20
0

Why don't you make the setter private?

public abstract class ReadOnlyList : IList
    {

        public bool IsReadOnly { get { return true; } }

        public object this[int index]
        {
            get
            {
                // there is nothing to put here! Ideally it would be left abstract.
                throw new NotImplementedException();
            }
            private set
            {
                // your private implementation
            }
        }

        // other members of IList ...
    }
L-Four
  • 13,345
  • 9
  • 65
  • 109
0

No. And it really breaks the Liskov Substitution Principle if you would find a way to do it.

Read more about LSP here: Can you explain Liskov Substitution Principle with a good C# example?

Community
  • 1
  • 1
jgauffin
  • 99,844
  • 45
  • 235
  • 372
  • How does this break LSP? The concrete subclass would have to provide a concrete getter implementation. If it does so, that subclass should (absent other issues) be a well-behaved `IList` (which are [allowed](http://msdn.microsoft.com/en-us/library/system.collections.ilist.isreadonly.aspx) to be read-only). – Matthew Flaschen Dec 15 '10 at 08:35
  • 1
    Because anyone using a `IList` would expect to be able to both get and set items in the list. `ReadOnlyCollection` in .Net framework also violates LSP since it does not fulfil that requirement. – jgauffin Dec 15 '10 at 08:37
  • No. If you checked my link, you would see `IList` was explicitly designed to be potentially read-only; that's why there's a `IList.IsReadOnly` propery. Thus, a read-only subclass can easily "substitute." – Matthew Flaschen Dec 15 '10 at 08:41
  • I'm aware of the ReadOnly property, it doesn't change anything regarding LSP. The IList contract still defines that the indexer can be used to get AND set items. So does the Add, Delete methods. – jgauffin Dec 15 '10 at 08:45
  • 1
    Creating a interface which only defines methods to access the list and the derive it with something like `IWritableList` would have been a way to fulfil LSP. `IsReadOnly` **is** a workaround. – jgauffin Dec 15 '10 at 08:47
  • I also think making a collection implement IList and making setter readonly breaks LSP. Consider that there is some existing code which uses IList and changes values using indexer. Now if you pass ReadonlyList to such code it will break. – Unmesh Kondolikar Dec 15 '10 at 08:49
  • 1
    @jg, you keep saying that, but it's still incorrect. The [`IList.Item`](http://msdn.microsoft.com/en-us/library/system.collections.ilist.item.aspx) documentation is crystal clear that a well-behaved `IList` *must* (not just may) throw a `NotSupportedException` if the setter is called and it happens to be read-only. ReadOnly is not being "added." It's part of the original interface. If client code is written correctly to deal with the original `IList`, it will have no issue with a read-only list, so there's no LSP problem. – Matthew Flaschen Dec 15 '10 at 08:51
  • You are in fact correct. The problem is the majority of IList users DO NOT CHECK the IsReadOnly property. And because of that, their code will break if a regular IList implementation is replaced with a ReadOnlyCollection. And I do consider that as a LSP violation even it it's not a per definition violation. – jgauffin Dec 15 '10 at 09:02
  • Not really - IList specifies that the collection *may* support read/write access - hence the `IsReadOnly` property in the first place. The readonly nature of an IList is not dealt with by the type system; it's orthogonal to LSP. However, the LSP is violated (for instance) by the fact that IList specifies usage of `ArgumentOutOfRangeException` whereas Array throws `IndexOutOfRangeException`. – Eamon Nerbonne Dec 15 '10 at 09:41
  • and even if consumers don't check IsReadOnly, that's not a problem nor "effective" LSP violation since the same information is also exposed via exceptions. The real problems are two-fold; namely that the exception specification isn't actually followed, and that the API is a little clunky with regard to something being readonly or not - a marker interface would have been cleaner and have enforced the constant nature of the IsReadOnly concept. But simplicity is also worth something - and having one universal interface does help there. – Eamon Nerbonne Dec 15 '10 at 09:45
  • 1
    I take back my statement concerning IndexOutOfRangeException vs. ArgumentOutOfRangeException - I could have sworn there was such an inconsistency, but trying this in LINQpad shows that an array cast as IList appropriately throws ArgumentOutOfRangeException – Eamon Nerbonne Dec 15 '10 at 09:48
0

If you want it to be a readonly collection then why implement IList?

You should implement IEnumerable instead.

Unmesh Kondolikar
  • 9,256
  • 4
  • 38
  • 51
  • 1
    IList is explicitly [permitted](http://msdn.microsoft.com/en-us/library/system.collections.ilist.isreadonly.aspx) to be read-only. `IEnumerable` doesn't provide random access. – Matthew Flaschen Dec 15 '10 at 08:39
  • Although this isn't nearly as fully featured as the `ReadOnlyCollection<>` solution, in practice it's often enough and has the virtue of being an even simpler API - and that's what counts, right? I'd only advocate ReadOnlyCollection where IEnumerable is insufficient. – Eamon Nerbonne Dec 15 '10 at 09:53
  • Also, `IEnumerable` players nicer with the type system: I commonly use methods to shuffle and sort collections - and these work on ILists. Read-only ILists will fail at run-time, whereas passing an IEnumerable will fail at compile time; that's nicer behavior. – Eamon Nerbonne Dec 15 '10 at 09:58
-1

Why would you want to have setter at all - absence of setter would indicate its read-only property.

EDIT: jgauffin pointed out that setter comes from IList. So you may have two options:

  1. Implement IList privately and provide public abstract getter for indexer (not 100% certain if I can have two indexers though).
  2. Indexer getter would invoke an abstract method such GetItem.
VinayC
  • 47,395
  • 5
  • 59
  • 72
-2

you dont need a setter

public abstract object this[int index] { get; }
Dean Chalk
  • 20,076
  • 6
  • 59
  • 90