Generics question

  • Thread starter Thread starter Mark
  • Start date Start date
M

Mark

Hi...

I'm trying to create a generic collection where the higher level code can
deal with more specific derivations, but they want to share some general
cleanup code. I've tried the following

public class MyBaseCollection<T> : Dictionary<string, T> where T :
MyBaseObject
{
}

then I have two branches that make MyBaseCollection<Obj1> and
MyBaseCollection<Obj2> where both Obj1 and Obj2 derive from MyBaseObject.

They both want to know about what the derivations do, but they want to call
a general
void Cleanup(MyBaseCollection<MyBaseObject> coll)
{
foreach (MyBaseObject in coll)
{ // do something
}
}

Now, I know I should probably add the Cleanup() function as a method of my
collection, but I was curious why MyBaseCollection<Obj1> wouldn't be castable
to MyBaseCollection<MyBaseObject> since the definition guarantees that T will
derive from MyBaseObject?

Thanks
Mark
 
Now, I know I should probably add the Cleanup() function as a method of my
collection, but I was curious why MyBaseCollection<Obj1> wouldn't be castable
to MyBaseCollection<MyBaseObject> since the definition guarantees that T will
derive from MyBaseObject?

Generics aren't covariant.

For instance, a List<string> isn't castable to a List<object>. This is
actually good in most cases. Suppose we could do it, and consider this
situation:

List<Giraffe> x = new List<Giraffe>();
List<Animal> y = x;
y.Add(new Alligator());

Which line should fail to compile? Or what should blow up at runtime?
Bear in mind that this is a reference type, so if all of the above
executes with no problem, we have a list of giraffes which happens to
contain an alligator...
 
Mark said:
Hi...

I'm trying to create a generic collection where the higher level code can
deal with more specific derivations, but they want to share some general
cleanup code. I've tried the following

public class MyBaseCollection<T> : Dictionary<string, T> where T :
MyBaseObject
{
}

then I have two branches that make MyBaseCollection<Obj1> and
MyBaseCollection<Obj2> where both Obj1 and Obj2 derive from MyBaseObject.

They both want to know about what the derivations do, but they want to
call
a general
void Cleanup(MyBaseCollection<MyBaseObject> coll)
{
foreach (MyBaseObject in coll)
{ // do something
}
}

Now, I know I should probably add the Cleanup() function as a method of my
collection,
Exactly.

but I was curious why MyBaseCollection<Obj1> wouldn't be castable
to MyBaseCollection<MyBaseObject> since the definition guarantees that T
will
derive from MyBaseObject?

I'm no generics expert, but I believe that MyBaseCollection<Obj1> compiles
to something akin to "MyBaseCollection_Obj1", which in your case derives
from Dictionary and nothing else. In other words, MyBaseObject does *not*
participate in the inheritance tree. Neither does
MyBaseCollection<MyBaseObject> (or "MyBaseCollection_MyBaseObject", if you
will). When you declare a variable of type MyBaseCollection<Obj1> the
compiler creates a new collection class that holds instances of Obj1 and
derives from Dictionary, nothing more. It's like a "find & replace"
operation at compile-time, replacing "T" with "Obj1".

I know this isn't a very technical description, and I'm pretty new to
generics myself, but it's how I try to wrap my head around it.
 
On Feb 1, 3:44 pm, "Scott Roberts" <[email protected]
software.com> wrote:

I'm no generics expert, but I believe that MyBaseCollection<Obj1> compiles
to something akin to "MyBaseCollection_Obj1", which in your case derives
from Dictionary and nothing else. In other words, MyBaseObject does *not*
participate in the inheritance tree. Neither does
MyBaseCollection<MyBaseObject> (or "MyBaseCollection_MyBaseObject", if you
will). When you declare a variable of type MyBaseCollection<Obj1> the
compiler creates a new collection class that holds instances of Obj1 and
derives from Dictionary, nothing more. It's like a "find & replace"
operation at compile-time, replacing "T" with "Obj1".

No, that's not what happens at all. That's *slightly* like what
happens with C++ templates, but generics are very different. IL itself
knows about generics, and if you declare a variable of type
MyBaseCollection<MyBaseObject>, that's the declaration which will be
emitted in the IL. No new types are created for it.

At JIT time there's all kinds of clever stuff going on, but mostly you
don't need to care about that.

Jon
 
Jon Skeet said:
On Feb 1, 3:44 pm, "Scott Roberts" <[email protected]
software.com> wrote:



