Lambdas are very unusual in C# in that they're one of very few types of expressions that don't actually have a type. They rely on their context in order to know what their type is. (Another example being null
.)
You can take the exact same lambda, and change the code surrounding it, and it will change the type that that lambda resolves to. If you put it somewhere expecting an Expression<T>
, it'll resolve to an expression, if you put it somewhere expecting a delegate, it'll end up being a delegate (with the two being radically different from each other), and the type of delegate it's bound to is based on the type of delegate expected, so the same lambda can resolve to entirely different types of delegates based on what's expected.
Almost nothing else acts like this; most expressions have exactly one type that they resolve to, and you can determine what that type is without looking at the context at all.
So in your first example, when you have:
(ctx, next) => {
Console.WriteLine("Test");
return next();
}
You can't determine the type alone. You need to look at where that expression is used to try to find something that that lambda can be converted to. That lambda can't be converted to an object
. The compiler would have no idea if it's supposed to be an expression or a delegate, or which delegate it's supposed to be. But it can convert it to an Func<IOwinContext, Func<Task>, Task>
, because it's a delegate of an appropriate type. So that's the overload used.
When you pass in handler
, that's not a lambda, it's just an instance of a delegate, and that, like any other object, can be converted to object
, so the other overload is valid, and being an instance method it "wins" when both are valid.