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 был функциональной базой данных.