{// no IL level conversions to/from decimal; I guess we could use the static operators, but
// this feels an edge-case
handled=false;
}
else
{
switch(unboxTypeCode)
{
caseTypeCode.Byte:
opCode=OpCodes.Conv_Ovf_I1_Un;break;
caseTypeCode.SByte:
opCode=OpCodes.Conv_Ovf_I1;break;
caseTypeCode.UInt16:
opCode=OpCodes.Conv_Ovf_I2_Un;break;
caseTypeCode.Int16:
opCode=OpCodes.Conv_Ovf_I2;break;
caseTypeCode.UInt32:
opCode=OpCodes.Conv_Ovf_I4_Un;break;
caseTypeCode.Boolean:// boolean is basically an int, at least at this level
caseTypeCode.Int32:
opCode=OpCodes.Conv_Ovf_I4;break;
caseTypeCode.UInt64:
opCode=OpCodes.Conv_Ovf_I8_Un;break;
caseTypeCode.Int64:
opCode=OpCodes.Conv_Ovf_I8;break;
caseTypeCode.Single:
opCode=OpCodes.Conv_R4;break;
caseTypeCode.Double:
opCode=OpCodes.Conv_R8;break;
default:
handled=false;
break;
}
}
if(handled)
{// unbox as the data-type, then use IL-level convert
il.Emit(OpCodes.Unbox_Any,dataType);// stack is now [target][target][data-typed-value]
il.Emit(opCode);// stack is now [target][target][typed-value]
if(unboxTypeCode==TypeCode.Boolean)
{// compare to zero; I checked "csc" - this is the trick it uses; nice
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
}
}
else
{// use flexible conversion
il.Emit(OpCodes.Ldtoken,nullUnderlyingType??unboxType);// stack is now [target][target][value][member-type-token]
il.EmitCall(OpCodes.Call,typeof(Type).GetMethod("GetTypeFromHandle"),null);// stack is now [target][target][value][member-type]
il.EmitCall(OpCodes.Call,typeof(Convert).GetMethod("ChangeType",newType[]{typeof(object),typeof(Type)}),null);// stack is now [target][target][boxed-member-type-value]
il.Emit(OpCodes.Unbox_Any,nullUnderlyingType??unboxType);// stack is now [target][target][typed-value]
}
}
if(nullUnderlyingType!=null)
if(nullUnderlyingType!=null)
{
{
il.Emit(OpCodes.Newobj,unboxType.GetConstructor(new[]{nullUnderlyingType}));// stack is now [target][target][typed-value]
il.Emit(OpCodes.Newobj,unboxType.GetConstructor(new[]{nullUnderlyingType}));// stack is now [target][target][typed-value]
il.Emit(OpCodes.Unbox_Any,to);// stack is now [target][target][typed-value]
}
elseif((op=GetOperator(from,to))!=null)
{
// this is handy for things like decimal <===> double
il.Emit(OpCodes.Unbox_Any,from);// stack is now [target][target][data-typed-value]
il.Emit(OpCodes.Call,op);// stack is now [target][target][typed-value]
}
else
{
boolhandled=false;
OpCodeopCode=default(OpCode);
switch(Type.GetTypeCode(from))
{
caseTypeCode.Boolean:
caseTypeCode.Byte:
caseTypeCode.SByte:
caseTypeCode.Int16:
caseTypeCode.UInt16:
caseTypeCode.Int32:
caseTypeCode.UInt32:
caseTypeCode.Int64:
caseTypeCode.UInt64:
caseTypeCode.Single:
caseTypeCode.Double:
handled=true;
switch(Type.GetTypeCode(via??to))
{
caseTypeCode.Byte:
opCode=OpCodes.Conv_Ovf_I1_Un;break;
caseTypeCode.SByte:
opCode=OpCodes.Conv_Ovf_I1;break;
caseTypeCode.UInt16:
opCode=OpCodes.Conv_Ovf_I2_Un;break;
caseTypeCode.Int16:
opCode=OpCodes.Conv_Ovf_I2;break;
caseTypeCode.UInt32:
opCode=OpCodes.Conv_Ovf_I4_Un;break;
caseTypeCode.Boolean:// boolean is basically an int, at least at this level
caseTypeCode.Int32:
opCode=OpCodes.Conv_Ovf_I4;break;
caseTypeCode.UInt64:
opCode=OpCodes.Conv_Ovf_I8_Un;break;
caseTypeCode.Int64:
opCode=OpCodes.Conv_Ovf_I8;break;
caseTypeCode.Single:
opCode=OpCodes.Conv_R4;break;
caseTypeCode.Double:
opCode=OpCodes.Conv_R8;break;
default:
handled=false;
break;
}
break;
}
if(handled)
{
il.Emit(OpCodes.Unbox_Any,from);// stack is now [target][target][col-typed-value]
il.Emit(opCode);// stack is now [target][target][typed-value]
if(to==typeof(bool))
{// compare to zero; I checked "csc" - this is the trick it uses; nice
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
}
}
else
{
il.Emit(OpCodes.Ldtoken,via??to);// stack is now [target][target][value][member-type-token]
il.EmitCall(OpCodes.Call,typeof(Type).GetMethod("GetTypeFromHandle"),null);// stack is now [target][target][value][member-type]
il.EmitCall(OpCodes.Call,typeof(Convert).GetMethod("ChangeType",newType[]{typeof(object),typeof(Type)}),null);// stack is now [target][target][boxed-member-type-value]
il.Emit(OpCodes.Unbox_Any,to);// stack is now [target][target][typed-value]
}
}
}
staticMethodInfoGetOperator(Typefrom,Typeto)
staticMethodInfoGetOperator(Typefrom,Typeto)
{
{
if(to==null)returnnull;
if(to==null)returnnull;
...
@@ -3548,7 +3758,7 @@ public IEnumerable<T> Read<T>(bool buffered = true)
...
@@ -3548,7 +3758,7 @@ public IEnumerable<T> Read<T>(bool buffered = true)
if(reader==null)thrownewObjectDisposedException(GetType().FullName,"The reader has been disposed; this can happen after all data has been consumed");
if(reader==null)thrownewObjectDisposedException(GetType().FullName,"The reader has been disposed; this can happen after all data has been consumed");
if(consumed)thrownewInvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
if(consumed)thrownewInvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
@@ -17,12 +17,14 @@ public void TestBasicStringUsage()
...
@@ -17,12 +17,14 @@ public void TestBasicStringUsage()
}
}
publicvoidTestClassWithStringUsage()
publicvoidTestClassWithStringUsage()
{
{
vararr=connection.Query<BasicType>("select 'abc' as [Value] union all select @txt",new{txt="def"}).ToArray();
vararr=connection.Query<BasicType>("select 'abc' as [Value], '123' as [Another_Value] union all select @txt, @txt2",new{txt="def",txt2="456"}).ToArray();
varresult=connection.Query<LocalDateResult>("SELECT @NotNullable AS NotNullable, @NullableNotNull AS NullableNotNull, @NullableIsNull AS NullableIsNull",param).Single();
result=connection.Query<LocalDateResult>("SELECT @NotNullable AS NotNullable, @NullableNotNull AS NullableNotNull, @NullableIsNull AS NullableIsNull",param).Single();