Возвращение универсального параметра с возвращаемым доходом - PullRequest
1 голос
/ 25 марта 2012

Надеюсь, это не дурак, не могу найти что-нибудь связанное онлайн

Я получаю странную ошибку времени компиляции в следующем методе расширения:

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: невозможно преобразовать объект типа ' 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 ]

6 голосов
/ 25 марта 2012

Давайте упростим:

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

Кстати, этот метод уже существует; это называется "Конкат".

5 голосов
/ 25 марта 2012

Я не уверен, что вы пытаетесь достичь, ваш метод определенно не похож на 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);
}
1 голос
/ 26 марта 2012

В ответ на ваш комментарий к ответу Эрика Липперта:

Я надеялся на решение, которое будет работать против 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().

...