Ошибка компиляции в одном случае, но не в другом, когда все выражения имеют одинаковые типы

#c# #linq

#c# #linq

Вопрос:

У меня есть фрагменты кода:

Метод заключается в следующем:

 public IEnumerable<c> Test(){
  var collection = new a();
  IEnumerable<c> bs = collection.Where(item => item.Id > 10).Select(item => item);
  return from item in collection
         where item.Id > 10
         select item;
}
  

(первые две строки компилируются нормально)

Он не компилируется и завершается с ошибкой:

Невозможно неявно преобразовать тип System.Collections.Generic.IEnumerable<Stackoverflow.b> в System.Collections.Generic.IEnumerable<Stackoverflow.c>

однако приведенный ниже код компилируется просто отлично:

 return from item in collection
       where item.Id > 10
       select new b(null);
  

поскольку тип выражения в предложении select одинаков, я бы ожидал, что он скомпилируется в обоих случаях или завершится неудачей в обоих.

если я использую синтаксис метода вместо понимания запроса и записи:

 collection.Where(item => item.Id > 10).Select(item => item)
  

это тоже компилируется

мой вопрос: «Что-то не так с юниверсом» (это ошибка компилятора) или я что-то упускаю?

Редактировать

У меня нет using System.Linq , поэтому обычный select и where методы не используются, только те, которые указаны ниже кода, являются частью академического упражнения, чтобы попытаться расширить разрешение перегрузки LINQ на основе шаблонов. Подписи намеренно странные / неожиданные, и где включает проекцию, хотя я считаю, что это, как правило, было бы довольно плохой идеей.

Редактировать

Определения рассматриваемых классов и метод расширения select

 public static class Enumerable {
    public static IEnumerable<c> Select(this IEnumerable<b> self, Func<b,b> selector){
        return null;
    }
}

public class a{
    public IEnumerable<b> Where(Predicate<a> predicate){
        return null;
    }
    public int Id { get; set; }
}

public class b{
}

public class c{
}
  

Комментарии:

1. Какова сигнатура метода, в котором находится этот код? Каков тип переменной, которой вы ее присваиваете?

2. Я понимаю соблазн приправить вещи юмором и ссылками на блоги, которые вам нравятся, но ваш вопрос будет проверен людьми, которые могут ответить на него на основе его названия, а затем в течение многих лет он будет проверяться по названию снова людьми, у которых может быть та же проблема, или совершенно другая.несвязанная проблема. Вы не помогаете никому из этих людей. PS: маловероятно, что вы обнаружили ошибку компилятора, особенно если предположительно ошибочное поведение заключается в выдаче сообщения об ошибке. Большинство ошибок компилятора заключаются в том, что они автоматически выдают неправильный код.

3. Каково определение collection ?

4. @Oded Я опубликовал определения. Я не считаю это важным для более общего вопроса «когда типы всех выражений одинаковы, почему он терпит неудачу в экземпляре, а не в другом», однако 🙂

5. Вы действительно верили, что это может быть ошибка компилятора?

Ответ №1:

Это все о переводе выражений запроса. Возможно, вы захотите взглянуть на спецификацию языка C # 4.0 7.16.2 (или 7.15.2 для C # 3.0) для получения более подробной информации, но вкратце, выражения, такие как:

 
from x in c
where f(x)
select x
  

Переводятся только в

 
c.Where(x => f(x))
  

итак, ваш перечисляемый.Select не будет вызываться, и в результате вы получите IEnumerable<b> вместо IEnumerable<c> . Именно то, на что жалуется компилятор.

Комментарии:

1. раздел, в котором я его нашел, был 7.15.2, хотя

2. да, это было 7.15.2 в C # 3.0. Я отредактировал свой ответ, чтобы упростить поиск этой части спецификации.

3. Интересный выбор, чтобы всегда удалять выбор. Довольно странно, я думаю, поскольку, если где не указано, выбор фактически выполняется (7.15.2.3), что в этом случае приводит к другому типу возвращаемого значения в двух случаях

4. @RuneFS: было бы излишне вызывать identity Select в обычных случаях, тем не менее «Важно, однако, чтобы результат выражения запроса никогда не был самим исходным объектом», вот почему он вызывается, когда нет «where». Я бы сказал, что было бы лучше избегать таких методов выбора, которые у вас есть, и вместо этого использовать отдельный метод расширения, например ConvertToC в дополнение к обычному Select .

5. Я полностью согласен, что с точки зрения кодирования представленный мной код имеет мало смысла. Однако это произошло как часть академического упражнения, намеренно пытающегося написать странный / удивительный linq. Пытаюсь понять, насколько перегружен метод на основе шаблонов. разрешение может быть изменено. Лично мне жаль, что элемент from в x, где предварительно выбранный элемент, не должен совпадать с x.Где(..).Select(..) или где true select — это не то же самое, что просто select . Я считаю, что это нарушает «наименьшее удивление»

Ответ №2:

Как указано в сообщении об ошибке.

Вы не возвращаете то же значение, что и сигнатура метода.

Если вы прочитали сообщение об ошибке, оно означает, что оно ожидает C, но получает B

Комментарии:

1. Затем объясните, почему один и тот же код в синтаксисе метода действительно возвращает тип сигнатуры метода. Метод selct действительно возвращает C. Если вы были правы, то почему …select new b(null) работает?

2. Прочитайте ответ Константина Ознобихина.

3. И я (косвенно) спрашивал, верно ли сообщение об ошибке, тогда в чем разница между выражениями. Что ваш ответ не объясняет

Ответ №3:

Попробуйте

 return from item in collection
    where item.Id > 10
    select (c)item;
  

Похоже, все, что вам не хватает, это приведение из b в c . Возможно, это связано с использованием .Выберите метод расширения компилятор может обработать это приведение автоматически. Я видел, как компилятор использовал правильный универсальный метод, основанный на возвращаемом типе. Поэтому вместо того, чтобы говорить MyItem item = GenericMethod<MyItem>(input) , можно сказать MyItem item = GenericMethod(input) , и компилятор знает, какой тип использовать.

Комментарии:

1. Это изменяет тип выражения выбора и приведет к сбою, поскольку не может найти соответствующий метод выбора.