Click here to Skip to main content
15,892,298 members
Articles / Programming Languages / MSIL

RunSharp - Reflection.Emit Has Never Been Easier

Rate me:
Please Sign up or sign in to vote.
4.92/5 (48 votes)
10 Aug 2009MIT5 min read 118.3K   509   118  
RunSharp (or Run#) is a high-level wrapper around the Reflection.Emit API, allowing you to generate code at runtime quickly and easily.
/*
 * Copyright (c) 2009, Stefan Simek
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;

namespace TriAxis.RunSharp
{
	using Operands;

	abstract class Conversion
	{
		public abstract void Emit(CodeGen g, Type from, Type to);
		public virtual bool IsAmbiguous { get { return false; } }
		public virtual bool IsValid { get { return true; } }

		const byte D = 0;	// direct conversion
		const byte I = 1;	// implicit conversion
		const byte E = 2;	// explicit conversion
		const byte X = 3;	// no conversion

		static byte[][] convTable = { // indexed by TypeCode [from,to]
			// FROM      TO:       NA,OB,DN,BL,CH,I1,U1,I2,U2,I4,U4,I8,U8,R4,R8,DC,DT,--,ST
			/* NA */ new byte[] { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X },
			/* OB */ new byte[] { X, D, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X },
			/* DN */ new byte[] { X, X, D, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X },
			/* BL */ new byte[] { X, X, X, D, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X },
			/* CH */ new byte[] { X, X, X, X, D, E, E, E, I, I, I, I, I, I, I, I, X, X, X },
			/* I1 */ new byte[] { X, X, X, X, E, D, E, I, E, I, E, I, E, I, I, I, X, X, X },
			/* U1 */ new byte[] { X, X, X, X, E, E, D, I, I, I, I, I, I, I, I, I, X, X, X },
			/* I2 */ new byte[] { X, X, X, X, E, E, E, D, E, I, E, I, E, I, I, I, X, X, X },
			/* U2 */ new byte[] { X, X, X, X, E, E, E, E, D, I, I, I, I, I, I, I, X, X, X },
			/* I4 */ new byte[] { X, X, X, X, E, E, E, E, E, D, E, I, E, I, I, I, X, X, X },
			/* U4 */ new byte[] { X, X, X, X, E, E, E, E, E, E, D, I, I, I, I, I, X, X, X },
			/* I8 */ new byte[] { X, X, X, X, E, E, E, E, E, E, E, D, E, I, I, I, X, X, X },
			/* U8 */ new byte[] { X, X, X, X, E, E, E, E, E, E, E, E, D, I, I, I, X, X, X },
			/* R4 */ new byte[] { X, X, X, X, E, E, E, E, E, E, E, E, E, D, I, E, X, X, X },
			/* R8 */ new byte[] { X, X, X, X, E, E, E, E, E, E, E, E, E, E, D, E, X, X, X },
			/* DC */ new byte[] { X, X, X, X, E, E, E, E, E, E, E, E, E, E, E, D, X, X, X },
			/* DT */ new byte[] { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, D, X, X },
			/* -- */ new byte[] { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X },
			/* ST */ new byte[] { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, D },
		};

		delegate Conversion ConversionProvider(Operand op, Type to, bool onlyStandard);

		#region Conversions
		sealed class Direct : Conversion
		{
			public static readonly Direct Instance = new Direct();

			public override void Emit(CodeGen g, Type from, Type to)
			{
			}
		}

		sealed class Primitive : Conversion
		{
			public static readonly Primitive Instance = new Primitive();

			public override void Emit(CodeGen g, Type from, Type to)
			{
				g.EmitConvHelper(Type.GetTypeCode(to));
			}
		}

		sealed class Boxing : Conversion
		{
			public static readonly Boxing Instance = new Boxing();

			public override void Emit(CodeGen g, Type from, Type to)
			{
				g.IL.Emit(OpCodes.Box, from);
			}
		}

		sealed class Unboxing : Conversion
		{
			public static readonly Unboxing Instance = new Unboxing();

			public override void Emit(CodeGen g, Type from, Type to)
			{
				g.IL.Emit(OpCodes.Unbox_Any, to);
			}
		}

		sealed class Cast : Conversion
		{
			public static readonly Cast Instance = new Cast();

			public override void Emit(CodeGen g, Type from, Type to)
			{
				g.IL.Emit(OpCodes.Castclass, to);
			}
		}

		class Invalid : Conversion
		{
			public static readonly Invalid Instance = new Invalid();

			public override void Emit(CodeGen g, Type from, Type to)
			{
				throw new InvalidCastException(string.Format(null, Properties.Messages.ErrInvalidConversion, from == null ? "<null>" : from.FullName, to.FullName));
			}

			public override bool IsValid
			{
				get
				{
					return false;
				}
			}
		}

		class Ambiguous : Conversion
		{
			public static readonly Ambiguous Instance = new Ambiguous();

			public override void Emit(CodeGen g, Type from, Type to)
			{
				throw new AmbiguousMatchException(string.Format(null, Properties.Messages.ErrAmbiguousConversion, from.FullName, to.FullName));
			}

			public override bool IsAmbiguous
			{
				get
				{
					return true;
				}
			}

			public override bool IsValid
			{
				get
				{
					return false;
				}
			}
		}

		class UserDefined : Conversion
		{
			Conversion before, after;
			IMemberInfo method;
			Type fromType, toType;
			bool sxSubset, txSubset;

			public UserDefined(Conversion before, IMemberInfo method, Conversion after)
			{
				this.before = before;
				this.method = method;
				this.fromType = method.ParameterTypes[0];
				this.toType = method.ReturnType;
				this.after = after;
			}

			public override void Emit(CodeGen g, Type from, Type to)
			{
				before.Emit(g, from, fromType);
				g.IL.Emit(OpCodes.Call, (MethodInfo)method.Member);
				after.Emit(g, toType, to);
			}

			public static Conversion FindImplicit(List<UserDefined> collection, Type from, Type to)
			{
				Type sx = null, tx = null;
				bool any = false;

				for (int i = 0; i < collection.Count; i++)
				{
					UserDefined udc = collection[i];
					any = true;
					if (sx == null && udc.fromType == from)
						sx = from;
					if (tx == null && udc.toType == to)
						tx = to;
				}

				if (!any)
					return Invalid.Instance;

				if (sx == null || tx == null)
				{
					for (int i = 0; i < collection.Count; i++)
					{
						UserDefined udc = collection[i];
						bool sxMatch = sx == null, txMatch = tx == null;

						for (int j = 0; j < collection.Count; j++)
						{
							UserDefined udc2 = collection[j];
							if (udc2 == udc)
								continue;

							if (sxMatch && GetImplicit(udc.fromType, udc2.fromType, true) == null)
								sxMatch = false;

							if (txMatch && GetImplicit(udc2.toType, udc.toType, true) == null)
								txMatch = false;

							if (!(sxMatch || txMatch))
								break;
						}

						if (sxMatch)
							sx = udc.fromType;
						if (txMatch)
							tx = udc.toType;

						if (sx != null && tx != null)
							break;
					}
				}

				if (sx == null || tx == null)
					return Ambiguous.Instance;

				UserDefined match = null;

				for (int i = 0; i < collection.Count; i++)
				{
					UserDefined udc = collection[i];
					if (udc.fromType == sx && udc.toType == tx)
					{
						if (match != null)
							return Ambiguous.Instance;	// ambiguous match
						else
							match = udc;
					}
				}

				if (match == null)
					return Ambiguous.Instance;

				return match;
			}

			public static Conversion FindExplicit(List<UserDefined> collection, Type from, Type to)
			{
				Type sx = null, tx = null;
				bool sxSubset = false, txSubset = false;
				bool any = false;

				for (int i = 0; i < collection.Count; i++)
				{
					UserDefined udc = collection[i];
					any = true;

					if (sx == null && udc.fromType == from)
						sx = from;
					if (tx == null && udc.toType == to)
						tx = to;

					if (udc.sxSubset = GetImplicit(from, udc.fromType, true) != null)
						sxSubset = true;
					if (udc.txSubset = GetImplicit(udc.toType, to, true) != null)
						txSubset = true;
				}

				if (!any)
					return Invalid.Instance;

				if (sx == null || tx == null)
				{
					for (int i = 0; i < collection.Count; i++)
					{
						UserDefined udc = collection[i];
						bool sxMatch = sx == null && !sxSubset || udc.sxSubset;
						bool txMatch = tx == null && !txSubset || udc.txSubset;

						if (!(sxMatch || txMatch))
							continue;

						for (int j = 0; j < collection.Count; j++)
						{
							UserDefined udc2 = collection[j];
							if (udc2 == udc)
								continue;

							if (sxMatch)
							{
								if (sxSubset)
								{
									if (udc.sxSubset && GetImplicit(udc.fromType, udc2.fromType, true) == null)
										sxMatch = false;
								}
								else
								{
									if (GetImplicit(udc2.fromType, udc.fromType, true) == null)
										sxMatch = false;
								}
							}

							if (txMatch)
							{
								if (txSubset)
								{
									if (udc.txSubset && GetImplicit(udc2.toType, udc.toType, true) == null)
										txMatch = false;
								}
								else
								{
									if (GetImplicit(udc.toType, udc2.toType, true) == null)
										txMatch = false;
								}
							}

							if (!(sxMatch || txMatch))
								break;
						}

						if (sxMatch)
							sx = udc.fromType;
						if (txMatch)
							tx = udc.toType;

						if (sx != null && tx != null)
							break;
					}
				}

				if (sx == null || tx == null)
					return Ambiguous.Instance;

				UserDefined match = null;

				for (int i = 0; i < collection.Count; i++)
				{
					UserDefined udc = collection[i];
					if (udc.fromType == sx && udc.toType == tx)
					{
						if (match != null)
							return Ambiguous.Instance;	// ambiguous match
						else
							match = udc;
					}
				}

				if (match == null)
					return Ambiguous.Instance;
				
				return match;
			}
		}
		#endregion

		sealed class FakeTypedOperand : Operand
		{
			Type t;

			public FakeTypedOperand(Type t) { this.t = t; }

			public override Type Type
			{
				get
				{
					return t;
				}
			}
		}

		public static Conversion GetImplicit(Type from, Type to, bool onlyStandard)
		{
			return GetImplicit(new FakeTypedOperand(from), to, onlyStandard);
		}

		// the sections mentioned in comments of this method are from C# specification v1.2
		public static Conversion GetImplicit(Operand op, Type to, bool onlyStandard)
		{
			Type from = Operand.GetType(op);

			if (to.Equals(from))
				return Direct.Instance;

			// required for arrays created from TypeBuilder-s
			if (from != null && to.IsArray && from.IsArray)
			{
				if (to.GetArrayRank() == from.GetArrayRank())
				{
					if (to.GetElementType().Equals(from.GetElementType()))
						return Direct.Instance;
				}
			}

			TypeCode tcFrom = Type.GetTypeCode(from);
			TypeCode tcTo = Type.GetTypeCode(to);
			byte ct = convTable[(int)tcFrom][(int)tcTo];

			// section 6.1.2 - Implicit numeric conversions
			if ((from != null && (from.IsPrimitive || from == typeof(decimal))) && (to.IsPrimitive || to == typeof(decimal)))
			{
				if (ct <= I)
				{
					if (from == typeof(decimal) || to == typeof(decimal))
						// decimal is handled as user-defined conversion, but as it is a standard one, always enable UDC processing
						onlyStandard = false;
					else
						return Primitive.Instance;
				}
			}

			IntLiteral intLit = op as IntLiteral;

			// section 6.1.3 - Implicit enumeration conversions
			if (!onlyStandard && to.IsEnum && (object)intLit != null && intLit.Value == 0)
				return Primitive.Instance;

			// section 6.1.4 - Implicit reference conversions
			if ((from == null || !from.IsValueType) && !to.IsValueType)
			{
				if (from == null) // from the null type to any reference type
					return Direct.Instance;

				if (to.IsAssignableFrom(from))	// the rest
					return Direct.Instance;
			}

			if (from == null)	// no other conversion from null type is possible
				return Invalid.Instance;

			// section 6.1.5 - Boxing conversions
			if (from.IsValueType)
			{
				if (to.IsAssignableFrom(from))
					return Boxing.Instance;
			}

			// section 6.1.6 - Implicit constant expression conversions
			if ((object)intLit != null && from == typeof(int) && to.IsPrimitive)
			{
				int val = intLit.Value;

				switch (tcTo)
				{
					case TypeCode.SByte:
						if (val >= sbyte.MinValue && val <= sbyte.MaxValue)
							return Direct.Instance;
						break;
					case TypeCode.Byte:
						if (val >= byte.MinValue && val <= byte.MaxValue)
							return Direct.Instance;
						break;
					case TypeCode.Int16:
						if (val >= short.MinValue && val <= short.MaxValue)
							return Direct.Instance;
						break;
					case TypeCode.UInt16:
						if (val >= ushort.MinValue && val <= ushort.MaxValue)
							return Direct.Instance;
						break;
					case TypeCode.UInt32:
						if (val >= 0)
							return Direct.Instance;
						break;
					case TypeCode.UInt64:
						if (val >= 0)
							return Primitive.Instance;
						break;
				}
			}
			if (from == typeof(long))
			{
				LongLiteral longLit = op as LongLiteral;
				if ((object)longLit != null && longLit.Value > 0)
					return Direct.Instance;
			}

			// section 6.1.7 - User-defined implicit conversions (details in section 6.4.3)
			if (onlyStandard || from == typeof(object) || to == typeof(object) || from.IsInterface || to.IsInterface ||
				to.IsSubclassOf(from) || from.IsSubclassOf(to))
				return Invalid.Instance;	// skip not-permitted conversion attempts (section 6.4.1)

			List<UserDefined> candidates = null;
			FindCandidates(ref candidates, FindImplicitMethods(from, to), op, to, GetImplicit);

			if (candidates == null)
				return Invalid.Instance;

			if (candidates.Count == 1)
				return candidates[0];

			return UserDefined.FindImplicit(candidates, from, to);
		}

		static IEnumerable<IMemberInfo> FindImplicitMethods(Type from, Type to)
		{
			while (from != null)
			{
				foreach (IMemberInfo mi in TypeInfo.GetMethods(from))
				{
					if (mi.IsStatic && mi.Name.Equals("op_Implicit", StringComparison.OrdinalIgnoreCase) && mi.ParameterTypes.Length == 1)
						yield return mi;
				}

				from = from.BaseType;
			}

			foreach (IMemberInfo mi in TypeInfo.GetMethods(to))
			{
				if (mi.IsStatic && mi.Name.Equals("op_Implicit", StringComparison.OrdinalIgnoreCase) && mi.ParameterTypes.Length == 1)
					yield return mi;
			}
		}

		static void FindCandidates(ref List<UserDefined> candidates, IEnumerable<IMemberInfo> methods, Operand from, Type to, ConversionProvider extraConv)
		{
			foreach (IMemberInfo mi in methods)
			{
				Conversion before = extraConv(from, mi.ParameterTypes[0], true);
				if (!before.IsValid)
					continue;

				Conversion after = extraConv(new FakeTypedOperand(mi.ReturnType), to, true);
				if (!after.IsValid)
					continue;

				if (candidates == null)
					candidates = new List<UserDefined>();

				candidates.Add(new UserDefined(before, mi, after));
			}
		}

		public static Conversion GetExplicit(Operand op, Type to, bool onlyStandard)
		{
			// try implicit
			Conversion conv = GetImplicit(op, to, onlyStandard);
			if (conv.IsValid)
				return conv;

			Type from = Operand.GetType(op);

			// section 6.3.2 - Standard explicit conversions
			if (onlyStandard)
			{
				if (from == null || !GetImplicit(to, from, true).IsValid)
					return Invalid.Instance;
			}

			TypeCode tcFrom = Type.GetTypeCode(from);
			TypeCode tcTo = Type.GetTypeCode(to);
			byte ct = convTable[(int)tcFrom][(int)tcTo];

			// section 6.2.1 - Explicit numeric conversions, 6.2.2 - Explicit enumeration conversions
			if ((from.IsPrimitive || from.IsEnum || from == typeof(decimal)) && (to.IsPrimitive || to.IsEnum || to == typeof(decimal)))
			{
				if (ct == D)
					return Direct.Instance;	// this can happen for conversions involving enum-s

				if (ct <= E)
				{
					if (from == typeof(decimal) || to == typeof(decimal))
						// decimal is handled as user-defined conversion, but as it is a standard one, always enable UDC processing
						onlyStandard = false;
					else
						return Primitive.Instance;
				}
			}

			// section 6.2.5 - User-defined explicit conversions (details in section 6.4.4)
			if (!(onlyStandard || from == typeof(object) || to == typeof(object) || from.IsInterface || to.IsInterface ||
				to.IsSubclassOf(from) || from.IsSubclassOf(to)))
			{
				List<UserDefined> candidates = null;
				FindCandidates(ref candidates, FindExplicitMethods(from, to), op, to, GetExplicit);

				if (candidates != null)
				{
					if (candidates.Count == 1)
						return candidates[0];

					return UserDefined.FindExplicit(candidates, from, to);
				}
			}

			// section 6.2.3 - Explicit reference conversions, 6.2.4 - Unboxing conversions
			// TODO: not really according to spec, but mostly works
			if (!from.IsValueType && from.IsAssignableFrom(to))
			{
				if (to.IsValueType)
					return Unboxing.Instance;
				else
					return Cast.Instance;
			}

			return Invalid.Instance;
		}

		static IEnumerable<IMemberInfo> FindExplicitMethods(Type from, Type to)
		{
			while (from != null)
			{
				foreach (IMemberInfo mi in TypeInfo.GetMethods(from))
				{
					if (mi.IsStatic &&
						(mi.Name.Equals("op_Implicit", StringComparison.OrdinalIgnoreCase) || 
						mi.Name.Equals("op_Explicit", StringComparison.OrdinalIgnoreCase)) &&
						mi.ParameterTypes.Length == 1)
						yield return mi;
				}

				from = from.BaseType;
			}

			while (to != null)
			{
				foreach (IMemberInfo mi in TypeInfo.GetMethods(to))
				{
					if (mi.IsStatic &&
						(mi.Name.Equals("op_Implicit", StringComparison.OrdinalIgnoreCase) ||
						mi.Name.Equals("op_Explicit", StringComparison.OrdinalIgnoreCase)) &&
						mi.ParameterTypes.Length == 1)
						yield return mi;
				}

				to = to.BaseType;
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Web Developer
Slovakia Slovakia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions