I am attempting to build a Dictionary
that looks like this:
var getValueFuncs = new Dictionary<TypeID, Func<string, object>>
{
{TypeID.BINARY, NotMine.GetBinaryValue},
{TypeID.BOOLEAN, NotMine.GetBooleanValue},
{TypeID.DATE, NotMine.GetDateTimeValue},
{TypeID.DOUBLE, NotMine.GetFloat64Value},
{TypeID.LONG, NotMine.GetInteger32Value},
{TypeID.STRING, NotMine.GetStringValue}
};
The various Func
s that I want to put into it all have different return types.
public interface INotMine : ICollection, ICloneable
{
byte[] GetBinaryValue(string propName);
bool? GetBooleanValue(string propName);
DateTime? GetDateTimeValue(string propName);
double? GetFloat64Value(string propName);
int? GetInteger32Value(string propName);
string GetStringValue(string propName);
}
Note that NotMine
is a separate class, that it inherits from INotMine
, and that I cannot change it.
The two lines to initialize the Dictionary
with the procedures that return byte[]
and string
compile with no trouble. The four lines for procedures that return nullable data types (i.e. bool?
, DateTime?
, double?
, and int?
) will not compile. They each report an error like this one:
bool? INotMine.GetBooleanValue(string propName)
'bool? INotMine.GetBooleanValue(string)' has the wrong return type
Expected a method with 'object GetBooleanValue(string)' signature
Given that everything inherits from object
, why will those lines not compile and go into my Dictionary
nicely?
EDIT: Before he deleted it, someone posted an answer that contained an elegant work-around that looked like this:
{TypeID.BINARY, s => NotMine.GetBinaryValue(s)}
I'm grateful for that, and I'll use that, but I'd still like to know why the original lines won't compile.
Consider the following:
Func<Animal, Turtle> fat = whatever;
Func<Mammal, Reptile> fmr = fat;
This is legal. fat
can take any Animal, and the caller of fmr
promises to pass a Mammal
, which is always an Animal
. Similarly fmr
requires that the function return a Reptile
, and fat
always returns a Turtle
, which is a Reptile
.
This kind of conversion is called a variant conversion. Specifically, the "I require any Animal so I'll accept Mammal" is called a contravariant conversion, and the "I return a Turtle so I meet your need for a Reptile" is a covariant conversion.
C# supports covariant and contravariant conversions on generic types only under these circumstances:
You meet the first two conditions -- your Func<string, object>
is a delegate type known to be safe to be contravariant in the argument and covariant in the return. However you do not meet the third condition. bool?
DateTime?
, double?
and int?
are not reference types. Therefore, variant conversion rules do not apply, and the conversions are illegal.
That would cover converting from Func<string, bool?>
to Func<string, object>
. But that's not what you're doing; you are converting from the method group containing a function that returns bool?
. Does that matter?
No. The rules are the same for method group conversions. A covariant conversion from a method group containing a method that returns bool?
to a Func<string, object>
is not legal because bool?
is still required to be a reference type to make that conversion legal.
No matter how you slice it, you are stuck. You cannot convert a method or delegate that returns bool?
directly to a delegate that returns object
.
The reason for this is summed up nicely in Jeroen's comment on the question. Where does the boxing conversion go? A boxing conversion is code that allocates heap memory and therefore that code has to go somewhere, but there is nowhere for the compiler to put it and still maintain reference identity on the delegate.
As you correctly note, if you provide your own lambda then the compiler has a place to put the boxing conversion; it sticks it on top of the return from the lambda. The price you pay for this is that the lambda is now a delegate that refers to the lambda body, and not directly to GetBooleanValue
, so there is a tiny indirection penalty there.