Visual Studio 2015 Intellisense не может определять типы лямбда в некоторых общих методах

Примечание: это была ошибка в Roslyn, исправленная в Visual Studio 2017.

Visual Studio 2015 не может определять типы лямбда-параметров в таких методах, как Enumerable.Join. Рассмотрим следующий код:

public class Book
{
    public int AuthorId { get; set; }
    public string Title { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public static void NoIntellisenseInEnumerableJoin()
{
    IEnumerable<Book> books = null;
    IEnumerable<Author> authors = null;

    //Intellisense fails on both 'book => book.AuthorId' and 'author => author.Id'
    var test = books.Join(authors, book => book.AuthorId, author => author.Id, (book, author) => new { book, author });
}

Когда я набираю book => book., ничего не появляется. Когда я нависаю над book, Intellisense называет его (parameter) ? book.

Что я пытался исправить?

  • devenv.exe /resetuserdata
  • Удаленные .suo,.vs и т.д., хотя это происходит в каждом проекте всем в моей команде.
  • Прошли все девять шагов в этом списке c-sharpcorner, за исключением пары, которая, похоже, не применяется к Visual Studio 2015.
  • Отправлено "нахмурившись"

Дополнительная информация

  • Проблема возникает в равной степени с Func<> и Expression<Func<>>
  • Intellisense, похоже, работает нормально (за исключением абсурдного времени обработки JavaScript, что является другой историей...)
  • Эта проблема возникает только в 2015 году. Пример хорошо работает в 2010, 2012 и 2013 годах, хотя один из моих товарищей по команде недавно начал иметь очень похожую проблему с 2013 годом вокруг обновления 4.
  • Я использую Visual Studio Enterprise 2015 Version 14.0.24620.00 Update 1, но та же проблема возникла до того, как я установил обновление 1.
  • Проблема не возникает во всех подобных случаях. Например, books.Select(book => book. работает правильно.
  • Если я вернусь после написания инструкции, VS знает, что это book и дает мне правильные варианты. Это приводит к интересной работе, когда я могу напечатать books.Join(authors, , , ), а затем заполнить пробелы и снова получить intellisense.
  • Кажется, что это связано с выводом типичных типов. См. Приведенный ниже пример дома. Wrapper<Book>.Combine(authors, book => book.AuthorId, author => author.Id работает для book, но не для author. Тип book исходит из общего аргумента класса, но тип author исходит из метода.
  • Удивительно, но явное указание типов не всегда устраняет проблему.
  • Сначала я думал, что проблема заключается в том, что Join имеет несколько переопределений, но проблема возникает в приведенном ниже примере ниже без переопределений.

Пример, выращенный в домашних условиях

public class Wrapper<TInner>
{
    public void Combine<TOuter, TKey>(Wrapper<TOuter> outer, Func<TInner, TKey> innerKey, Func<TOuter, TKey> outerKey)
    { }

    public void ThisWorks<TOuter>(Wrapper<TOuter> outer, Func<TInner, int> innerKey, Func<TOuter, int> outerKey)
    { }
}

public static class WrapperExtensions
{
    public static void CombineExt<TInner, TOuter, TKey>(this Wrapper<TInner> inner, Wrapper<TOuter> outer,
        Func<TInner, TKey> innerKey, Func<TOuter, TKey> outerKey)
    { }

    public static void ThisAlmostWorks<TInner, TOuter>(this Wrapper<TInner> inner, Wrapper<TOuter> outer,
        Func<TInner, int> innerKey, Func<TOuter, int> outerKey)
    { }
}

public static class NoIntellisenseExamples
{
    public static void NoIntellisenseInSimplerCase()
    {
        var books = new Wrapper<Book>();
        var authors = new Wrapper<Author>();

        //Intellisense fails on 'author => author.Id' but works for the book lambda.
        books.Combine(authors, book => book.AuthorId, author => author.Id);

        new Wrapper<Book>().Combine<Author, int>(authors, book => book.AuthorId, author => author.Id);

        //Intellisense fails on both 'book => book.AuthorId' and 'author => author.Id' in both of the following:
        books.CombineExt(authors, book => book.AuthorId, author => author.Id);
        WrapperExtensions.CombineExt(books, authors, book => book.AuthorId, author => author.Id);

        //Intellisense works perfectly here.
        books.ThisWorks(authors, book => book.AuthorId, author => author.Id);

        //Intellisense fails on 'book => book.AuthorId' but works for 'author => author.Id'
        books.ThisAlmostWorks(authors, book => book.AuthorId, author => author.Id);
    }
}

Ответ 1

Я попытался напечатать ваш первый пример, и то же самое случилось со мной. Я набираю book => book. и ничего не получаю:

введите описание изображения здесь

Завершенное утверждение

Почему это происходит, я не знаю. Я могу только предположить, что это связано с тем, что оператор неполный, поскольку после завершения инструкции я могу вернуться и удалить .AuthorId, а затем получить intellisense в переменной book:

введите описание изображения здесь

Обходной путь

Это не отличное решение, но вы можете обойти эту проблему, объявив тип переменной в выражении лямбда. Когда вы это сделаете, вы должны получить intellisense:

введите описание изображения здесь

Ответ 2

Ниже приведена интерпретация того, что может произойти с intellisense, когда вы пытаетесь написать код и полагаетесь на интерпретацию intellisense родовых типов и определяете, какие варианты класса предлагать при кодировании book => book. или author => author..

1.

public void Combine<TOuter, TKey>(Wrapper<TOuter> outer, Func<TInner, TKey> innerKey, Func<TOuter, TKey> outerKey) {}

//Intellisense fails on 'author => author.Id' but works for the book lambda.
books.Combine(authors, book => book.AuthorId, author => author.Id);

Здесь TKey, передаваемый в качестве параметра запроса, относится к типу книги, соответствующей TOuter. Таким образом, автор потерпит неудачу.

2.

public void ThisWorks<TOuter>(Wrapper<TOuter> outer, Func<TInner, int> innerKey, Func<TOuter, int> outerKey){}

//Intellisense works perfectly here.
books.ThisWorks(authors, book => book.AuthorId, author => author.Id);

Имеет смысл, что это работает, поскольку один тип передается в качестве параметра в книги запросов, а затем каждая функция внутри него передается с его типом параметра и соответствующим ключом.

3.

public static void CombineExt<TInner, TOuter, TKey>(this Wrapper<TInner> inner, Wrapper<TOuter> outer,
    Func<TInner, TKey> innerKey, Func<TOuter, TKey> outerKey) {}

//Intellisense fails on both 'book => book.AuthorId' and 'author => author.Id' in both of the following:
books.CombineExt(authors, book => book.AuthorId, author => author.Id);
WrapperExtensions.CombineExt(books, authors, book => book.AuthorId, author => author.Id);

Здесь я не уверен, но было бы непонятно, какой тип TKey присваивается, книга или автор.

4.

public static void ThisAlmostWorks<TInner, TOuter>(this Wrapper<TInner> inner, Wrapper<TOuter> outer,
    Func<TInner, int> innerKey, Func<TOuter, int> outerKey) { }

//Intellisense fails on 'book => book.AuthorId' but works for 'author => author.Id'
books.ThisAlmostWorks(authors, book => book.AuthorId, author => author.Id);

Здесь я подозреваю, что вы переопределяете Type с помощью this Wrapper, поэтому он использует автора.

Для вашего теста var:

IEnumerable<Book> books = null;  
IEnumerable<Author> authors = null;

Вы выбираете из списков, которые являются нулевыми, поэтому они оба потерпят неудачу. Попробуйте их инициализировать, как в вашем следующем коде.

Код реальной жизни:

Затем измените AuthorId на Author. Вы должны иметь возможность выбирать из обеих таблиц. Лично я считаю, что это более простой способ справиться с привязкой, используя представления модели и выполнение запросов в базах данных.

public class Book
{
    public Author Author { get; set; }
    public string Title { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public static void NoIntellisenseInEnumerableJoin()
{
    IEnumerable<Book> books = null;
    IEnumerable<Author> authors = null;


var books_  = from book in books
                       select book;

var books = books.Include(book => book.Author);

books_ = books_.Where(book => book.Author.Id, // to do.

Для создания новых объектов я бы не делал этого с помощью связанного запроса. Автор либо существует, либо должен быть создан, и книга может быть создана либо добавлением Author Author, либо использованием int AuthorId.

Также для класса Book необходимо, чтобы ID был функциональной базой данных.