Problems with CollectionBase

M

Michi Henning

Looking at the documentation for System.Collections.CollectionBase
(http://msdn2.microsoft.com/en-us/library/
system.collections.collectionbase.aspx), I find:

public abstract class CollectionBase : IList, ICollection, IEnumerable

So, CollectionBase implements IList.

Looking at the IList members, we see:

int Add (Object value)

So, I can add anything to an IList.

Now I implement a type-safe collection of integers using
CollectionBase:

public class S : System.Collections.CollectionBase
{
public int Add(int i)
{
return InnerList.Add(i);
}
}

This now is type safe:

S s = new S();
s.Add("Hello"); // Compile-time error, as expected.

The compiler says:

The best overloaded method match for 'Example.S.Add(int)' has some
invalid arguments
Argument '1': cannot convert from 'string' to 'int'

Of course, that's what I want because the collection is supposed to
contain only integers, not strings.

However, I have several questions:

1) How is this actually implemented? That is, how does the
CollectionBase implementation prevent me from calling the Add(object o
value) method that is present on the IList interface? As far as I can
see, I cannot do this in C#. Or, to put it differently, I don't think
I could write CollectionBase myself in C#. Is that the correct view?

2) As far as I can see, the type system gets bent rather severely by
this. After all, CollectionBase says that it implements IList, and
IList promises that it provides its Add() method but, when I try to
call that Add() method, I get a compile-time error. Now, is the method
there or not? Does CollectionBase implement IList or not?

3) Consider the following code that uses the above class S:

static void addSomething(System.Collections.IList l)
{
l.Add("Hello");
}

So, addSomething() is a method that adds an element of type string to
the IList that is passed by the caller.

Now I write:

S s = new S(); // Type-safe collection of ints derived from
CollectionBase

addSomething(s);

System.Collections.IEnumerator e = s.GetEnumerator();
e.MoveNext();
System.Console.WriteLine(e.Current);

When I run this code, it prints:

Hello

In other words, I just have added added a string to my supposedly type-
safe collection of ints. All that was necessary was to pass the
instance as type IList to addSomething().

So, a CollectionBase is indeed an IList because I can pass it as an
IList and, if I do, I can invoke operations on IList, such as
Add(Object val). But, if I deal with my class S as type S and try to
call Add(Object val), I get a compile-time error. So, it appears that
a class derived from CollectionBase is somewhat schizophrenic?

Cheers,

Michi.
 
P

Peter Duniho

Michi said:
[...]
1) How is this actually implemented? That is, how does the
CollectionBase implementation prevent me from calling the Add(object o
value) method that is present on the IList interface?

CollectionBase has an explicit implementation of the IList interface.
This means that the method declaration in the class is "IList.Add"
rather than simply "Add" (the latter would be an implicit
implementation, and the method would be available through both the class
and the inherited interface).
As far as I can
see, I cannot do this in C#. Or, to put it differently, I don't think
I could write CollectionBase myself in C#. Is that the correct view?

Nope. You can write your own classes that explicitly implement
interfaces, and the methods in those interfaces will be accessible only
when the class is cast to the interface.
2) As far as I can see, the type system gets bent rather severely by
this. After all, CollectionBase says that it implements IList, and
IList promises that it provides its Add() method but, when I try to
call that Add() method, I get a compile-time error. Now, is the method
there or not? Does CollectionBase implement IList or not?

It does, and you can use any IList member as long as you cast
CollectionBase to an IList. Because it doesn't implicitly implement the
interface, you _must_ get an IList before using the methods though.
3) Consider the following code that uses the above class S:

static void addSomething(System.Collections.IList l)
{
l.Add("Hello");
}

[...]
In other words, I just have added added a string to my supposedly type-
safe collection of ints. All that was necessary was to pass the
instance as type IList to addSomething().

Yup. Similar dangers exist with pretty much any of the classes that
ultimately resolve to containing a collection of "object" instances.

The other problem with these kinds of collection is, of course, that
value types have to be boxed. Given your previous vehement objection to
boxing, I'd think you'd want to stay as far away as possible from
something like CollectionBase and IList.
So, a CollectionBase is indeed an IList because I can pass it as an
IList and, if I do, I can invoke operations on IList, such as
Add(Object val). But, if I deal with my class S as type S and try to
call Add(Object val), I get a compile-time error. So, it appears that
a class derived from CollectionBase is somewhat schizophrenic?

