Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visual Studio 2015 Intellisense fails to determine types of lambdas in some generic methods

Note: this was a bug in Roslyn that has been fixed in Visual Studio 2017.

Visual Studio 2015 cannot determine the types of lambda parameters in methods such as Enumerable.Join. Consider the following code:

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

When I type book => book., nothing comes up. When I hover over book, Intellisense labels it (parameter) ? book.

What I have tried to fix it

  • devenv.exe /resetuserdata
  • Deleted .suo, .vs, etc., although this happens on every project to everyone on my team
  • Gone through all nine steps in this c-sharpcorner list except a couple that don't seem to apply to Visual Studio 2015.
  • Sent a "frown"

Additional Information

  • The problem occurs equally with Func<> and Expression<Func<>>
  • Intellisense otherwise seems to be working fine (except for the absurd JavaScript processing times, which is another story ...)
  • This problem only occurs in 2015. The example works fine in 2010, 2012, and 2013, although one of my teammates recently started having a very similar issue with 2013 around update 4.
  • I am using Visual Studio Enterprise 2015 Version 14.0.24620.00 Update 1, but the same problem occurred before I installed Update 1.
  • The problem does not occur in all similar cases. For instance, books.Select(book => book. works correctly.
  • If I come back after the statement is written, VS knows it's a Book and gives me the correct options. This leads to an interesting work-around where I can type books.Join(authors, , , ) first, then fill in the blanks and get intellisense again.
  • It seems to have something to do with inference of generic types. See the home-brewed example below. Wrapper<Book>.Combine(authors, book => book.AuthorId, author => author.Id works for book but not for author. The type of book comes from the class's generic argument, but the type of author comes from the method's.
  • Surprisingly, explicitly specifying the types does not always fix the problem.
  • At first I thought the problem was that Join has multiple overrides, but the problem occurs in the home-grown example below with no overrides.

A home-grown example

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);
    }
}
like image 406
JounceCracklePop Avatar asked Jan 31 '16 00:01

JounceCracklePop


1 Answers

I've tried typing out your first example and the same thing happened to me. I type book => book. and get nothing:

enter image description here

A Completed Statement

Why this is happening, I don't know. I can only assume that it's something to do with the statement being incomplete because once the statement is complete, I can go back and delete the .AuthorId and I then get intellisense on the book variable:

enter image description here

A Workaround

It's not a great solution, but you can work around this problem by declaring the type of the variable in the lambda statement. When you do that, you should get intellisense:

enter image description here

like image 105
reduckted Avatar answered Oct 07 '22 17:10

reduckted