Example #1
0
        /// <summary>
        /// Creates a deserializer for a graph of objects.
        /// </summary>
        /// <param name="subTypes">The types of the subobjects.</param>
        /// <param name="reader">The reader to analyze.</param>
        /// <param name="structure">The structure of the record we are reading.</param>
        /// <param name="allowBindChild">True if the columns should be allowed to bind to children.</param>
        /// <returns>A function that takes an IDataReader and deserializes an object of type T.</returns>
        private static Delegate CreateGraphDeserializer(Type[] subTypes, IDataReader reader, IRecordStructure structure, bool allowBindChild)
        {
            Type type     = subTypes[0];
            bool isStruct = type.GetTypeInfo().IsValueType;

            // go through each of the subtypes
            var deserializers = CreateDeserializersForSubObjects(subTypes, reader, structure, allowBindChild);

            // create a new anonymous method that takes an IDataReader and returns T
            var dm = new DynamicMethod(
                String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()),
                type,
                new[] { typeof(IDataReader) },
                true);
            var il          = dm.GetILGenerator();
            var localObject = il.DeclareLocal(type);

            // keep track of the properties that we have already used
            // the tuple is the level + property
            var usedMethods = new HashSet <Tuple <int, ClassPropInfo> >();

            ///////////////////////////////////////////////////
            // emit the method
            ///////////////////////////////////////////////////
            for (int i = 0; i < deserializers.Length; i++)
            {
                // if there is no deserializer for this object, then skip it
                if (deserializers[i] == null)
                {
                    continue;
                }

                // for subobjects, dup the core object so we can set values on it
                if (i > 0)
                {
                    if (isStruct)
                    {
                        il.Emit(OpCodes.Ldloca_S, localObject);
                    }
                    else
                    {
                        il.Emit(OpCodes.Ldloc, localObject);
                    }
                }

                // if we don't have a callback, then we are going to store the value directly into the field on T or one of the subobjects
                // here we determine the proper set method to store into.
                // we are going to look into all of the types in the graph and find the first parameter that matches our current type
                ClassPropInfo setMethod = null;
                for (int parent = 0; parent < i; parent++)
                {
                    // find the set method on the current parent
                    setMethod = GetFirstMatchingMethod(ClassPropInfo.GetMembersForType(subTypes[parent]).Where(m => m.CanSetMember), subTypes[i]);

                    // make sure that at a given level, we only use the method once
                    var tuple = Tuple.Create(parent, setMethod);
                    if (usedMethods.Contains(tuple))
                    {
                        continue;
                    }
                    else
                    {
                        usedMethods.Add(tuple);
                    }

                    // if we didn't find a matching set method, then continue on to the next type in the graph
                    if (setMethod == null)
                    {
                        continue;
                    }

                    // if the parent is not the root object, we have to drill down to the parent, then set the value
                    // the root object is already on the stack, so emit a get method to get the object to drill down into
                    for (int p = 0; p < parent; p++)
                    {
                        var getMethod = GetFirstMatchingMethod(ClassPropInfo.GetMembersForType(subTypes[p]).Where(m => m.CanGetMember && m.CanSetMember), subTypes[p + 1]);
                        if (getMethod == null)
                        {
                            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "In order to deserialize sub-objects, {0} must have a get/set method for type {1}", subTypes[p].FullName, subTypes[p + 1].FullName));
                        }
                        getMethod.EmitGetValue(il);
                    }

                    break;
                }

                // call the deserializer for the subobject
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, deserializers[i]);

                // other than the root object, set the value on the parent object
                if (i == 0)
                {
                    // store root object in loc.0
                    il.Emit(OpCodes.Stloc, localObject);
                }
                else
                {
                    if (setMethod == null)
                    {
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot find set method for type {0} into type {1}", subTypes[i].FullName, subTypes[0].FullName));
                    }

                    setMethod.EmitSetValue(il);
                }
            }

            // return the object from loc.0
            il.Emit(OpCodes.Ldloc, localObject);
            il.Emit(OpCodes.Ret);

            // convert the dynamic method to a delegate
            var delegateType = typeof(Func <,>).MakeGenericType(typeof(IDataReader), type);

            return(dm.CreateDelegate(delegateType));
        }
		/// <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="method">The set property method to call.</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, ClassPropInfo method)
		{
			// 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 == typeof(System.Data.Linq.Binary))
			{
				// 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, _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) && targetType != typeof(string) && !targetType.IsValueType)
			{
				// we are getting a string from the database, but the target is not a string, but it's a reference type
				// assume the column is an xml data type and that we want to deserialize it

				// before: stack => [target][object-value]
				il.EmitLoadType(targetType);

				// after: stack => [target][object-value][memberType]
				il.Emit(OpCodes.Call, _deserializeXml);
				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
			{
				// 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);

				// look for a constructor that takes the type as a parameter
				Type[] sourceTypes = new Type[] { sourceType };
				ConstructorInfo ci = targetType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, sourceTypes, null);
				if (ci != null)
				{
					// if the constructor only takes nullable types, warn the programmer
					if (Nullable.GetUnderlyingType(ci.GetParameters()[0].ParameterType) != null)
						throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Class {0} must provide a constructor taking a parameter of type {1}. Nullable parameters are not supported.", targetType, sourceType));

					il.Emit(OpCodes.Newobj, ci);
				}
				else
				{
					// 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,
								targetType));
						}
					}

					// 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;
		}