3

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(); 
    }
}
2
  • This ought to be working, as IEnumerator<T> is declared covariant. See <out T> in the documentation, here: learn.microsoft.com/en-us/dotnet/api/… Commented Jun 29, 2021 at 21:55
  • I would have thought so, too. I'm stuck with VS 2015; perhaps the language/library variant I'm using (C# v 6, I believe) isn't up to the task? Commented Jun 29, 2021 at 21:59

3 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

1 Comment

That did the trick. It never occurred to me that one might implement an interface with a struct. Thank you.
1

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();
    }
}

Comments

0

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();

2 Comments

There's also .AsEnumerable().GetEnumerator() if I'm not mistaken.
Since List<T>.Enumerator is derived from IEnumerator<T> and the compiler knows T will be Shape or derived from Shape, it seems like a no-brainer for the compiler since implicit conversions from IEnumerator<Derived> to IEnumerator<Base> are supported by the language. Color me surprised, but thanks for your reply!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.