It does appear that way, doesn't it? But it's not really quite that bad.

Again, the main thing going on here is the difference between implicit
and explicit interface implementations.

However, the other thing you've noticed is the danger in using
interfaces, classes, and methods that are based on "object". I honestly
have no idea why MSDN call the CollectionBase class a "base class for a
strongly typed collection". I mean, sure...you can build a sort-of
strongly typed collection on top of CollectionBase, but because of the
IList end-around, it's only strongly typed if you never cast the
collection to an IList.

To me, that's not really a strongly typed collection.

Of course, generics _are_ strongly typed, and are actually the right way
to go IMHO if what you really want is a strongly typed collection. You
really can't get objects of the wrong type into a generic class,
assuming you've declared the class correctly (e.g. don't declare a
List<object> if you really want a List<int>).

My guess is that the CollectionBase class is described as a way to
implement strongly typed collections mainly because in 1.1 there were no
generics and so that's the closest .NET could come to a true "strongly
typed collection" implementation while still supporting the interfaces
like IList.

One hopes that, had generics been around when CollectionBase was first
created and documented, no doc writer would dream of implying that
CollectionBase is actually a good foundation on which to create a truly
strongly typed collection.

One hopes.

Pete
 
M

Michi Henning

Michi said:

CollectionBase has an explicit implementation of the IList interface.
This means that the method declaration in the class is "IList.Add"
rather than simply "Add" (the latter would be an implicit
implementation, and the method would be available through both the class
and the inherited interface).

Ah, OK, thanks for that. I wasn't aware of this. So I can implement
CollectionBase myself in C# after all.
It does, and you can use any IList member as long as you cast
CollectionBase to an IList. Because it doesn't implicitly implement the
interface, you _must_ get an IList before using the methods though.

Yes. Of course, if I pass containers around generically, that happens
quite often.
Yup. Similar dangers exist with pretty much any of the classes that
ultimately resolve to containing a collection of "object" instances.

The other problem with these kinds of collection is, of course, that
value types have to be boxed. Given your previous vehement objection to
boxing, I'd think you'd want to stay as far away as possible from
something like CollectionBase and IList.

Well, that's exactly where I'm coming from. The current version of Ice
for C#
uses CollectionBase to implement sequences. That's because the
..NET 1.1 documentation made a strong recommendation to
use CollectionBase:

Notes to Implementers
This base class is provided to make it easier for implementers to
create a
strongly typed custom collection. Implementers are encouraged to
extend
this base class instead of creating their own.

Now that C# supports generics, naturally, I want to provide a new
mapping
that uses generics. But I cannot just throw away the CollectionBase
mapping
that is used currently because I need to maintain backward
compatibility.
Hence the need to pass collections generically.
It does appear that way, doesn't it? But it's not really quite that bad.

Well, if CollectionBase cannot guarantee its supposed type safety,
that makes it rather moot, doesn't it?
I honestly
have no idea why MSDN call the CollectionBase class a "base class for a
strongly typed collection".

Me neither.
I mean, sure...you can build a sort-of
strongly typed collection on top of CollectionBase, but because of the
IList end-around, it's only strongly typed if you never cast the
collection to an IList.

To me, that's not really a strongly typed collection.

My sentiment too.
Of course, generics _are_ strongly typed, and are actually the right way
to go IMHO if what you really want is a strongly typed collection. You
really can't get objects of the wrong type into a generic class,
assuming you've declared the class correctly (e.g. don't declare a
List<object> if you really want a List<int>).

See above. I have no choice but to deal with CollectionBase.
My guess is that the CollectionBase class is described as a way to
implement strongly typed collections mainly because in 1.1 there were no
generics and so that's the closest .NET could come to a true "strongly
typed collection" implementation while still supporting the interfaces
like IList.

