2012-03-24 3 views
1

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

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

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

Ошибка:

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

Означает ли это, что общие ограничения, не рассматриваются компилятором при определении, если метод подходит для использования 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) 
{ 
    ... 
} 

Что компилирует, но результаты в:

Unhandled Exception: System.InvalidCastException: Unable to cast object of type '<AddRange>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

Обновлен с примерами. (Обновляется снова, чтобы сделать его короче). –

+1

Вы упомянули ошибку * compile-time *, но ваш пример компилируется. Он не работает с * исключением * во время выполнения - это то, что вы имели в виду, или ваш код просто не показывает то, что вы изначально видели? –

ответ

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); 
} 
+0

Итак, что случилось с моей второй попыткой (первый фрагмент)? –

+0

Как я уже сказал, блок итератора может возвращать только 'IEnumerable ', вы не можете заставить его возвращать некоторый случайный тип коллекции, который вы пытаетесь сделать. – svick

+0

Это имеет смысл (и намекнул на мой вопрос). Что сбивает с толку, так это то, что 'IEnumerable enumStr = new List () {" Hello "} .Concat (новый список () {" World "});' работает, хотя он все равно должен возвращать один и тот же пользовательский тип итератора, так как почему будет ли код в моем втором фрагменте причиной исключения? –

6

Давайте упрощать:

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

Почему это незаконно?

По той же причине, что это является незаконным:

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

Компилятор только знает, как превратить М в метод, который возвращает 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; 
} 

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

+0

Согласовано. Сначала я попробовал «IEnumerable.Concat», но в момент компиляции он привел в основном к тем же ошибкам, поэтому я применил собственную версию на основе [версии Jared Par] (http://stackoverflow.com/a/1210311/635634) для добавления к экземплярам 'IEnumerable' (у меня уже было это в моем проекте, поэтому я просто клонировал функцию). Это привело к тому, что я пытался использовать дженерики для абстрагирования типов в надежде на то, что это сработает, но не повезло (в результате возникает этот вопрос). Я надеялся на решение, которое будет работать против 'IEnumerable ', но будет соглашаться с одним для 'ICollection '. –

1

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

I hoped for a solution that would work against IEnumerable but will settle with one for 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().

 Смежные вопросы

  • Нет связанных вопросов^_^