/// <summary> /// Emit the IL to convert the current value on the stack and set the value of the object. /// </summary> /// <param name="il">The IL generator to output to.</param> /// <param name="sourceType">The current type of the value.</param> /// <param name="mapping">The column mapping to use.</param> /// <remarks> /// Expects the stack to contain: /// Target Object /// Value to set /// The value is first converted to the type required by the method parameter, then sets the property. /// </remarks> /// <returns>A label that needs to be marked at the end of a succesful set.</returns> public static Label EmitConvertAndSetValue(ILGenerator il, Type sourceType, ColumnMappingEventArgs mapping) { var method = mapping.ClassPropInfo; // targetType - the target type we need to convert to // underlyingTargetType - if the target type is nullable, we need to look at the underlying target type // rawTargetType - if the underlying target type is enum, we need to look at the underlying target type for that // sourceType - this is the type of the data in the data set Type targetType = method.MemberType; Type underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType; // some labels that we need Label isDbNullLabel = il.DefineLabel(); Label finishLabel = il.DefineLabel(); // if the value is DbNull, then we continue to the next item il.Emit(OpCodes.Dup); // dup value, stack => [target][value][value] il.Emit(OpCodes.Isinst, typeof(DBNull)); // isinst DBNull:value, stack => [target][value-as-object][DBNull or null] il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // br.true isDBNull, stack => [target][value-as-object] // handle the special target types first if (targetType == typeof(char)) { // char il.EmitCall(OpCodes.Call, _readChar, null); } else if (targetType == typeof(char?)) { // char? il.EmitCall(OpCodes.Call, _readNullableChar, null); } else if (targetType == TypeHelper.LinqBinaryType) { // unbox sql byte arrays to Linq.Binary // before: stack => [target][object-value] // after: stack => [target][byte-array-value] il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][byte-array] // before: stack => [target][byte-array-value] // after: stack => [target][Linq.Binary-value] il.Emit(OpCodes.Newobj, TypeHelper.LinqBinaryCtor); } else if (targetType == typeof(XmlDocument)) { // special handler for XmlDocuments // before: stack => [target][object-value] il.Emit(OpCodes.Call, _readXmlDocument); // after: stack => [target][xmlDocument] } else if (targetType == typeof(XDocument)) { // special handler for XDocuments // before: stack => [target][object-value] il.Emit(OpCodes.Call, _readXDocument); // after: stack => [target][xDocument] } else if (sourceType == typeof(string) && CanDeserialize(mapping.Serializer, targetType)) { // we are getting a string from the database, but the target is not a string, and it's a reference type // assume the column is a serialized data type and that we want to deserialize it // before: stack => [target][object-value] il.Emit(OpCodes.Castclass, typeof(string)); il.EmitLoadType(targetType); // after: stack => [target][object-value][memberType] // determine the serializer to use to convert the string to an object var serializerMethod = mapping.Serializer.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(Type) }, null); if (serializerMethod == null) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Serializer type {0} needs the method 'public static object Deserialize(string, Type)'", mapping.Serializer.Name)); il.Emit(OpCodes.Call, serializerMethod); il.Emit(OpCodes.Unbox_Any, targetType); } else if (underlyingTargetType.IsEnum && sourceType == typeof(string)) { var localString = il.DeclareLocal(typeof(string)); // if we are converting a string to an enum, then parse it. // see if the value from the database is a string. if so, we need to parse it. If not, we will just try to unbox it. il.Emit(OpCodes.Isinst, typeof(string)); // is string, stack => [target][string] il.Emit(OpCodes.Stloc, localString); // pop loc.2 (enum), stack => [target] // call enum.parse (type, value, true) il.EmitLoadType(underlyingTargetType); il.Emit(OpCodes.Ldloc, localString); // push enum, stack => [target][enum-type][string] il.Emit(OpCodes.Ldc_I4_1); // push true, stack => [target][enum-type][string][true] il.EmitCall(OpCodes.Call, _enumParse, null); // call Enum.Parse, stack => [target][enum-as-object] // Enum.Parse returns an object, which we need to unbox to the enum value il.Emit(OpCodes.Unbox_Any, underlyingTargetType); } else if (EmitConstructorConversion(il, sourceType, targetType)) { // target type can be constructed from source type } else { // this isn't a system value type, so unbox to the type the reader is giving us (this is a system type, hopefully) // now we have an unboxed sourceType il.Emit(OpCodes.Unbox_Any, sourceType); if (sourceType != targetType) { // attempt to convert the value to the target type if (!EmitConversionOrCoersion(il, sourceType, targetType)) { if (sourceType != targetType) { throw new InvalidOperationException(String.Format( CultureInfo.InvariantCulture, "Field {0} cannot be converted from {1} to {2}. Create a conversion constructor or conversion operator.", method.Name, sourceType.AssemblyQualifiedName, targetType.AssemblyQualifiedName)); } } // if the target is nullable, then construct the nullable from the data if (Nullable.GetUnderlyingType(targetType) != null) il.Emit(OpCodes.Newobj, targetType.GetConstructor(new[] { underlyingTargetType })); } } ///////////////////////////////////////////////////////////////////// // now the stack has [target][value-unboxed]. we can set the value now method.EmitSetValue(il); // stack is now EMPTY ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// // jump over our DBNull handler il.Emit(OpCodes.Br_S, finishLabel); ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// // cleanup after IsDBNull. ///////////////////////////////////////////////////////////////////// il.MarkLabel(isDbNullLabel); // stack => [target][value] il.Emit(OpCodes.Pop); // pop value, stack => [target] // if the type is an object, set the value to null // this is necessary for overwriting output parameters, // as well as overwriting any properties that may be set in the constructor of the object if (!method.MemberType.IsValueType) { il.Emit(OpCodes.Ldnull); // push null method.EmitSetValue(il); } else { // we didn't call setvalue, so pop the target object off the stack il.Emit(OpCodes.Pop); // pop target, stack => [empty] } return finishLabel; }
/// <summary> /// Emits the IL to convert a value to a target type. /// </summary> /// <param name="il">The IL generator to output to.</param> /// <param name="memberName">The name of the member being converted.</param> /// <param name="sourceType">The source type.</param> /// <param name="targetType">The target type.</param> /// <param name="serializer">The serializer to use to deserialize the value.</param> public static void EmitConvertValue(ILGenerator il, string memberName, Type sourceType, Type targetType, IDbObjectSerializer serializer) { // targetType - the target type we need to convert to // underlyingTargetType - if the target type is nullable, we need to look at the underlying target type // rawTargetType - if the underlying target type is enum, we need to look at the underlying target type for that // sourceType - this is the type of the data in the data set Type underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType; // some labels that we need var finishLabel = il.DefineLabel(); Label isDbNullLabel = il.DefineLabel(); // if the value is DbNull, then we continue to the next item il.Emit(OpCodes.Dup); // dup value, stack => [target][value][value] il.Emit(OpCodes.Isinst, typeof(DBNull)); // isinst DBNull:value, stack => [target][value-as-object][DBNull or null] il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // br.true isDBNull, stack => [target][value-as-object] // handle the special target types first if (targetType == typeof(char)) { // char il.EmitCall(OpCodes.Call, _readChar, null); } else if (targetType == typeof(char?)) { // char? il.EmitCall(OpCodes.Call, _readNullableChar, null); } else if (targetType == TypeHelper.LinqBinaryType) { // unbox sql byte arrays to Linq.Binary // before: stack => [target][object-value] // after: stack => [target][byte-array-value] il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][byte-array] // before: stack => [target][byte-array-value] // after: stack => [target][Linq.Binary-value] il.Emit(OpCodes.Newobj, TypeHelper.LinqBinaryCtor); } else if (targetType == typeof(XmlDocument)) { // special handler for XmlDocuments // before: stack => [target][object-value] il.Emit(OpCodes.Call, _readXmlDocument); // after: stack => [target][xmlDocument] } else if (targetType == typeof(XDocument)) { // special handler for XDocuments // before: stack => [target][object-value] il.Emit(OpCodes.Call, _readXDocument); // after: stack => [target][xDocument] } else if (serializer != null && serializer.CanDeserialize(sourceType, targetType)) { // we are getting a string from the database, but the target is not a string, and it's a reference type // assume the column is a serialized data type and that we want to deserialize it il.EmitLoadType(targetType); StaticFieldStorage.EmitLoad(il, serializer); il.Emit(OpCodes.Call, typeof(TypeConverterGenerator).GetMethod("DeserializeObject", BindingFlags.NonPublic | BindingFlags.Static)); il.Emit(OpCodes.Unbox_Any, targetType); } else if (underlyingTargetType.IsEnum && sourceType == typeof(string)) { var localString = il.DeclareLocal(typeof(string)); // if we are converting a string to an enum, then parse it. // see if the value from the database is a string. if so, we need to parse it. If not, we will just try to unbox it. il.Emit(OpCodes.Isinst, typeof(string)); // is string, stack => [target][string] il.Emit(OpCodes.Stloc, localString); // pop loc.2 (enum), stack => [target] // call enum.parse (type, value, true) il.EmitLoadType(underlyingTargetType); il.Emit(OpCodes.Ldloc, localString); // push enum, stack => [target][enum-type][string] il.Emit(OpCodes.Ldc_I4_1); // push true, stack => [target][enum-type][string][true] il.EmitCall(OpCodes.Call, _enumParse, null); // call Enum.Parse, stack => [target][enum-as-object] // Enum.Parse returns an object, which we need to unbox to the enum value il.Emit(OpCodes.Unbox_Any, underlyingTargetType); } else if (EmitConstructorConversion(il, sourceType, targetType)) { // target type can be constructed from source type } else { // this isn't a system value type, so unbox to the type the reader is giving us (this is a system type, hopefully) // now we have an unboxed sourceType il.Emit(OpCodes.Unbox_Any, sourceType); if (sourceType != targetType) { // attempt to convert the value to the target type if (!EmitConversionOrCoersion(il, sourceType, targetType)) { if (sourceType != targetType) { throw new InvalidOperationException(String.Format( CultureInfo.InvariantCulture, "Field {0} cannot be converted from {1} to {2}. Create a conversion constructor or conversion operator.", memberName, sourceType.AssemblyQualifiedName, targetType.AssemblyQualifiedName)); } } // if the target is nullable, then construct the nullable from the data if (Nullable.GetUnderlyingType(targetType) != null) il.Emit(OpCodes.Newobj, targetType.GetConstructor(new[] { underlyingTargetType })); } } ///////////////////////////////////////////////////////////////////// // convert DBNull to default of the given type il.Emit(OpCodes.Br_S, finishLabel); il.MarkLabel(isDbNullLabel); il.Emit(OpCodes.Pop); TypeHelper.EmitDefaultValue(il, targetType); il.MarkLabel(finishLabel); }