简体   繁体   中英

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

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

在此输入图像描述

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:

在此输入图像描述

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:

在此输入图像描述

The following is an interpretation of what may be occurring with intellisense when you are trying to write code and relying on the intellisense interpretation of the generic types and it's determination of which class options to offer when you code book => book. or 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);

Here the TKey being passed as a parameter to the query is of type book, matching the TOuter. So the author will fail.

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

It makes sense that this works, as there's one type passed as a parameter to the query books, and then each function within it is passed with it's parameter type and it's corresonding key.

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

Here I am unsure, but it would be unclear what type TKey is being assigned to, book or author.

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

Here I suspect you're overriding the Type with this Wrapper , so it's using author.

For your var test:

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

You are selecting from lists that are null, so they will both fail. Try intialising them, as in your further code.

For real life code:

Then change AuthorId to Author. You need to be able to select from both tables. Personally I find this an easier way to deal with binding, using model views and running queries on the databases.

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.

For creating new objects, I would not being doing this through a linked query like that. The author either exists, or needs to be created and the book can be created either adding the Author Author , or using the int AuthorId .

Also the Book class needs an ID to be a functional database.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM