Greg Hickman
Greg Hickman

Reputation: 41

Why No Implicit Conversion Allowed In This Generic Code?

In the implementation of GetEnumerator() below, the compiler refuses to convert List<T>.Enumerator to IEnumerator<Shape> even though T is constrained by Shape. (1) Why is this, and (2) is there a workaround I'm overlooking?

using System.Collections.Generic;

interface Shape {
    void Draw();
}

class Picture<T> : IEnumerable<Shape> where T : Shape {  
List<T> shapes;
    public IEnumerator<Shape> GetEnumerator()
    {
        return shapes.GetEnumerator(); 
    }
}

Upvotes: 3

Views: 69

Answers (3)

Kit
Kit

Reputation: 21699

The compiler simply does not support this kind of type inference. If your types can be reference types, Ben's answer is the way to go. If not you're stuck with workarounds.

There are a few workarounds. Choose the one that makes sense for you.

  1. Use Enumerable.Cast.
public IEnumerator<Shape> GetEnumerator()
{
    return shapes.Cast<Shape>().GetEnumerator(); 
}
  1. Make the list a list of Shape. Drawback is that your implementation will have to cast back to T at some point. Here's an example with some methods I've added.
List<Shape> shapes = new List<Shape>();
public IEnumerator<Shape> GetEnumerator()
{
    return shapes.GetEnumerator(); 
}

public void AddShape(T shape) => shapes.Add(shape);

public T GetShape() => (T)shapes.First();

Upvotes: 0

Guru Stron
Guru Stron

Reputation: 141600

Because variance does not work for value types. So if it is suitable for you can constrain T to be a class:

private class Picture<T> : IEnumerable<Shape> where T : class, Shape
{
    private List<T> shapes;
    public IEnumerator<Shape> GetEnumerator()
    {
        return shapes.GetEnumerator();
    }
}

Upvotes: 1

Ben Voigt
Ben Voigt

Reputation: 283624

Change the constraint from

where T : Shape

to

where T : class, Shape

Interface covariance doesn't work for value types.

Documented here:

Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.

Upvotes: 6

Related Questions