Upcast of generics in C# 3.0

G

Guest

Hello,

I was wondering if C# 3.0 finally supported generic upcasting. Consider the
following code which does work in C# 2:

string[] xs = {"this", "is", "a", "test"};
object[] ys = xs;

Now, analogously, I would expect the following to work as well:

IEnumerable<string> xs = new string[] { "this", "is", "a", "test" };
IEnumerable<object> ys = xs;

As of now, this doesn't work because C# is unable to perform the conversion.
Actually, it is obvious why this code doesn't work: it would require the
creation of a new object, an operation which is not generally supported for
generics at the moment.

However, there is actually no technical reason not to allow this in general
(a proposed solution would have the compiler generate implicit conversion
methods, which, unfortunately, is not possible at the moment either because
the current version of C# does not allow to formulate the required
constraint).

So, long story short, my question: Does this work in C# 3.0? And if it
doesn't: Is there a sensible reason for this? I need this kind of code
constantly and the workarounds I have to use are nothing short of annoying.
 
A

Allan Ebdrup

Konrad Rudolph said:
Hello,

I was wondering if C# 3.0 finally supported generic upcasting. Consider
the
following code which does work in C# 2:

string[] xs = {"this", "is", "a", "test"};
object[] ys = xs;

Now, analogously, I would expect the following to work as well:

IEnumerable<string> xs = new string[] { "this", "is", "a", "test" };
IEnumerable<object> ys = xs;

As of now, this doesn't work because C# is unable to perform the
conversion.
I need this kind of code
constantly and the workarounds I have to use are nothing short of
annoying.

Could you provide an example where you need this.

Kind Regards,
Allan Ebdrup
 
A

Allan Ebdrup

Allan Ebdrup said:
message news:[email protected]...
IEnumerable<string> xs = new string[] { "this", "is", "a", "test" };
IEnumerable<object> ys = xs;

Why do you need ys to be IEnumerable<object>
If it's because you want to pass it to a method (M) that takes an
IEnumerable<object> parameter like this:

public void M(IEnumerable<object> parameter){...}

You could chang M to generic method like this:

public void M<T>(IEnumerable<T> parameter) {...}

that way you can pass both IEnumerable<string> and IEnumerable<object> to M

IEnumerable<string> xs = ...
IEnumerable<object> ys = ...
M(xs); //this is valid
M(ys); //this is valid

The reason you dont have to specify T when calling M is because the type can
be inferred.
This way you can also pecify a method with a parameter of the type
IEnumerable<IMyInterface> like this

public void M<T>(IEnumerable<T> parameter) where T : IMyInterface {...}

Does this help any?

Kind Regards,
Allan Ebdrup
 
G

Guest

Hi,

thanks for your advice, but:
Does this help any?

Not really. You're right that I could do that in specific cases. However,
mostly I work with API that's already in place -- for example, consider the
'List<T>.AddRange' method. Here, the argument is of type 'IEnumerable<T>'. Of
course, in this simple case I could (and actually do) just call the 'Add'
method in a 'foreach' loop. But again, this is just a solution to a specific
issue and does not solve the more general problem.

In practice, I stumble across this kind of problem very frequently. In all
those cases, there are different solutions. Any of these solutions taken for
itself is quite acceptable but the whole problem, once put together,
introduces a coding overhead which I find insufferable. These workarounds
just encumber the code and make it generally harder do read because they
distract from the real problem my code is trying to solve. This is a kind of
problem that I would expect in low-level languages such as C but not in
(very) high-level languages such as C#.

The whole concept of having to employ workarounds just doesn't fit C#.
Therefore, generic template upcasting is the kind of things I expect C# to
simply do for me without any questions asked.
 
G

Guest

Considering the immense number of requests for covariance in future C#
versions, this additional thread is somewhat redundant.
 
B

Ben Voigt [C++ MVP]

Konrad Rudolph said:
Hello,

I was wondering if C# 3.0 finally supported generic upcasting. Consider
the
following code which does work in C# 2:

string[] xs = {"this", "is", "a", "test"};
object[] ys = xs;

In fact, this should not be allowed. The only reason .NET has it was to
attract Java programmers who have had that bug for a very long time.
Now, analogously, I would expect the following to work as well:

IEnumerable<string> xs = new string[] { "this", "is", "a", "test" };
IEnumerable<object> ys = xs;

Since IEnumerable only allows reading, this should be allowed. But it's not
the case for all interfaces. Also, you wouldn't want the compiler to infer
which interfaces are covariant or contravariant, because only the developer
knows whether the interface is likely to remain that way. There ought to be
an attribute to mark interfaces covariant or contravariant, but as far as I
know you can't pass type placeholders to attributes, nor even the actual
type. So without a significant redesign of .NET, you won't get that
behavior.
 