Except that this results in this rather strange behavior. It seems it
would
have been better to not provide CollectionBase at all and accept the
fact
that, without generics, there cannot be any such thing as a truly type-
safe
container. Providing a base class (and strongly advocating its use)
that,
in the end, does not achieve what it sets out to do probably does more
harm than good. (I know that it's definitely hurting me right now.)
One hopes that, had generics been around when CollectionBase was first
created and documented, no doc writer would dream of implying that
CollectionBase is actually a good foundation on which to create a truly
strongly typed collection.

I would say that it's not a good foundation even without generics.
Either
the class is type-safe or it isn't. Making it "half type-safe" only
creates
a false sense of security.

Cheers,

Michi.
 
M

Michi Henning

Following up on my own post, I just notice that the framework contains
a large number of classes that derive from CollectionBase. All these
classes are vulnerable to exactly the same type inconsistency that I
mentioned originally. That's probably not a good thing.

Cheers,

Michi.
 
S

schneider

I think your missing a method which allows you to enforce:

protected override void OnValidate(object value) {

base.OnValidate(value);

}
 
M

Michi Henning

I think your missing a method which allows you to enforce:

protected override void OnValidate(object value) {

base.OnValidate(value);

}

Does this really work? The OnValidate method would be implemented in
S, which derives from CollectionBase. But, when I pass S as an IList,
I'm passing the instance as a base interface. Seeing that calling
Add() on the base interface invokes the implementation of Add() in
CollectionBase and not the implementation of Add() in S, wouldn't the
OnValidate() method that runs also be the one in CollectionBase
instead of the one in S?

(I'm away from work and don't have a compiler here, so I can't try
this immediately. But that's the behavior I would expect.)

If it happens to work, this would prevent the string from getting into
the collection in the first place, which is better than only finding
out that something is wrong when I pull the values out of the
collection and suddenly get a string instead of an int (or, rather, a
cast exception).

But this still leaves a bad taste in my mouth because the whole point
of CollectionBase is to have a collection that is type-safe at compile
time, but OnValidate delays error detection until run time.

Cheers,

Michi.
 
P

Peter Duniho

Michi said:
Does this really work?

That depends on your definition of "really work".
The OnValidate method would be implemented in
S, which derives from CollectionBase.
Yes.

But, when I pass S as an IList,
I'm passing the instance as a base interface.

Yes. But it's the same instance, nevertheless. The only thing that's
changed is what methods are visible to the code using the instance via
the reference types as an IList versus a CollectionBase-derived object.

This has a variety of implications, but the most important one here is
that even if there's some code that only sees the instance as an IList,
if that code calls something that eventually calls OnValidate(), the
override _will_ be called. It has to; it's the same instance.
Seeing that calling
Add() on the base interface invokes the implementation of Add() in
CollectionBase and not the implementation of Add() in S, wouldn't the
OnValidate() method that runs also be the one in CollectionBase
instead of the one in S?

No. Your Add() method is not an override of the existing IList.Add()
method. It's a completely new Add() method, necessarily so because your
Add() method has a specific type different from any other Add() method
already in the class. At best, it's an additional overload of an
existing Add() method, but since CollectionBase doesn't have a general
Add() method it's not even that.

But OnValidate() is an override method, of the existing OnValidate
virtual method. As such, any time OnValidate(Object value) is called
using that instance, the override itself is called. This is true
regardless of how code using the instance refers to it.
(I'm away from work and don't have a compiler here, so I can't try
this immediately. But that's the behavior I would expect.)

If it happens to work, this would prevent the string from getting into
the collection in the first place, which is better than only finding
out that something is wrong when I pull the values out of the
collection and suddenly get a string instead of an int (or, rather, a
cast exception).

But this still leaves a bad taste in my mouth because the whole point
of CollectionBase is to have a collection that is type-safe at compile
time, but OnValidate delays error detection until run time.

I disagree with the assertion that "the whole point of CollectionBase is
to have a collection that is type-safe at compile time". That may be
your intent, but there's nothing about CollectionBase or its
documentation that suggest that's the point, and in fact it should be
obvious that that _isn't_ the point.

If you want compile-time type-safeness, use generics. _Those_ are
specifically intended for supporting compile-time strong typing.

For what it's worth, I overlooked OnValidate (and the other OnXXX
methods), not using CollectionBase myself. Seeing those now, I think it
makes more sense for CollectionBase to be documented as providing for
strongly-typed behavior. The strong typing is a run-time rather than
compile time, but it does exist and I see now that it's more reasonable
for the documentation to make that statement (though I would still argue
that the documentation should be more clear about distinguishing
run-time versus compile-time).

In fact, if only I'd looked more closely at the sample code on the main
CollectionBase doc page, where they implement OnValidate and throw an
exception when the type is wrong, I would have realized my mistake
earlier. From that, I think it's much more clear in what way they mean
CollectionBase supports strong typing. Sure, it should be more explicit
elsewhere as well, but the specifics are in fact there for someone
willing to take the time to examine the docs thoroughly.

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top