Search code examples
c#genericsienumerabletypecast-operator

Returning generic parameter with yield return


Hopefully this isn't a dupe, couldn't find anything related online

I'm getting a strange compile time error in the following extension method:

public static TCol AddRange<TCol, TItem>(this TCol e, IEnumerable<TItem> values) 
    where TCol: IEnumerable<TItem>
{
    foreach (var cur in e)
    {
        yield return cur;
    }
    foreach (var cur in values)
    {
        yield return cur;
    }
}

Error:

The body of 'TestBed.EnumerableExtensions.AddRange(TCol, System.Collections.Generic.IEnumerable)' cannot be an iterator block because 'TCol' is not an iterator interface type

Does this mean that generic constraints are not considered by the compiler when determining if a method qualifies for yield return use?

I use this extension method in a class which defines the collection using a generic parameter. Something like (in addition to a few type cast operators):

public class TestEnum<TCol, TItem>
    where TCol : class, ICollection<TItem>, new()
{
    TCol _values = default(TCol);

    public TestEnum(IEnumerable<TItem> values)
    {
        _values = (TCol)(new TCol()).AddRange(values);
    }
    public TestEnum(params TItem[] values) : this(values.AsEnumerable()) { }

    ...
}

And in turn, used like (remember I have type cast operators defined):

TestEnum<List<string>, string> col = new List<string>() { "Hello", "World" };
string someString = col;
Console.WriteLine(someString);

Originally, my extension method looked like:

public static IEnumerable<TItem> AddRange<TItem>(this IEnumerable<TItem> e, IEnumerable<TItem> values)
{
    ...
}

Which compiles but results in:

Unhandled Exception: System.InvalidCastException: Unable to cast object of type '<AddRange>d__61[System.String]' to type 'System.Collections.Generic.List1[System.String]'.

Is there an alternative way to do this?


As requested, here's a small sample:

class Program
{
    public static void Main()
    {
        TestEnum<List<string>, string> col = new List<string>() { "Hello", "World" };
        string someString = col;

        Console.WriteLine(someString);
    }
}

public class TestEnum<TCol, TItem>
    where TCol : class, ICollection<TItem>, new()
{
    TCol _values = default(TCol);

    public TestEnum(IEnumerable<TItem> values)
    {
        _values = (TCol)(new TCol()).AddRange(values);
    }
    public TestEnum(params TItem[] values) : this(values.AsEnumerable()) { }
    public static implicit operator TItem(TestEnum<TCol, TItem> item)
    {
        return item._values.FirstOrDefault();
    }
    public static implicit operator TestEnum<TCol, TItem>(TCol values)
    {
        return new TestEnum<TCol, TItem>(values);
    }
}
public static class EnumerableExtensions
{
    public static IEnumerable<TItem> AddRange<TItem>(this IEnumerable<TItem> e, IEnumerable<TItem> values)
    {
        foreach (var cur in e)
        {
            yield return cur;
        }
        foreach (var cur in values)
        {
            yield return cur;
        }
    }
}

To repro the compile-time exception:

class Program
{
    public static void Main()
    {
        TestEnum<List<string>, string> col = new List<string>() { "Hello", "World" };
        string someString = col;

        Console.WriteLine(someString);
    }
}

public class TestEnum<TCol, TItem>
    where TCol : class, ICollection<TItem>, new()
{
    TCol _values = default(TCol);

    public TestEnum(IEnumerable<TItem> values)
    {
        _values = (TCol)(new TCol()).AddRange(values);
    }
    public TestEnum(params TItem[] values) : this(values.AsEnumerable()) { }
    public static implicit operator TItem(TestEnum<TCol, TItem> item)
    {
        return item._values.FirstOrDefault();
    }
    public static implicit operator TestEnum<TCol, TItem>(TCol values)
    {
        return new TestEnum<TCol, TItem>(values);
    }
}
public static class EnumerableExtensions
{
    public static TCol AddRange<TCol, TItem>(this TCol e, IEnumerable<TItem> values)
        where TCol : IEnumerable<TItem>
    {
        foreach (var cur in e)
        {
            yield return cur;
        }
        foreach (var cur in values)
        {
            yield return cur;
        }
    }
}

Solution

  • I'm not sure what are you trying to accomplish, your method certainly doesn't look like AddRange(), because it doesn't add anything to any collection.

    But if you write an iterator block, it will return an IEnumerable<T> (or IEnumerator<T>). The actual run-time type it returns is compiler generated and there is no way to force it to return some specific collection, like List<T>.

    From your example, AddRange() simply doesn't return List<T>, which is why you can't cast the result to that type. And there is no way to make iterator block return List<T>.

    If you want to create a method that adds something to a collection, it probably means you need to call Add(), not return some other collection from the method:

    public static void AddRange<T>(
        this ICollection<T> collection, IEnumerable<T> items)
    {
        foreach (var item in items)
            collection.Add(item);
    }