Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Question about Generics

341 views
Skip to first unread message

hamma...@gmail.com

unread,
Jun 30, 2006, 2:49:22 PM6/30/06
to
Hello,

I'm wondering if it's possible to do the following with Generics:

Let's say I have a generic member variable as part of a generic class
like this:

List<DLinqQuery<TDataContext>> _queries;

where DLinqQuery is a generic class that takes a type parameter
TDataContext.

This member variable is part of a generic class called
DLinqDataSource<TDataContext> and is exposed as a public property of
the specified type.

It is assumed that all TDataContext types will be derived from the
concrete class DataContext.

Now, let's say I derive a class from DataContext called MyDataContext.

I the create two concrete classes like this:

public class MyDataQuery : DLinqQuery<MyDataContext>{}
public class MyDataSource : DLinqDataSource<MyDataContext>{}

I want to be able to override the property in the MyDataSource class to
return a List<MyDataQuery> instead of the generic
List<DLinqQuery<TDataContext>> which would become
List<DLinqQuery<MyDataContext> in an instantiation of MyDataSource.

Unfortunately, casting the base property value (which is a
List<DLinqQuery<MyDataContext>> to the type List<MyDataQuery> just
doesn't work, but from a functional perspective it seems like that it
would.

Any ideas on whether this can be done?

Nicholas Paldino [.NET/C# MVP]

unread,
Jun 30, 2006, 3:05:43 PM6/30/06
to
The reason you can not do this is because generic types are not
covariant (I believe that is the term). This means that if you have a type
Dog which derives from Animal, and a List<Dog>, you can not cast it to a
List<Animal>.

The reason for this is that when you have a class, Cat which derives
from Animal, and you have the new List<Animal> (cast from List<Dog>), that
list wouldn't know what to do with the Cat when it is added.

The workaround here is to define your classes like so:

public class MyDataQuery : DLinqQuery<MyDataContext>{}

public class MyDataSource<T> : DLinqDataSource<T>{}

Then, when you declare your MyDataSource, you would do this:

MyDataSource<MyDataQuery> ds = new MyDataSource<MyDataQuery>();

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- m...@spam.guard.caspershouse.com


"hammad.aw...@gmail.com" <hamma...@gmail.com> wrote in message
news:1151693362.6...@y41g2000cwy.googlegroups.com...

Tom Spink

unread,
Jun 30, 2006, 3:11:09 PM6/30/06
to
hammad.aw...@gmail.com wrote:

Hi,

This question has been asked a few times here. The answer I gave to one of
them was that you cannot cast it, because a List<A> is not, and is not a
derivative of a List<B> if A derives from B. They're two different types,
because the type is expressed as a result of the type name and it's type
parameters.

So, you'll have to write a function that will convert the
List<DLinqQuery<MyDataContext>> to List<MyDataQuery>. Also, the definition
of override is to change the behaviour of a virtual method defined in a
base class, not to alter it's signature, e.g.:

public virtual bool foo ( int bar ) { ... }

Can only be overriden to change the behaviour of 'foo', not it's return
type, and not it's parameters.

The generic List class contains a generic method called ConvertAll<> which
can help you convert the list, or you can just enumerate through your list
and build a brand new one.

--
Hope this helps,
Tom Spink

Jon Skeet [C# MVP]

unread,
Jul 2, 2006, 8:55:06 AM7/2/06
to
Nicholas Paldino [.NET/C# MVP] <m...@spam.guard.caspershouse.com> wrote:
> The reason you can not do this is because generic types are not
> covariant (I believe that is the term).

It is - although the CLR itself supports covariance. You can tell the
CLR that something is a list of "some type which is derived from Dog"
or "some type which is a super-type of Dog". However, C# doesn't expose
this, and neither do the framework libraries.

It's a bit of a shame - the Java libraries *do* expose this, and
although it's a fairly confusing topic, it can be very handy. With Mads
on the C# team now, I wouldn't be surprised to see
covariance/contravariance appear in a later version - although now the
standard libraries have been defined without support, the usefulness of
them would be very much diminished.

(I should say that this is one of the very few places where Java
generics are better than C# generics.)

--
Jon Skeet - <sk...@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too

Tom Spink

unread,
Jul 2, 2006, 12:28:30 PM7/2/06
to
Jon Skeet [C# MVP] wrote:

> Nicholas Paldino [.NET/C# MVP] <m...@spam.guard.caspershouse.com> wrote:
>> The reason you can not do this is because generic types are not
>> covariant (I believe that is the term).
>
> It is - although the CLR itself supports covariance. You can tell the
> CLR that something is a list of "some type which is derived from Dog"
> or "some type which is a super-type of Dog". However, C# doesn't expose
> this, and neither do the framework libraries.
>
> It's a bit of a shame - the Java libraries *do* expose this, and
> although it's a fairly confusing topic, it can be very handy. With Mads
> on the C# team now, I wouldn't be surprised to see
> covariance/contravariance appear in a later version - although now the
> standard libraries have been defined without support, the usefulness of
> them would be very much diminished.
>

Hi Jon,

> (I should say that this is one of the very few places where Java
> generics are better than C# generics.)

I agree with that, very reluctantly. I've always found Java generics nasty,
since it's not a feature of the JVM, but a feature of the compiler,
unlike .NET which is a feature of the CLI, not the compiler, which IMHO,
the latter feels nicer. <g>

Jon Skeet [C# MVP]

unread,
Jul 2, 2006, 1:36:02 PM7/2/06
to
Tom Spink <tsp...@gmail.com> wrote:
> > (I should say that this is one of the very few places where Java
> > generics are better than C# generics.)
>
> I agree with that, very reluctantly. I've always found Java generics nasty,
> since it's not a feature of the JVM, but a feature of the compiler,
> unlike .NET which is a feature of the CLI, not the compiler, which IMHO,
> the latter feels nicer. <g>

It's certainly better to be part of the platform itself (although it's
still a feature of the compiler, of course. If the compiler doesn't
support generics, you're not going to be able to use them). I'd say
it's still nicer to have Java-style generics than not to have them at
all, but it would have been much better to have .NET-style generics,
even without covariance.

Barry Kelly

unread,
Jul 2, 2006, 9:06:40 PM7/2/06
to
Jon Skeet [C# MVP] <sk...@pobox.com> wrote:

> Nicholas Paldino [.NET/C# MVP] <m...@spam.guard.caspershouse.com> wrote:
> > The reason you can not do this is because generic types are not
> > covariant (I believe that is the term).
>
> It is - although the CLR itself supports covariance. You can tell the
> CLR that something is a list of "some type which is derived from Dog"
> or "some type which is a super-type of Dog". However, C# doesn't expose
> this, and neither do the framework libraries.
>
> It's a bit of a shame - the Java libraries *do* expose this, and
> although it's a fairly confusing topic, it can be very handy. With Mads
> on the C# team now, I wouldn't be surprised to see
> covariance/contravariance appear in a later version - although now the
> standard libraries have been defined without support, the usefulness of
> them would be very much diminished.
>
> (I should say that this is one of the very few places where Java
> generics are better than C# generics.)

A problem with enabling covariance and contravariance is that it would
add type-checks at the CLR level to operations which currently don't
need a type-check. For example, covariance of object arrays is enabled
in C#. As a result, all writes into an object array cause a runtime
type-check.

The type-safe way to enable covariance and contravariance is to limit
the methods callable on a generic parameter explicitly allowed to accept
covariant or contravariant values. Basically, variables of a covariant
type are allowed to return the covariant type in out or return
parameters only, while variables of a contravariant type are allowed to
accept values of a contravariant types only.

There is good coverage of this issue in a recent MS paper:

Variance and Generalized Constraints for C# Generics
Burak Emir, Andrew J. Kennedy, Claudio Russo, Dachuan Yu
July 2006

http://research.microsoft.com/research/pubs/view.aspx?type=inproceedings&id=1215

Direct link:
http://research.microsoft.com/~akenn/generics/ECOOP06.pdf

Discussion on Lambda the Ultimate:

http://lambda-the-ultimate.org/node/1573

-- Barry

--
http://barrkel.blogspot.com/

Jon Skeet [C# MVP]

unread,
Jul 3, 2006, 1:40:28 AM7/3/06
to
Barry Kelly <barry....@gmail.com> wrote:
> > (I should say that this is one of the very few places where Java
> > generics are better than C# generics.)
>
> A problem with enabling covariance and contravariance is that it would
> add type-checks at the CLR level to operations which currently don't
> need a type-check. For example, covariance of object arrays is enabled
> in C#. As a result, all writes into an object array cause a runtime
> type-check.

That's one sort of covariance, yes.



> The type-safe way to enable covariance and contravariance is to limit
> the methods callable on a generic parameter explicitly allowed to accept
> covariant or contravariant values. Basically, variables of a covariant
> type are allowed to return the covariant type in out or return
> parameters only, while variables of a contravariant type are allowed to
> accept values of a contravariant types only.

That's the sort of covariance/contravariance I was thinking of :)
Perhaps there ought to be two different terms for it...

Barry Kelly

unread,
Jul 3, 2006, 2:09:06 AM7/3/06
to
Jon Skeet [C# MVP] <sk...@pobox.com> wrote:

> Barry Kelly <barry....@gmail.com> wrote:
> > > (I should say that this is one of the very few places where Java
> > > generics are better than C# generics.)
> >
> > A problem with enabling covariance and contravariance is that it would
> > add type-checks at the CLR level to operations which currently don't
> > need a type-check. For example, covariance of object arrays is enabled
> > in C#. As a result, all writes into an object array cause a runtime
> > type-check.
>
> That's one sort of covariance, yes.
>
> > The type-safe way to enable covariance and contravariance is to limit
> > the methods callable on a generic parameter explicitly allowed to accept
> > covariant or contravariant values. Basically, variables of a covariant
> > type are allowed to return the covariant type in out or return
> > parameters only, while variables of a contravariant type are allowed to
> > accept values of a contravariant types only.
>
> That's the sort of covariance/contravariance I was thinking of :)
> Perhaps there ought to be two different terms for it...

If one describes reading from an array as a method "T GetValue(int)",
and writing into an array as "void SetValue(int, T)", then there is an
exact correspondence between covariance/contravariance in classes and
covariance in arrays, as described above. They're the same thing. The
SetValue(int, T) method should be contravariant, not covariant - e.g.
considering arrays of dogs and mammals, if SetValue() is going to be the
only "method" called on the array, then one should be able to supply an
array of mammals where an array of dogs is expected. Slightly
counter-intuitive, perhaps, but that's where the "contra" comes in :)

The paper gives good coverage of a possible way of introducing it into
C#. My only concern is that it would confuse things, since reasoning
about covariance / contravariance can be a little tricky.

Jon Skeet [C# MVP]

unread,
Jul 3, 2006, 4:37:39 AM7/3/06
to
Barry Kelly wrote:
> > That's the sort of covariance/contravariance I was thinking of :)
> > Perhaps there ought to be two different terms for it...
>
> If one describes reading from an array as a method "T GetValue(int)",
> and writing into an array as "void SetValue(int, T)", then there is an
> exact correspondence between covariance/contravariance in classes and
> covariance in arrays, as described above. They're the same thing.

Not quite (IMO). The CLR supports covariance/contravariance by allowing
a type to be declared as List<Stream+> or List<Stream-> for instance.
That is a sort of *declarative* covariance/contravariance which can
tell the compiler that it *will* support one of GetValue or SetValue
(and which one :) That's not the same as the runtime
covariance/contravariance of arrays in terms of compile-time type
safety, so it's worth making the distinction between them. The CLR
supports compile-time-unsafe covariance for arrays, but
compile-time-safe covariance/contravariance for generics. C# supports
compile-time-unsafe covariance for arrays, but has no concept of the
compile-time-safe generic covariance/contravariance supported by the
CLR.

Does that explain the distinction I make between the two sorts of
covariance any better?

Jon

Barry Kelly

unread,
Jul 3, 2006, 8:19:14 AM7/3/06
to
"Jon Skeet [C# MVP]" <sk...@pobox.com> wrote:

> Barry Kelly wrote:
> > > That's the sort of covariance/contravariance I was thinking of :)
> > > Perhaps there ought to be two different terms for it...
> >
> > If one describes reading from an array as a method "T GetValue(int)",
> > and writing into an array as "void SetValue(int, T)", then there is an
> > exact correspondence between covariance/contravariance in classes and
> > covariance in arrays, as described above. They're the same thing.
>
> Not quite (IMO). The CLR supports covariance/contravariance by allowing
> a type to be declared as List<Stream+> or List<Stream-> for instance.

Let's be clear in exactly what the CLR does and doesn't support, within
the spectrum of variance. I'll try and enumerate the different
possibilities, as far as I know them:

1) Covariance using inheritance, for return values / out-parameters (or
contravariance using inheritance, for in-arguments). This includes the
covariance supported by C++. If it were valid in C#, one could declare:

class Mammal
{
public virtual Mammal GetValue()
{
Console.WriteLine("Mammal::GetValue");
return null;
}
}

class Dog : Mammal
{
public override Dog GetValue()
{
Console.WriteLine("Dog::GetValue");
return null;
}
}

This will get an error in C#. It isn't supported in IL, because the
return type is part of the method specification, and there's no
"override" keyword in IL. Thus, the IL translation of the above would
define "Dog::GetValue" as a new, overloaded virtual method. Finally,
even though native C++ supports the construct, managed C++ does not. It
gives this error message:

---8<---
error C2392: 'Dog ^Dog::GetValue(void)' : covariant returns types are
not supported in managed types, otherwise 'Mammal
^Mammal::GetValue(void)' would be overridden
--->8---

2) Covariance / contravariance of generic parameters. The CLI spec (II
9.5) says that covariance and contravariance is supported for interfaces
and delegate types, but not for class or value types. The CLI support is
at the definition site, not the use site. The PDF I linked to doesn't
talk about the existing CLI support, but rather about a different kind
of variance - use-site variance. The CLI support is for definition-site
variance. This is different to Java 5 variance support, which supports
use-site variance.

So, neither List<-Stream> nor List<+Stream> can be declared. Instead,
the declaration would need to look like List<+T> or List<-T>. And even
then, List<> would need to be an interface, and what's more, the
covariant definition can't accept T values as input arguments for any of
its methods, and similarly the contravariant can't return T values.

---8<---
class Mammal { }
class Dog : Mammal { }

interface IReader<+T> // allows covariance
{
T GetValue();
}

interface IWriter<-T> // allows contravariance
{
void SetValue(T value);
}
--->8---

2a) Covariance of generic parameters.

This would allow the following, using the CIL syntax for covariance:

IReader<Dog> dogReader = null;
IReader<Mammal> mammalReader = dogReader;

2b) Contravariance of generic parameters.

This would allow:

IWriter<Mammal> mammalWriter = null;
IWriter<Dog> dogWriter = mammalWriter;

These are both disallowed in C#, but allowed at the IL level. Here's a
test IL file which assembles and passes PEVerify:

---8<---
.assembly extern mscorlib {}
.assembly Test {}

.class private auto ansi beforefieldinit Mammal
extends [mscorlib]System.Object {}

.class private auto ansi beforefieldinit Dog
extends Mammal {}

.class interface private abstract auto ansi IReader`1<+T>
{
.method public hidebysig newslot abstract virtual
instance !T GetValue() cil managed {}
}

.class interface private abstract auto ansi IWriter`1<-T>
{
.method public hidebysig newslot abstract virtual
instance void SetValue(!T 'value') cil managed {}
}

.class private auto ansi beforefieldinit App
extends [mscorlib]System.Object
{
.method private hidebysig static void Main() cil managed
{
.entrypoint
.locals init (
[0] class IReader`1<class Dog> dogReader,
[1] class IReader`1<class Mammal> mammalReader,
[2] class IWriter`1<class Mammal> mammalWriter,
[3] class IWriter`1<class Dog> dogWriter)

ldnull
stloc.0

ldloc.0
stloc.1

ldnull
stloc.2

ldloc.2
stloc.3

ret
}
}
--->8---

If one switches around the assignments, to try and treat covariance
contravariantly and vice versa, changing the body of the Main method to:

---8<---
ldnull
stloc.1

ldloc.1
stloc.0

ldnull
stloc.3

ldloc.3
stloc.2

ret
--->8----

One then gets the following errors from PEVerify:

---8<---
[IL]: Error: [App::Main][found ref 'IReader`1[Mammal]'][expected ref
'IReader`1[Dog]'] Unexpected type on the stack.
[IL]: Error: [App::Main][found ref 'IWriter`1[Dog]'][expected ref
'IWriter`1[Mammal]'] Unexpected type on the stack.
--->8---

Similarly, if one tries to make IReader<+T> contravariant, i.e. change
to IReader<-T>, and IWriter<-T> covariant, one gets the following
errors:

---8<---
[token 0x02000004] Type load failed.
[token 0x02000005] Type load failed.
--->8---

So, the covariant and contravariant support is there.

3) Covariance of arrays, the way C# and Java supports it.

This acts more or less like T[] was defined "Array<+T>", with the
addition of a run-time check for input arguments, which can only support
contravariance, not covariance.

> That is a sort of *declarative* covariance/contravariance which can
> tell the compiler that it *will* support one of GetValue or SetValue
> (and which one :) That's not the same as the runtime
> covariance/contravariance of arrays in terms of compile-time type
> safety, so it's worth making the distinction between them. The CLR
> supports compile-time-unsafe covariance for arrays, but
> compile-time-safe covariance/contravariance for generics. C# supports
> compile-time-unsafe covariance for arrays, but has no concept of the
> compile-time-safe generic covariance/contravariance supported by the
> CLR.
>
> Does that explain the distinction I make between the two sorts of
> covariance any better?

It does, in so far as array covariance isn't provably sound - instead it
relies on runtime type checks. That's one clear demarcation.

Jon Skeet [C# MVP]

unread,
Jul 3, 2006, 8:45:25 AM7/3/06
to
Barry Kelly wrote:

<snip>

Thanks for that. I hadn't realised that only interfaces were supported
in the generics side of things. I'll have to digest that further to see
what it actually allows. (I'm too tired to do so at the minute - to
properly understand covariance and contravariance I have to be pretty
wide-awake, which isn't compatible with having young twins.)

Nice job on summarising things - and I'm glad my distinction made at
least a *bit* of sense in the end :)

Jon

0 new messages