返回带有yield return的泛型参数

时间:2012-03-24 22:53:15

标签: c# generics ienumerable typecast-operator

希望这不是一个骗局,找不到任何相关的在线

我在以下扩展方法中遇到奇怪的编译时错误:

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

错误:

  

'TestBed.EnumerableExtensions.AddRange(TCol,System.Collections.Generic.IEnumerable)'的主体不能是迭代器块,因为'TCol'不是迭代器接口类型

这是否意味着编译器在确定方法是否符合yield return使用条件时不考虑通用约束?

我在一个使用泛型参数定义集合的类中使用此扩展方法。像(除了几个类型转换操作符)之类的东西:

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

    ...
}

反过来,使用像(记得我定义了类型转换操作符):

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

最初,我的扩展方法看起来像:

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

编译但会导致:

  

未处理的异常:System.InvalidCastException:无法转换类型为'&lt; AddRange&gt; d__6 1[System.String]' to type 'System.Collections.Generic.List 1 [System.String]'的对象。

有没有其他方法可以做到这一点?


根据要求,这是一个小样本:

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

重新编译编译时异常:

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

3 个答案:

答案 0 :(得分:6)

让我们简化:

static T M() where T : IEnumerable<int>
{
    yield return 1;
}

为什么这是非法的?

出于同样的原因,这是非法的:

static List<int> M() 
{
    yield return 1;
}

编译器只知道如何将M转换为返回IEnumerable<something>的方法。它不知道如何将M变成一个返回其他任何东西的方法。

您的泛型类型参数T可以是List<int>,也可以是实现IEnumerable<T>的无限多种其他类型。 C#编译器不知道如何将该方法重写为一个返回它一无所知的类型的方法。

现在,关于你的方法:首先是TCol的功能是什么?为什么不说:

public static IEnumerable<TItem> AddRange<TItem>(
  this IEnumerable<TItem> s1, IEnumerable<TItem> s2)
{
  foreach(TItem item in s1) yield return item;
  foreach(TItem item in s2) yield return item;
}

顺便提一下,这种方法已经存在;它被称为“康卡特”。

答案 1 :(得分:5)

我不确定你想要完成什么,你的方法肯定不像AddRange(),因为它不会向任何集合添加任何内容。

但是如果你编写一个迭代器块,它将返回IEnumerable<T>(或IEnumerator<T>)。它返回的实际运行时类型是编译器生成的,并且无法强制它返回某些特定的集合,例如List<T>

从您的示例中,AddRange()根本不会返回List<T>,这就是您无法将结果强制转换为该类型的原因。并且无法使迭代器块返回List<T>

如果你想创建一个为某个集合添加某个东西的方法,这可能意味着你需要调用Add(),而不是从该方法返回一些其他集合:

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

答案 2 :(得分:1)

回应你对Eric Lippert回答的评论:

  

我希望有一个可以解决IEnumerable的解决方案,但是会用ICollection来解决这个问题。

public static void AddRange<TCol, TItem>(this TCol collection, IEnumerable<TItem> range)
    where TCol : ICollection<TItem>
{
    var list = collection as List<TItem>;
    if (list != null)
    {
        list.AddRange(range);
        return;
    }

    foreach (var item in range)
        collection.Add(item);
}

我将方法定义为void以模仿List<T>.AddRange()的语义。