在查看EF Core的源代码时,我看到一个声明如下的Include函数:
public static IIncludableQueryable<TEntity, TProperty> Include<TEntity, TProperty>(
[NotNull] this IQueryable<TEntity> source,
[NotNull] Expression<Func<TEntity, TProperty>> navigationPropertyPath)
where TEntity : class
{ }
另外一个像这样的人:
public static IQueryable<TEntity> Include<TEntity>(
[NotNull] this IQueryable<TEntity> source,
[NotNull, NotParameterized] string navigationPropertyPath)
where TEntity : class
{ )
但是,当实际调用这些函数时,我们可以不指定类型而直接调用它们,为什么?
_context.Users.Include( u => u.PropertyFromAnotherTable)
.Where( u => u.Age > 25 )
.ToListAsync( cancellationToken );
答案 0 :(得分:3)
因为类型是从上下文中推断的,也就是说,编译器发现您给的参数明确地定义了泛型类型参数。
您可以轻松地看到一个简单的版本:
public static void TestGeneric<T>(T myParam) { }
public static void Example()
{
string testString = null;
int testInt = 0;
TestGeneric(testString);
TestGeneric(testInt);
}
将鼠标悬停在Visual Studio中的两个“ TestGeneric”调用上:第一个签名为TestGeneric<string>(string)
,第二个签名为TestGeneric<int>(int)
。
请注意,类型是在编译时推断出来的(因此,泛型版本仍然是严格且精确的类型)
C#语言的此功能有很多细微之处和局限性。我现在没有所有详细信息在内存中(请参阅下面的进一步阅读部分)。
例如,如果您使用不同的子类,则类型参数的协方差和协方差等。在某些情况下,您可能会遇到歧义。编译器会抱怨,在这种情况下,您仍然需要显式提供类型参数。
(不确定,请随时发表评论!)
_context.Users.Include( u => u.PropertyFromAnotherTable)
.Where( u => u.Age > 25 )
.ToListAsync( cancellationToken );
首先,由于扩展方法的第一个参数,可以很容易地推断出TEntity
的{{1}}类型:
Include<TEntity, TProperty>
由于您在this IQueryable<TEntity> source,
上调用它,因此我假设它类似于_context.Users
,因此可以将其隐式转换为DbSet<User>
,因此IQueryable<User>
可以是{{1} }。
然后,得出TEntity
的类型应为User
。因此u => u.PropertyFromAnotherTable
属于User类型,并且由于它具有名为Expression<Func<User, TProperty>>
的唯一属性,因此C#也可以推断u
的类型。
这里有更多信息,在众多博客中仅引用了一个博客:http://joelabrahamsson.com/a-neat-little-type-inference-trick-with-c/
它提到了乔恩·斯凯特(Jon Skeet)的“ C# In Depth”这本书,如果您对我之前提到的细节以及语言的发展感兴趣,我建议您这样做。
此外,在此问答中,一些有趣的答案和Eric Lippert的博客链接也值得一读,以供参考:Why doesn't C# infer my generic types?