Dapper: What's the difference between these two pieces of code?

c# dapper

Question

I've registered a custom type handler for Dapper to Json serialize a string. This works as expected. However, whilst coding, I discovered two pieces of code after refactoring, the latter one which didn't trigger the datatype handler, but should in my opinion, so what's the difference?

First the code that works as expected - the custom type handler is called by dapper, and the data is serialized when inserted into the table:

if (val.GetType() != typeof (String))
{
    var v = new JsonString {Value = val};
    this.Connection.Execute("insert into misc (k,v) values (@keyName, @v)", 
        new { keyName, v });
}
else
{
    this.Connection.Execute("insert into misc (k,v) values (@keyName, @val)", 
        new { keyName, val });
}

Now for the code which doesn't work as it inserts into the table the fully qualified type string instead of the serialized data, but which I think is semantically similar:

var v = val.GetType() != typeof (String)
        ? new JsonString {Value = val}
        : val;

// t is set to type of JsonString, therefore the dapper type handler should be used
var t = v.GetType();

this.Connection.Execute("insert into misc (k,v) values (@keyName, @v)", 
    new { keyName, v });

Is it a dapper bug or a c# oddity? I assume it's something to do with the auto typing in the ternary conditional which is the failing point, but t is set to the data type that is serialized by Dapper (or rather the custom type handler). What's the difference?

Accepted Answer

I am going to assume that the compile-time type (i.e. declaration type) of val is System.Object. I will explain why the two situations are not equivalent.

One must be careful to distinguish between the compile-time type of a variable and the actual run-time type (which is found by .GetType()).

In the first piece of code, in the branch where val is not String at run-time, we declare:

var v = new JsonString {Value = val};

Here var is substituted by JsonString since that is the compile-time type of the right-hand side of the = assignment. Then the anonymous type instance:

new { keyName, v }

is going to be a class (I will call it class Anonymous1) with a member

public JsonString v { get { ... } }

Now, in the second piece of code, we have instead:

var v = val.GetType() != typeof (String)
        ? new JsonString {Value = val}
        : val;

The compile-time types of the operands to the ternary operator are:

{bool} ? {JsonString} : {object}

At compile-time, a best common type for JsonString and object must be found. That common type is object. So object becomes the compile-time type of v here, i.e. var means object here.

Then the anonymous type:

new { keyName, v }

is a type "Anonumous2" whose "2nd" property reads:

public object v { get { ... } }

So to sum up: In the first case you pass in an object which has a property v declared as a JsonString which when retrieved returns a JsonSting that happens to have run-time JsonString. In the second code sample you pass in an object which has a property v declared as object which when retrieved returns an object that happens to have run-time type JsonString.

I do not know much on how Dapper works! But presumably when it sees (by reflection?) that the property type is object, it simply calls .ToString() on it. If that object happens to be of run-time type string, that should be OK, since string.ToString() is overridden. But JsonString.ToString() is not like that.

When Dapper sees the property is declared as JsonString, Dapper does something smarter than calling .ToString().


Popular Answer

Assuming there is no implicit conversion available between JsonString and String, the code in the second example won't work. If there is an implicit conversion available, you need to provide more information about the exception that is occurring.

WIth no conversion available between JsonString, a variable declared with type var has a type that is inferred by the compiler ((https://msdn.microsoft.com/en-us/library/bb384061.aspx). In this case, variable v is either of type JsonString or String depending upon which part of the assignment occurs. If the compiler assumes it is of type JsonString, any assignment with a String will fail.

In your second code example, the first line of code is wrong since the assignment results in two different data types getting assign to variable v.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why