SharpDevelop Community

Get your problems solved!
Welcome to SharpDevelop Community Sign in | Join | Help
in Search

Itai Bar-Haim

Generics... why so incomplete?

Hi all.

In this blog post I am going to whine a lot about the missing features of generics, that make the coders miserable.

Generally, there is one repeating case that's giving me the hard time: Passing generic-based collections.

Lets look at the following example:

 
class Circle : Shape { ... } 

void foo (IEnumerable<Shape> shapes)  { ... }

void bar ()
{
    foo (new List<Circle>());
}
 

You get a compilation error for that one. I can't understand why. Logically, if a Circle is a Shape and a List is an IEnumerable, than a List of Circles is an Enumeration of Shapes! Why is it wrong?

Another example occure when using the 'where' clause:

class ShapesHandler<T> where T : Shape
{
    void foo(params T[] shapes) { ... }

    void bar ()
    {
        List<Shape> someShapes = new List<Shape>();
        foo (someShapes.ToArray());
    }
}

This also yields a compilation error. And of course the same goes if I had an array of Circles that I wanted to cast to an array of Shapes. The same error.

Again, I cannot understand why; If the class ShapesHandler specifically defines that it can take any class type that is a Shape, why can't I pass in an array of shapes where ever an array of T is needed, and why can't I cast an array of T to an array of Shapes?

(If you have a reasonable explanation, I would be glad to hear it. So far I only got the pretty lame explanation of 'this is a different type, this is how the compiler works' which is really unacceptable by me as it only describes the problem in a different words)

Itai. 

Comments

 

chrisstefano said:

There are a number of blogs on this subject. I believe the term to use is covariance.

Checkout this blog on the subject http://blogs.msdn.com/rmbyers/archive/2005/02/16/375079.aspx

February 16, 2007 2:15 PM
 

MattWard said:

The following O'Reilly article discusses this, but it does not really give you a concrete reason why but instead gives you an example where such a substitution does not make sense and then shows you to solve the problem using generic methods.

http://www.ondotnet.com/pub/a/dotnet/2005/06/20/generics.html

You can get your code to compile if you use generic methods as shown below:

void foo<T>(IEnumerable<T> shapes) where T : Shape

{

}

void bar ()

{

foo (new List<Circle>());

}

class ShapesHandler<T> where T : Shape

{

void foo<K>(params K[] shapes) where K : Shape

{  

}

void bar ()

{

List<Shape> someShapes = new List<Shape>();

foo (someShapes.ToArray());

}

}

The ShapesHandler code above needs a little more explanation since it is not really covered in the original article. At first glance your original definition would seem correct. However the original code only works if it outside of the ShapesHandler class, presumably because the type specific version needs to be created first. Inside the class itself you could probably use the type T instead of the concrete Shape class, something like:

void bar ()

{

List<T> someShapes = new List<T>();

foo (someShapes.ToArray());

}

However all the above code does not explain the "why" just the "how". The "why" is explained in the Rick Byers post linked to in the previous comment.

February 16, 2007 2:34 PM
 

DavidSrbecky said:

Not even looking at the articles I will try to give quick counter-examples:

Case 1:

List<Circle> c = new List<Circle>();

List<Shape> s = c;  // Must not work because then you could...

s.Add(new Rectangle()); // ...add rectangle to circle list

Case 2:

class ShapesHandler<T> where T : Shape

{

   T foo(params T[] shapes) {

     return shapes[0];

   }

}

void bar ()

{

  ShapesHandler<Circle> circleHandler = new ShapesHandler<Circle>();

  Circle circle = circleHandler.foo (new Shape[] {new Rectangle()}); // Assign an Rectangle to Circle... ops

}

February 18, 2007 11:26 PM
 

Blue Ninja said:

David,

In your first case, you are adding a rectangle to the list of shapes, not the list of circles. As long as circle and rectangle inherit from shape, that would be okay - but, you could not add the rectangle to a list of circles, because rectangles don't inherit from circles, but rather from shapes. Or am I misunderstanding that?

If I didn't misunderstand, wouldn't that change case 2 so that it would in fact not be possible to put a rectangle into a circleHandler?

February 19, 2007 3:53 PM
 

itaibh said:

No, David is correct in his examples. It doesn't matter which type is exposed by reference, what really matters is the actual type behind it.

However, this should still be possible with immutable object, such as read-only lists.

Something like

List<Circle> c = new List<Circle>();

ReadOnlyList<Shape> s = c.AsReadOnly();

should have worked fine if it was possible to mark specific types as immutable (or if the CLR could identify that the types does not expose any method or property or field in such way it is changeable by regular code (not dealing with special cases like reflection based changes of private members, etc...)

Itai.

February 19, 2007 7:07 PM
 

Blue Ninja said:

Well, no wonder it's making so many people pull their hair out, then! I can see how this would in many cases cause more confusion and frustration than it would be handy. I'll have to wrap my brain around it a bit more...

February 19, 2007 9:19 PM
 

DavidSrbecky said:

Yes, rest assured that this behavior is not 'incompleteness'.  It is very well thought out and if something is not possible, it is usually for very good reason.

February 22, 2007 9:11 PM
Powered by Community Server (Commercial Edition), by Telligent Systems
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.