Olivier's answer is basically correct but it could use some additional explication.
Why does reflecting the return type for the static factory method give the concrete type, while calling the constructor inline gives the interface? I would expect them to both be the same
Your Program class is equivalent to the following class:
class Program
{
static void Test(ThingCreator creator)
{
Console.WriteLine(creator.Method.ReturnType);
}
static IThing Anon1()
{
return new Foo();
}
static IThing Anon2()
{
return new Bar();
}
static void Main()
{
Test(new ThingCreator(Foo.Create));
Test(new ThingCreator(Bar.Create));
Test(new ThingCreator(Program.Anon1));
Test(new ThingCreator(Program.Anon2));
}
}
And now it should be clear why the program prints what it does.
The moral of the story here is that when we generate the hidden method for the lambdas, those hidden methods return whatever was required by the delegate, and not whatever the lambda returns.
Why is that?
A more germane example would demonstrate that:
static void Blah(Func<object> f)
{
Console.WriteLine(f().ToString());
}
static void Main()
{
Blah( () => 123 );
}
I hope you agree that this must be generated as
static object Anon() { return (object)123; }
and not
static int Anon() { return 123; }
Because the latter cannot be converted to Func<object>
! There is nowhere for the boxing instruction to go, but the call to ToString
expects a reference type to be returned by f()
.
Thus the general rule is that the lambda, when reified as a hidden method, must have the return type granted to it by the conversion to the delegate type.
Also, is there a way to specify in the lambda version that I want the return value to be the concrete type? Or is calling a static method the only way to do that?
Sure.
interface IThing {}
class Foo : IThing {}
delegate T ThingCreator<T>() where T : IThing;
public class Program
{
static void Test<T>(ThingCreator<T> tc) where T : IThing
{
Console.WriteLine(tc.Method.ReturnType);
}
public static void Main()
{
Test(() => new Foo());
}
}
Why is this different?
Because type inference deduces that T
is Foo
and therefore we are converting the lambda to ThingCreator<Foo>
, which has return type Foo
. Therefore the method generated returns Foo
, as the delegate type expects.
BUT WAIT, you say... I cannot change the signature of Test or of ThingCreator
No worries! You can still make this work:
delegate T ThingCreator<T>() where T : IThing;
delegate IThing ThingCreator();
public class Program
{
static void Test(ThingCreator tc)
{
Console.WriteLine(tc.Method.ReturnType);
}
static ThingCreator DoIt<T>(ThingCreator<T> tc) where T : class, IThing
{
return tc.Invoke;
}
public static void Main()
{
Test(DoIt(() => new Foo()));
}
}
The down side is that now every one of your non-generic ThingCreator
delegates is a delegate which calls a ThingCreator<T>
delegate, which is a bit of a waste of time and memory. But you get type inference, method group conversion and lambda conversion to make you a method of the desired return type, and a delegate to that method.
Note the class
constraint. Do you see why that constraint must be there? This is left as an exercise to the reader.
Func<Foo> f = () => new Foo(); Test(f.Invoke);
But the real question is, why do you even check.Method.ReturnType
on a (possibly multi-cast) delegate instance? What is the use for that; why do you care? – BussellTest
. I didn't expect the side effect by passing a lambda instead of a reference to an actual function. Had to dig to find they checked the method's return type. If it were my own code, I'd not create such a thing. – Paraphernalia