rory.ap
rory.ap

Reputation: 35290

ReadOnlyCollection<T> implements IList<T> but doesn't implement add method

How is it that System.Collections.ObjectModel.ReadOnlyCollection<T> implements System.Collections.Generic.IList<T> but doesn't implement its Add method?

I'm not asking why it doesn't have an Add method -- that's obvious, because it's supposed to be read-only; I'm asking how it can get away without implementing the method, which is required by the IList<T> interface contract.

Upvotes: 9

Views: 1046

Answers (3)

Crono
Crono

Reputation: 10478

Looking at ReadOnlyCollection<> implementation, one can see that the Add method is implemented like this:

int IList.Add(object value)
{
    ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
    return -1;
}

As you can see it's actually an explicit implementation of IList.Add method. This explains why the Add method isn't available directly out of ReadOnlyCollection<>. If you cast the value to IList then you will be able to call it, but that will always result into a NotSupportedException being thrown.

Notice that other IList methods such as Clear, Insert and Remove are also implemented in a similar way in this class, as well as some of the methods of ICollection<> too: they're explicitly implemented and throws back exceptions.

Starting from .NET 4.5 the System.Collections.Generic.IReadOnlyCollection<> now exists. While the ReadOnlyCollection<> class implements it, it still keeps on implementing IList, which I guess would be for retrocompatibility reasons. Still, if you are using 4.5 and think of making your own implementation of a readonly collection, I would advise that you don't implement IList and use IReadOnlyCollection<> instead.

Upvotes: 2

Sam Harwell
Sam Harwell

Reputation: 99889

It explicitly implements the method, along with several others that would generally alter the underlying collection. See the section Explicit Interface Implementations on the following page:

ReadOnlyCollection<T>

The remarks section of the Add method indicates why they used an explicit implementation:

This member is an explicit interface member implementation. It can be used only when the ReadOnlyCollection<T> instance is cast to an ICollection<T> interface.

As a result, developers are prevented from calling the Add method directly on an instance of ReadOnlyCollection<T> (which wouldn't make sense anyway). Explicit interface implementations are frequently used when the implementation of an interface method would always throw an exception due to additional constraints placed on the implementing class (in this case, the object is read-only).

For more information, see the page Explicit Interface Implementation (C# Programming Guide)


Bonus use case:

Another place where I use explicit interface implementations is when the implementation of the interface method is overly complex, and derived types might introduce an error in the logic. For example, consider the complex implementation of IOleCommandTarget.QueryStatus here (reference source). This code is all boiler-plate code only for the purpose of correct behavior. It does not actually provide the status of any commands.

/// <inheritdoc/>
int IOleCommandTarget.QueryStatus(ref Guid guidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
{
    using (OleCommandText oleCommandText = OleCommandText.FromQueryStatus(pCmdText))
    {
        Guid cmdGroup = guidCmdGroup;
        for (uint i = 0; i < cCmds; i++)
        {
            OLECMDF status = QueryCommandStatus(ref cmdGroup, prgCmds[i].cmdID, oleCommandText);
            if (status == default(OLECMDF) && _next != null)
            {
                int hr = _next.QueryStatus(ref cmdGroup, cCmds, prgCmds, pCmdText);
                if (ErrorHandler.Failed(hr))
                    return hr;
            }
            else
            {
                prgCmds[i].cmdf = (uint)status;
            }
        }

        return VSConstants.S_OK;
    }
}

My exposing the following simpler method as protected virtual instead of just making the interface method public virtual, derived types don't need to worry about properly interpreting the pCmdText parameter or how to handle _next for each of the items in prgCmds (reference source).

/// <summary>
/// Gets the current status of a particular command.
/// </summary>
/// <remarks>
/// The base implementation returns 0 for all commands, indicating the command is
/// not supported by this command filter.
/// </remarks>
/// <param name="commandGroup">The command group.</param>
/// <param name="commandId">The command ID.</param>
/// <param name="oleCommandText">A wrapper around the <see cref="OLECMDTEXT"/>
/// object passed to <see cref="IOleCommandTarget.QueryStatus"/>, or
/// <see langword="null"/> if this parameter should be ignored.</param>
/// <returns>A collection of <see cref="OLECMDF"/> flags indicating the current
/// status of a particular command, or 0 if the command is not supported by the
/// current command filter.</returns>
protected virtual OLECMDF QueryCommandStatus(ref Guid commandGroup, uint commandId, OleCommandText oleCommandText)
{
    return default(OLECMDF);
}

Upvotes: 6

Tim Schmelter
Tim Schmelter

Reputation: 460158

It's implemented in this way:

int IList.Add(object value)
{
    ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
    return -1;
}

It's also documented at MSDN:

Adds an item to the IList. This implementation always throws NotSupportedException.

Upvotes: 3

Related Questions