G

Guest

So without a significant redesign of .NET, you won't get that
behavior.

Well, as far as I know the .NET CTS actually *does* allow generic covariance
and contravariance. C# simply doesn't implement it. Perhaps the most obvious
solution would be to introduce Java-style generic wildcards.
 
N

Nicholas Paldino [.NET/C# MVP]

Konrad,

No, this is not supported in C# 3.0, and there is a good reason for it.

Instead of using the IEnumerable<T> interface, lets use List<T>
instances. Say for example you had this abstract class:

abstract class Animal {}

And the following concrete implementations:

class Dog : Animal {}
class Cat : Animal {}

If you then created a list of Dogs, like so:

List<Dog> d = new List<Dog>();

Assume you allowed the cast, as you suggest, and did this:

List<Animal> a = d;

Remember, when you cast, you don't actually change the type of what the
variable is referencing. While variable a is of type List<Animal> it points
to an instance of List<Dog>.

Now, because you have a List<Animal> you should be able to do this:

a.Add(new Cat());

Because a really points to a List<Dog>, you get a runtime exception.

Now, it could be argued that you want this behavior, but this is the
reason why it's not currently supported, or will be supported in C# 3.0.
 
J

Jon Skeet [C# MVP]

There ought to be
an attribute to mark interfaces covariant or contravariant, but as far as I
know you can't pass type placeholders to attributes, nor even the actual
type. So without a significant redesign of .NET, you won't get that
behavior.

I don't think there's an attribute for covariance/contravariance - I
think there's something in the "plain" metadata for the type.

It's unclear to me whether existing framework types could have the
covariance/contravariance which already exists in the CLR applied
retrospectively without breaking things. I suspect it'll never happen,
effectively :(

Jon
 
J

Jon Skeet [C# MVP]

Well, as far as I know the .NET CTS actually *does* allow generic covariance
and contravariance.

Only of interfaces, and only where it's safe to do so. It could apply
to IEnumerable<T> but not to IList<T> or List<T>. Most of the requests
C# simply doesn't implement it.

Neither do any framework types.
Perhaps the most obvious
solution would be to introduce Java-style generic wildcards.

I *believe* that only works as it does in Java because of type
erasure.

Jon
 
B

Ben Voigt [C++ MVP]

Konrad Rudolph said:
Well, as far as I know the .NET CTS actually *does* allow generic
covariance
and contravariance. C# simply doesn't implement it. Perhaps the most
obvious
solution would be to introduce Java-style generic wildcards.

A google for "Common Type System" and "covariance" returns countless hits,
changing to "generic covariance" returns exactly zero. Can you provide a
reference?
 
A

Allan Ebdrup

Konrad Rudolph said:
Hi,

thanks for your advice, but:


Not really. You're right that I could do that in specific cases. However,
mostly I work with API that's already in place -- for example, consider
the
'List<T>.AddRange' method. Here, the argument is of type 'IEnumerable<T>'.
Of
course, in this simple case I could (and actually do) just call the 'Add'
method in a 'foreach' loop. But again, this is just a solution to a
specific
issue and does not solve the more general problem.

That only shows that AddRange should have been a generic method.
AddRange<T2>(IEnumerable<T2>) where T2 : T {}
You could use the new extension methods to provide your own AddRange for
List, and fix it once and for all.
In practice, I stumble across this kind of problem very frequently. In all
those cases, there are different solutions. Any of these solutions taken
for
itself is quite acceptable but the whole problem, once put together,
introduces a coding overhead which I find insufferable. These workarounds
just encumber the code and make it generally harder do read because they
distract from the real problem my code is trying to solve. This is a kind
of
problem that I would expect in low-level languages such as C but not in
(very) high-level languages such as C#.

I personally almost never come across this problem, but I agree that if
there are more examples like the AddRange method. Microsoft should have
fixed this before shipping. You should report your findings to Microsoft and
we can all hope they fix it.
The whole concept of having to employ workarounds just doesn't fit C#.
Therefore, generic template upcasting is the kind of things I expect C# to
simply do for me without any questions asked.

There are other problems with upcasting that made them not include it, MS
wants to catch as many type errors as possible at compile time not at
runtime. Personally I like this, what we you are experiencing is that it
takes some time to get your head around this way of thinking, but when we
are all used to it, the benefits of the choices made for C# outweigh the
steep learning curve IMHO.

Kind Regards,
Allan Ebdrup
 

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