> [...]
> 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:
>
[quoted text clipped - 7 lines]
> 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
Michi Henning - 07 Sep 2007 08:42 GMT
> > [...]
>
[quoted text clipped - 3 lines]
> 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.
> > 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
[quoted text clipped - 5 lines]
> 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.
> > 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
[quoted text clipped - 7 lines]
> 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.
> > 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
[quoted text clipped - 3 lines]
>
> 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.
Michi Henning - 07 Sep 2007 08:48 GMT
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.
schneider - 07 Sep 2007 21:03 GMT
I think your missing a method which allows you to enforce:
protected override void OnValidate(object value) {
base.OnValidate(value);
}
> Following up on my own post, I just notice that the framework contains
> a large number of classes that derive from CollectionBase. All these
[quoted text clipped - 4 lines]
>
> Michi.
Michi Henning - 07 Sep 2007 21:57 GMT
On Sep 8, 6:03 am, "schneider"
> I think your missing a method which allows you to enforce:
>
[quoted text clipped - 3 lines]
>
> }
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.
Peter Duniho - 08 Sep 2007 01:22 GMT
> On Sep 8, 6:03 am, "schneider"
>> I think your missing a method which allows you to enforce:
[quoted text clipped - 6 lines]
>
> 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.)
[quoted text clipped - 8 lines]
> 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