Because types are inferred from the context, i.e. the compiler sees thathe parameters you gave unambiguously define the generic type parameters.
You can witness a simpler version of this easily :
public static void TestGeneric<T>(T myParam) { }
public static void Example()
{
string testString = null;
int testInt = 0;
TestGeneric(testString);
TestGeneric(testInt);
}
Hover over both "TestGeneric" calls in Visual Studio : the first signature is TestGeneric<string>(string)
, the second is TestGeneric<int>(int)
.
Note that the types are inferred on compilation (and thus, the generics version are still strongly and precisely typed)
There are many subtleties and limitations of this feature of the C# language. I don't have right now in memory all the details (see further reading section below).
For instance, if you play with different subclasses, the covariance and contravariance of the type parameters, etc... In some cases, you may encounter ambiguousness. The compiler will complain, and in this case, you'll still need to explicitly provide the type parameter(s).
My guess on C# mechanics in your case :
(not completely certain, feel free to comment !)
_context.Users.Include( u => u.PropertyFromAnotherTable)
.Where( u => u.Age > 25 )
.ToListAsync( cancellationToken );
First the TEntity
type for Include<TEntity, TProperty>
can easily be inferred because of the first argument of the extension method :
this IQueryable<TEntity> source,
since you call it on _context.Users
, which is I presume something like DbSet<User>
then it can be implicitly cast to IQueryable<User>
, so TEntity
could be User
.
Then, it follows that u => u.PropertyFromAnotherTable
should be of type Expression<Func<User, TProperty>>
. So u
is of type User, and since it has a unique property named PropertyFromAnotherTable
, C# can then infer the type of TProperty
as well.
Further reading :
Some more info here, to cite only one blog among many : http://joelabrahamsson.com/a-neat-little-type-inference-trick-with-c/
It mentions "C# In Depth" book from Jon Skeet, which I recommend if you're interested in the details I mentioned earlier, and in the evolution of the language.
Also, some interesting answers and links to blog from Eric Lippert in this Q&A are also worth reading for the mechanices : Why doesn't C# infer my generic types?