No, that's not what happens at all. That's *slightly* like what
happens with C++ templates, but generics are very different. IL itself
knows about generics, and if you declare a variable of type
MyBaseCollection<MyBaseObject>, that's the declaration which will be
emitted in the IL. No new types are created for it.

Would it be safe to view it as a new "type" from a design (or non-technical)
point of view? If not, why not? What types of things can I do with
MyBaseCollection<Obj1> that I couldn't do with a non-generic collection like
MyCollectionOfObj1?
 
Scott Roberts said:
Would it be safe to view it as a new "type" from a design (or non-technical)
point of view? If not, why not? What types of things can I do with
MyBaseCollection<Obj1> that I couldn't do with a non-generic collection like
MyCollectionOfObj1?

Well, it's a different type to (say) MyBaseCollection<Foo>, but there's
no need for the compiler to provide any information about the type.

As for what you can do with generics that you can't do otherwise -
that's far too big a question for a newsgroup post, unfortunately.
Probably best to look that one up in a book.
 
Jon Skeet said:
Well, it's a different type to (say) MyBaseCollection<Foo>, but there's
no need for the compiler to provide any information about the type.

As for what you can do with generics that you can't do otherwise -
that's far too big a question for a newsgroup post, unfortunately.
Probably best to look that one up in a book.

I am aware of the differences when designing classes. I'm asking about using
the classes (i.e. reference variables).

Let me rephrase. Suppose I have a class named MyCollectionOfObj1 and it
defines the exact same interface (methods and properties) as
MyBaseCollection<Obj1>. If I then have two reference variables:

MyBaseCollection<Obj1> a = new MyBaseCollection<Obj1>():
MyCollectionOfObj1 b = new MyCollectionOfObj1():

What kinds of things can I do with "a" that I can't do with "b"? I obviously
can't cast "a" to MyBaseCollection<MyBaseObject>, just like I can't cast "b"
to MyCollectionOfMyBaseObject. Is there anything I *can* do different with
"a"?

What I'm getting at is this: I view "a" as a reference variable of type
MyBaseCollection<Obj1>. You seem to be telling me that
MyBaseCollection<Obj1> is not technically a type. I'm asking "from a
functional perspective, what's the difference"?
 
Scott Roberts said:
I am aware of the differences when designing classes. I'm asking about using
the classes (i.e. reference variables).

Let me rephrase. Suppose I have a class named MyCollectionOfObj1 and it
defines the exact same interface (methods and properties) as
MyBaseCollection<Obj1>. If I then have two reference variables:

MyBaseCollection<Obj1> a = new MyBaseCollection<Obj1>():
MyCollectionOfObj1 b = new MyCollectionOfObj1():

What kinds of things can I do with "a" that I can't do with "b"? I obviously
can't cast "a" to MyBaseCollection<MyBaseObject>, just like I can't cast "b"
to MyCollectionOfMyBaseObject. Is there anything I *can* do different with
"a"?

Not if it has the same interface, no.
What I'm getting at is this: I view "a" as a reference variable of type
MyBaseCollection<Obj1>. You seem to be telling me that
MyBaseCollection<Obj1> is not technically a type. I'm asking "from a
functional perspective, what's the difference"?

No, it's definitely a type - but not one that ever requires specific
code within IL. All the code is within MyBaseCollection<T>. The C#
compiler doesn't build a new type declaration with all the code from
MyBaseCollection<T> with a "find and replace" of T => Obj1. It's just
done by the JIT compiler.
 
What I'm getting at is this: I view "a" as a reference variable of type
No, it's definitely a type - but not one that ever requires specific
code within IL. All the code is within MyBaseCollection<T>. The C#
compiler doesn't build a new type declaration with all the code from
MyBaseCollection<T> with a "find and replace" of T => Obj1. It's just
done by the JIT compiler.

Gotcha. So while it's not technically a new type, it's functionally
equivalent to a new type - at least at the source code level.
 
Scott Roberts said:
Gotcha. So while it's not technically a new type, it's functionally
equivalent to a new type - at least at the source code level.

The closed, constructed type is definitely new - it just doesn't have
any different IL code behind it. It's a different type at execution
time as well - you can ask an object what type it is and it'll know its
generic type parameters etc. The JIT will also JIT separately for value
type type parameters, but share code between reference type type
parameters. So:

List<string> and List<object> share native code, but
List<int> and List<double> don't.
 
Mark,

you should be able to do something like this.

void Cleanup<T>(MyBaseCollection<T> coll) where T:MyBaseObject
{
foreach (T in coll)
{ // do something
}
}
 
Back
Top