[Из песочницы] Лень, рефлексия, атрибуты, динамические сборки

Небольшой опыт, полученный благодаря лени


Года три назад, работал я на одну фирму. Было нас 4 программиста. Один писал бизнес логику приложения. Описывал он ее с помощью интерфейсов (interface). Логические связи, зависимости и т. д. Наша же задача была реализовать эти интерфейсы и связать с GUI. Основной проблемой в этой системе были постоянные изменения структуры связей и параметров. То есть нам приходилось постоянно заниматься правкой и рефакторингом.

Я человек достаточно ленивый. Поэтому пришла мысль — неужели нельзя, как нибудь это автоматизировать. И я сел за книжки.

Шаг первый

Первая идея была достаточна явна и проста. Интерфейсы содержаться в отдельных файлах — так почему бы не распарсить их и создать текстовой файл со сгенерированным классом. Так и было сделано.

К сожалению тех исходников не сохранилось, но есть аналог, кому интересно может посмотреть (классы строятся на основе таблиц из базы данных)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using SV.DataBaseWork;
using v2;
using System.Data.SqlClient;
namespace CreatorDBClasses
{
    class ColumnDB
    {
        public ColumnDB(string name, string type)
        {
            Name = name;
            Type = ConvertType(type);
        }
        public string Name;
        public string Type;
        public string Initial = "null";
        public string ConvertType(string tp)
        {
            switch (tp)
            {
                case "String":
                    Initial = "\"\"";
                    return "string";
                case "Double":
                    Initial = "0";
                    return "double";
                case "Boolean":
                    Initial = "false";
                    return "bool";
                case "Int32":
                    Initial = "0";
                    return "int";
                default:
                    Initial = "null";
                    return tp;
            }
        }
    }
    /// 
    /// Сводное описание для CreatorDBWorkClasses
    /// 
    public class CreatorClasses
    {
        String connStr = null;
        public CreatorClasses(string table, String ConnectionString = null)
        {
            tablename = table;
            className = "cl_" + tablename;
            if (string.IsNullOrEmpty(ConnectionString))
                connStr = v2_SacuraConstants.ConnectionString;
            else
                connStr = ConnectionString;
            //
            // TODO: добавьте логику конструктора
            //
        }
        string mBaseClass = "DataBaseWorkBase";
        public string BaseClass
        {
            get { return mBaseClass; }
            set { mBaseClass = value; }
        }

        string tab = "\t\t";
        string className;
        string sources;
        public string Sources
        {
            get { return sources; }
        }
        string tablename;
        List mListColumn = new List();
        public bool GetSources()
        {
            sources = "\tpublic class " + className + (string.IsNullOrEmpty(BaseClass) ? "" : " : " + BaseClass) + ", IDisposable\r\n\t{\r\n";
            sources += GetConstructor();
            sources += GetProperty();
            sources += GetInitFunction();
            sources += GetSaveFunction();
            sources += GetDeleteFunction();
            sources += GetDisposable();
            sources += StaticGetList();
            sources += GetLastError();
            sources += "\t}";
            return true;
        }
        string GetLastError()
        {
            return "#region Error\r\n" +
                "        string mError;\r\n" +
                "        public string lastError\r\n" +
                "        {\r\n" +
                "            get { return mError; }\r\n" +
                "        }\r\n" +
                "#endregion\r\n";
        }
        string StaticGetList()
        {
            return "#region Static_GetList\r\n" +
                   "     public static List<"+className+"> GetList(string SQL_WHERE = \"\")\r\n" +
                   "     {\r\n" +
                   "         List<" + className + "> lst = null;\r\n" +
                   "         DataBaseWorkBase db = new DataBaseWorkBase(v2_SacuraConstants.ConnectionString);\r\n" +
                   "         if (db.Open())\r\n" +
                   "         {\r\n" +
                   "             lst = new List<" + className + ">();\r\n" +
                   "             SqlCommand sqlcmd = db.CreateSQLCommand();\r\n" +
                   "             sqlcmd.CommandText = \"SELECT * \" +\r\n" +
                   "                 \"FROM "+tablename+" \" + SQL_WHERE;\r\n" +
                   "             SqlDataReader reader = sqlcmd.ExecuteReader();\r\n" +
                   "             while (reader.Read())\r\n" +
                   "             {\r\n" +
                   "                 " + className + " ord = new " + className + "();\r\n" +
                   "                 if (ord.InitFromDataReader(reader))\r\n" +
                   "                     lst.Add(ord);\r\n" +
                   "             }\r\n" +
                   "             reader.Close();\r\n" +
                   "             reader = null;\r\n" +
                   "         }\r\n" +
                   "         db.Close();\r\n" +
                   "         db = null;\r\n" +
                   "         return lst;\r\n" +
                   "     }\r\n" +
                "#endregion\r\n";
        }
        string GetDisposable()
        {
            return "#region Члены IDisposable\r\n" +
                   "     public override void Close()\r\n" +
                   "     {\r\n" +
                   "         base.Close();\r\n" +
                   "     }\r\n" +
                   "\r\n" +
                   "    protected override void Dispose(bool disposing)\r\n" +
                   "    {\r\n" +
                   "        if (disposing)\r\n" +
                   "        {\r\n"+
                   "            Close();\r\n" +
                   "            base.Dispose(true);\r\n" +
                   "        }\r\n" +
                   "    }\r\n" +                  
                   "#endregion\r\n";
        }
        string GetDeleteFunction()
        {
            string con = "#region Delete\r\n"+            
                tab+"public bool Delete()\r\n"+ 
                tab+"{\r\n"+ 
                tab+"    bool result = false;\r\n"+ 
                tab+"    try\r\n"+ 
                tab+"    {\r\n"+ 
                tab+"        SqlCommand sqlcmd = CreateSQLCommand();\r\n"+ 
                tab+"        sqlcmd.CommandText = \"DELETE FROM "+tablename+" WHERE ID=@ID\";\r\n"+ 
                tab+"        sqlcmd.Parameters.AddWithValue(\"@ID\", m_ID);\r\n"+ 
                tab+"        sqlcmd.ExecuteNonQuery();\r\n"+ 
                tab+"        result = true;\r\n"+ 
                tab+"    }\r\n"+ 
                tab+"    catch (System.Exception ex)\r\n"+ 
                tab+"    {\r\n"+ 
                tab+"    }\r\n"+ 
                tab+"    return result;\r\n"+ 
                tab+"}\r\n"+ 
                "#endregion\r\n";
            return con;
        }
        string GetInitParams()
        {
            string pr = "";
            int cnt = mListColumn.Count;
            for (int a = 0; a < cnt; a++)
            {
                if (mListColumn[a].Type != "string")
                    pr += tab + tab + mListColumn[a].Type + ".TryParse(reader[" + a.ToString() + "].ToString(), out m_" + mListColumn[a].Name + ");\r\n";
                else
                    pr += tab + tab + mListColumn[a].Name +"=reader[" + a.ToString() + "].ToString();\r\n";
            }          
            return pr;
        }
        string GetInitFunction()
        {
            string fn = "#region Init\r\n" +
                    tab + "public bool InitFromDataReader(SqlDataReader reader)\r\n" +
                    tab + "{\r\n" +
                    tab + "    try\r\n" +
                    tab + "    {\r\n" +
                    GetInitParams() +
                    tab + "    }\r\n" +
                    tab + "    catch (System.Exception ex)\r\n" +
                    tab + "    {\r\n" +
                    tab + "        return false;\r\n" +
                    tab + "    }\r\n" +
                    tab + "    return true;\r\n" +
                    tab + "}\r\n" +
                    tab + "public bool Init(string id)\r\n" +
                    tab + "{\r\n" +
                    tab + "    if (string.IsNullOrEmpty(id))\r\n" +
                    tab + "        return false;\r\n" +
                    tab + "    int t;\r\n" +
                    tab + "    int.TryParse(id, out t);\r\n" +
                    tab + "    return Init(t);\r\n" +
                    tab + "}\r\n" +
                    tab + "public bool Init(int id)\r\n" +
                    tab + "{\r\n" +
                    tab + "   if (!base.Open())\r\n" +
                    tab + "       return false;\r\n" +
                    tab + "   bool IsLoad = false;\r\n" +
                    tab + "   try\r\n" +
                    tab + "   {\r\n" +
                    tab + "       SqlCommand sqlcmd = CreateSQLCommand();\r\n" +
                    tab + "       sqlcmd.CommandText = \"SELECT * \" +\r\n" +
                    tab + "           \"FROM " + tablename + " WHERE [ID]=@ID\";\r\n" +
                    tab + "       sqlcmd.Parameters.AddWithValue(\"@ID\", id);\r\n" +
                    tab + "       SqlDataReader reader = sqlcmd.ExecuteReader();\r\n" +
                    tab + "       if (reader.Read())\r\n" +
                    tab + "       {\r\n" +
                    tab + "          if (!InitFromDataReader(reader))\r\n" +
                    tab + "            {\r\n" +
                    tab + "              reader.Close();\r\n" +
                    tab + "              base.Close();\r\n"+
                    tab + "              return false;\r\n" +
                    tab + "            }\r\n" +
                    tab + "           IsLoad = true;\r\n" +
                    tab + "       }\r\n" +
                    tab + "       reader.Close();\r\n" +
                    tab + "   }\r\n" +
                    tab + "   catch (System.Exception ex)\r\n" +
                    tab + "   {\r\n" +
                    tab + "       mError = ex.Message;\r\n" +
                    tab + "       IsLoad = false;\r\n" +
                    tab + "   }\r\n" +
                    tab + "   base.Close();" +
                    tab + "   return IsLoad;\r\n" +
                    tab + "}\r\n";
            fn += "#endregion\r\n";
            return fn;
        }
        string GetConstructor()
        {
            string con = "#region Constructor\r\n" +
                tab + "public " + className + "(string ConnectionString)\r\n" +
                tab + "\t: base (ConnectionString)\r\n" +
                tab + "{\r\n" +
                tab + "}\r\n" +
                tab + "public " + className + "()\r\n" +
                tab + "\t:base(v2_SacuraConstants.ConnectionString)\r\n" +
                tab + "{\r\n" +
                tab + "}\r\n" +
                "#endregion\r\n";
            return con;
        }
        string GetProperty()
        {
            mListColumn.Clear();
            string src = "#region Property\r\n";
            //////////////////////////////////////////////////////////////////////////
            //add property
            SqlConnection myConnection = new SqlConnection(connStr);
            SqlDataAdapter myAdapter = new SqlDataAdapter("select * from " + tablename, myConnection);
            DataSet dataSet = new DataSet();
            myConnection.Open();
            myAdapter.Fill(dataSet, "tablename");
            myConnection.Close();
            ConstraintCollection prKey = dataSet.Tables[0].Constraints;

            for (int i = 0; i < dataSet.Tables[0].Columns.Count; i++)
            {
                string tab1 = "\t\t\t";
                src += tab;
                ColumnDB clTp = new ColumnDB(dataSet.Tables[0].Columns[i].ColumnName, dataSet.Tables[0].Columns[i].DataType.Name);
                mListColumn.Add(clTp);
                src += clTp.Type + " m_" + clTp.Name +"="+clTp.Initial+ ";\r\n";
                src += "\t\t";
                src += "public "+clTp.Type + " " + clTp.Name + "\r\n" + tab + "{\r\n" +
                    tab1 + "get\r\n" + tab1 + "{\r\n" + tab1 + "\treturn m_" + clTp.Name + ";\r\n" + tab1 + "}\r\n" + tab1 +
                    "set\r\n" + tab1 + "{\r\n" + tab1 + "\tm_" + clTp.Name + "=value;\r\n" + tab1 + "}\r\n" + tab + "}\r\n";
            }
            //////////////////////////////////////////////////////////////////////////
            return src + "#endregion\r\n";
        }
        string GetSaveInsertParams()
        {
            string pr = "";
            int cnt = mListColumn.Count;
            for (int a = 1; a < cnt; a++)
            {
                pr += tab + tab + tab + (a == 1 ? "\"[" : "\",[") + mListColumn[a].Name + "]\"+\r\n";
            }
            return pr;
        }
        string GetSaveInsertValues()
        {
            string pr = "";
            int cnt = mListColumn.Count;
            for (int a = 1; a < cnt; a++)
            {
                pr += tab + tab + tab + (a == 1 ? "\"@" : "\",@") + mListColumn[a].Name + (a != cnt - 1 ? "\"+\r\n" : ")\"");
            }
            return pr;
        }
        string GetSaveUpdateParams()
        {
            string pr = "";
            int cnt = mListColumn.Count;
            for (int a = 1; a < cnt; a++)
            {
                pr += tab + tab + tab + (a == 1 ? "\"[" : "\",[") + mListColumn[a].Name + "]=@" + mListColumn[a].Name +
                    "\"" + (a != cnt - 1 ? "+\r\n" : "");
            }
            return pr;
        }
        string GetAddWithValue()
        {
            string pr = "";
            int cnt = mListColumn.Count;
            for (int a = 1; a < cnt; a++)
            {
                pr += tab + tab + "sqlcmd.Parameters.AddWithValue(\"@" + mListColumn[a].Name +"\", m_"+
                    mListColumn[a].Name+");\r\n";
            }
            return pr;
        }
        string GetSaveFunction()
        {
            string con = "#region Save\r\n" +
            tab + "public bool Save()\r\n" +
            tab + "    {\r\n" +
            tab + "        bool result = false;\r\n" +
            tab + "        try\r\n" +
            tab + "        {\r\n" +
            tab + "            SqlCommand sqlcmd = CreateSQLCommand();\r\n" +
            tab + "            if (m_ID <= 0)\r\n" +
            tab + "            {\r\n" +
            tab + "                sqlcmd.CommandText = \"INSERT INTO " + tablename + " (  \"+\r\n" +
                    GetSaveInsertParams() +
            tab + "                     \") VALUES (\"+\r\n" +
                    GetSaveInsertValues() + "+\";SELECT CAST(scope_identity() AS int)\";\r\n" +
            tab + "            }\r\n" +
            tab + "            else\r\n" +
            tab + "            {\r\n" +
            tab + "                sqlcmd.CommandText = \"UPDATE " + tablename + " SET \" +\r\n" +
                    GetSaveUpdateParams() + "+\r\n" +
            tab + "                \" WHERE ID=@ID\";\r\n" +
            tab + "                sqlcmd.Parameters.AddWithValue(\"@ID\", m_ID);\r\n" +
            tab + "            }\r\n" +
            tab + GetAddWithValue() + "\r\n" +
            tab + "            if (m_ID > 0)\r\n" +
            tab + "                sqlcmd.ExecuteNonQuery();\r\n" +
            tab + "            else\r\n" +
            tab + "            {\r\n" +
            tab + "                object ob;\r\n" +
            tab + "                ob = sqlcmd.ExecuteScalar();\r\n" +
            tab + "                if(ob != null)\r\n" +
            tab + "                    int.TryParse(ob.ToString(), out m_ID);\r\n" +
            tab + "            }\r\n" +
            tab + "        }\r\n" +
            tab + "        catch (System.Exception ex)\r\n" +
            tab + "        {\r\n" +
            tab + "            mError = ex.Message;\r\n" +
            tab + "            result = false;\r\n" +
            tab + "        }\r\n" +
            tab + "        return result;\r\n" +
            tab + "    }\r\n" +
            "#endregion\r\n";
            return con;
        }
    }

}


Вроде бы, проблема решена. Но тем не менее работы еще много оставалось: перенос файлов, рефакторинг. Да еще у ребят ничего не изменилось. Они занимались созданием UI и привязкой его к объектной модели.

Шаг второй

Продолжая поиски в сети я наткнулся на описание класс Type, заинтересовавшись, я почитал про него подробнее. Есть много интересных функций у этого класса. Фактически благодаря ему можно полностью получить всю информацию по классу. Конструктор, реализованные интерфейсы, свойства, переменные, функции… Полную информацию. И я начал эксперементировать с ним, и в итоге, получил:

Класс для работы с типами классов

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

namespace SV.Tools
{
	public delegate void AddProp(string name, string val);
	/// 
	/// структура для дублирующих параметров
	/// 
	public struct MetodInfoDouble
	{
		public MethodInfo getMetod;
		public MethodInfo setMetod;
	}
	/// 
	/// класс со статик функциями для работы с классами :( ну и описалово получилось
	/// 
	public class ClassesTools
	{
		/// 
		/// получения данных всех порперти класса включая вложения
		/// 
		/// 
		/// 
		/// 
		public static void GetAllPropertyData(object ob, AddProp delegateProp, string parentName = "")
		{
			if (ob == null || delegateProp == null)
				return;
			PropertyInfo[] propInfo = ob.GetType().GetProperties();
			for (int b = 0; b < propInfo.Length; b++)
			{
				ParameterInfo[] param = propInfo[b].GetIndexParameters();
				if (param.Length == 0 && propInfo[b].CanRead && propInfo[b].Name != "Root" && propInfo[b].Name != "Parent")
				{
					object data = propInfo[b].GetValue(ob, null);
					if (propInfo[b].PropertyType.IsInterface && data != null)
					{
							GetAllPropertyData(data, delegateProp, (parentName == "" ? "" : parentName + ".") + propInfo[b].Name);

					}
					else
					{
						delegateProp((parentName == "" ? "" : parentName + ".") + propInfo[b].Name,
							( data == null ? "NULL" : SV.ConversionTools.DataCoversion.ConvertByType(data)));
					}
				}
			}
		}

		static AppDomain domain = Thread.GetDomain();
		static Assembly[] asm = domain.GetAssemblies();
		/// 
		/// поиск интерфеса по имени
		/// 
		/// имя интерфейса
		/// 
		public static Type FindInterfece(string Namespace, string Name, bool isIgnorCase = true)
		{
			if (Namespace == "" || Name == "")
				return null;
			
			int count = asm.Length;
			for (int a = 0; a < count; a++)
			{
				Type t = asm[a].GetType(Namespace+"."+Name);
				if (t != null)
					return t;
			}
			return null;
		}
		public static List GetAsseblyIntrface(string AssembleName, string Namespace)
		{
			if (Namespace == "" )
				return null;
			int count = asm.Length;
			for (int a = 0; a < count; a++)
			{
				if (asm[a].GetName().Name == AssembleName)
				{
					Type[] t = asm[a].GetTypes();
					List lst = new List();
					count = t.Length;
					for(int b =0; b < count; b++)
					{
						if (t[b].Namespace == Namespace)
							lst.Add(t[b]);
					}
					return lst;
				}				
			}
			return null;
		}
		/// 
		/// находит все нтерфейсы, включая вложенные, удаляет дубликаты
		/// 
		/// 
		/// 
		public static void GetAllInterfece(Type[] tp, ref List listInterfece)
		{
			if (tp == null)
				return;
			int count = tp.Length;
			for (int a = 0; a < count; a++)
			{
				Type rezult = listInterfece.Find(
					delegate(Type typ)
					{
						return tp[a] == typ;
					}
					);
				if (rezult == null)
					listInterfece.Add(tp[a]);
				Type[] t = tp[a].GetInterfaces();
				GetAllInterfece(t, ref listInterfece);
			}
		}
		/// 
		/// находит все нтерфейсы, включая вложенные
		/// 
		/// 
		/// 
		public static List GetAllInterfece(Type parentClass)
		{
			List listClasses = new List();
			GetAllInterfece(new Type[] { parentClass }, ref listClasses);
			return listClasses;
		}
		/// 
		/// находит все нтерфейсы, включая вложенные, удаляет дубликаты
		/// 
		/// 
		/// 
		/// 
		public static List GetAllInterfece(Type parentClass, List listInterfece)
		{
			List listClasses = new List();
			GetAllInterfece(new Type[] { parentClass }, ref listClasses);
			GetAllInterfece(listInterfece.ToArray(), ref listInterfece);
			return RemoveDouble(listClasses, listInterfece);
		}
		/// 
		/// удаляет дубликаты в списках
		/// 
		/// источник
		/// список в котором уберуться встречающиеся значения в sources
		/// возращаемое значение
		public static List RemoveDouble(List sources, List editableList)
		{
			for (int a = editableList.Count - 1; a >= 0; a--)
			{
				if (sources.Find((Type t) => editableList[a] == t) != null)
					editableList.RemoveAt(a);
			}
			return editableList;
		}
		/// 
		/// поиск параметра во всех интерфейсах типа
		/// 
		/// тип класса
		/// имя параметра
		/// найденный параметр
		/// 
		/// тестовая функция
		/// 
		public static PropertyInfo FindProperty(Type _class, string propertyName)
		{
			List allProperty = GetAllProperty(_class);
			int count = allProperty.Count;
			PropertyInfo info = null;
			for (int a = 0; a < count; a++)
			{
				if (allProperty[a].Name == propertyName)
				{
					info = allProperty[a];
					break;
				}
			}

			return info;
		}
		public static List RemoveDouble(List property)
		{
			List retryList = new List();
			int count = property.Count;
			for (int a = 0; a < count; a++)
			{
				if (retryList.Find((Type inf) => property[a] == inf) == null)
					retryList.Add(property[a]);

			}
			return retryList;
		}

		public static List RemoveDouble(List property)
		{
			List retryList = new List();
			int count = property.Count;
			for (int a = 0; a < count; a++)
			{
				if(retryList.Find( (PropertyInfo inf) =>property[a] == inf	) == null)
					retryList.Add(property[a]);

			}
			return retryList;
		}
		/// 
		/// получает все параметры по типу, с удалением дублей
		/// 
		/// родидельский тип
		/// список параметров
		/// 
		/// тестовая функция
		/// 
		public static List GetAllProperty(Type parent)
		{
			List allTypes = GetAllInterfece(parent);
			List allProperty = new List();
			if (allTypes != null)
			{
				int count = allTypes.Count;
				for(int a =0; a < count; a++)
				{
					allProperty.AddRange(allTypes[a].GetProperties(/*BindingFlags.Default*/));
				}
			}
			return RemoveDouble(allProperty);
		}
		/// 
		/// поиск параметра по имени
		/// 
		/// имя параметра
		/// 
		public static PropertyInfo GetPropertyByName(object curr, string name, ref object objectIsCall)
		{
			if (curr == null)
				return null;
			PropertyInfo pI = curr.GetType().GetProperty(name);
			objectIsCall = curr;
			if (pI == null)
			{

				int t = name.IndexOf('.');
				if (t > 0)
				{
					string curName = name.Substring(0, t);
					pI = curr.GetType().GetProperty(curName);
					if (pI != null)
					{
						name = name.Remove(0, t + 1);
						if (name.Length > 0)
						{
							object v = pI.GetValue(curr, null);
							if (v == null)
								return null;
							return GetPropertyByName(v, name, ref objectIsCall);
						}
					}

				}
			}
			return pI;
		}
		/// 
		/// формирует списки метаданных на основании переданных типов
		/// 
		/// список типов интерфейса\класса
		/// список для заполнения
		/// список для заполнения
		/// список для заполнения
		/// список для заполнения
		/// список для заполнения
		public static void GetInterfaceMetadata(List interfeceList, ref List propertyInfo,
			ref List memberInfo, ref List fieldInfo, ref List metodInfo, ref List eventInfo)
		{
			int count = interfeceList.Count;
			for (int a = 0; a < count; a++)
			{
				//////////////////////////////////////////////////////////////////////////
				//базовые евенты и проперти
				PropertyInfo[] propertyIE = interfeceList[a].GetProperties();
				propertyInfo.AddRange(propertyIE);
				EventInfo[] events = interfeceList[a].GetEvents();
				eventInfo.AddRange(events);
				MemberInfo[] membersIE = interfeceList[a].GetMembers();
				memberInfo.AddRange(membersIE);
				FieldInfo[] fieldIE = interfeceList[a].GetFields();
				fieldInfo.AddRange(fieldIE);
				MethodInfo[] metodIE = interfeceList[a].GetMethods();
				metodInfo.AddRange(metodIE);
			}

		}
		/// 
		/// функция нахождения пвоторяющихся  пропертей
		/// 
		/// 
		/// 
		public static Dictionary RemoveDoubleProperty(List propertyInfoInterface)
		{
			if (propertyInfoInterface == null)
				return null;
			Dictionary m_doubleList = new Dictionary();
			int count = propertyInfoInterface.Count - 1;
			for (int a = count; a >= 0; a--)
			{
				List fnd = propertyInfoInterface.FindAll(
					(PropertyInfo inf) => inf.Name == propertyInfoInterface[a].Name);

				PropertyInfo fullMetod = null;
				MetodInfoDouble mDouble = new MetodInfoDouble();
				mDouble.getMetod = null;
				mDouble.setMetod = null;

				if (fnd != null && fnd.Count > 1)
				{
					string tmp = "";
					for (int b = 0; b < fnd.Count; b++)
					{
						tmp += fnd[b].ReflectedType.FullName + "\r\n";
						propertyInfoInterface.Remove(fnd[b]);
						if (fnd[b].CanRead && fnd[b].CanWrite)
							fullMetod = fnd[b];
						else if (fnd[b].CanRead)
							mDouble.getMetod = fnd[b].GetGetMethod();
						else if (fnd[b].CanWrite)
							mDouble.setMetod = fnd[b].GetSetMethod();
					}
#if DEBUG
					//MessageBox.Show("DEBUG:\r\nПовторяющийся параметр с именем: " + fnd[0].Name + "\r\nВ интерфейсах:\r\n" + tmp);
#endif

					if (fullMetod != null)
						propertyInfoInterface.Add(fullMetod);
					else
					{
						m_doubleList.Add(fnd[0].Name, mDouble);
						propertyInfoInterface.Add(fnd[0]);
					}
				}

			}

			return m_doubleList;
		}
		public static bool IsPrimitive(Type t)
		{
			if (t == null)
				return true;
			if (!t.IsClass && !t.IsInterface || t.IsPrimitive || t.IsEnum || t == typeof(String) || t == typeof(Guid) || t == typeof(DateTime))
				return true;

			return false;
		}
		/// 
		/// функция получения данных и параметра по имени параметра
		/// 
		/// имя параметра
		///передаваемые параметры
		/// данные, могут быть null если не найденн параметр или он нулевой
		static public object GetData(object baseCalss,string propertyName, object[] param = null)
		{
			if (baseCalss == null || propertyName == null)
				return null;
			object RecalcOb = null;
			PropertyInfo _PropertyDescriptor = SV.Tools.ClassesTools.GetPropertyByName(baseCalss, propertyName.ToString(), ref RecalcOb);
			object v = null;
			if (_PropertyDescriptor != null)
				v = _PropertyDescriptor.GetValue((RecalcOb == null ? baseCalss : RecalcOb), param);
			return v;
		}
		/// 
		/// установка данных в параметр по имени
		/// 
		/// имя параметра
		/// новое значение
		///передаваемые параметры
		/// false - если параметр небыл найден
		static public bool SetData(object baseCalss, string propertyName, object newPropertyData, object[] param = null)
		{
			if (baseCalss == null || propertyName == null)
				return false;
			object RecalcOb = null;
			PropertyInfo _PropertyDescriptor = SV.Tools.ClassesTools.GetPropertyByName(baseCalss, propertyName, ref RecalcOb);
			if (_PropertyDescriptor == null)
				return false;

			object data = newPropertyData;
			if (newPropertyData != null && newPropertyData.GetType() != _PropertyDescriptor.PropertyType)
				data = SV.ConversionTools.DataCoversion.ConvertByType(data.ToString(), _PropertyDescriptor.PropertyType);
			_PropertyDescriptor.SetValue((RecalcOb == null ? baseCalss : RecalcOb), data, param);
			return true;
		}


	}
}


Теперь я мог получить полностью всю информацию по любому объекту и 
даже отправлять и получать данные в параметры просто по имени параметра
		/// 
		/// функция получения данных и параметра по имени параметра
		/// 
		/// имя параметра
		///передаваемые параметры
		/// данные, могут быть null если не найденн параметр или он нулевой
		static public object GetData(object baseCalss,string propertyName, object[] param = null)
		{
			if (baseCalss == null || propertyName == null)
				return null;
			object RecalcOb = null;
			PropertyInfo _PropertyDescriptor = SV.Tools.ClassesTools.GetPropertyByName(baseCalss, propertyName.ToString(), ref RecalcOb);
			object v = null;
			if (_PropertyDescriptor != null)
				v = _PropertyDescriptor.GetValue((RecalcOb == null ? baseCalss : RecalcOb), param);
			return v;
		}
		/// 
		/// установка данных в параметр по имени
		/// 
		/// имя параметра
		/// новое значение
		///передаваемые параметры
		/// false - если параметр небыл найден
		static public bool SetData(object baseCalss, string propertyName, object newPropertyData, object[] param = null)
		{
			if (baseCalss == null || propertyName == null)
				return false;
			object RecalcOb = null;
			PropertyInfo _PropertyDescriptor = SV.Tools.ClassesTools.GetPropertyByName(baseCalss, propertyName, ref RecalcOb);
			if (_PropertyDescriptor == null)
				return false;

			object data = newPropertyData;
			if (newPropertyData != null && newPropertyData.GetType() != _PropertyDescriptor.PropertyType)
				data = SV.ConversionTools.DataCoversion.ConvertByType(data.ToString(), _PropertyDescriptor.PropertyType);
			_PropertyDescriptor.SetValue((RecalcOb == null ? baseCalss : RecalcOb), data, param);
			return true;
		}


Это был прорыв. Была создана обертка, которая занималась биндингом данных к GUI по тестовым структурам. Скорость разработки значительно увеличилась. В принципе можно было бы успокоиться.
Но я же ленивый.

Шаг третий

Сидя, как то, в пятницу, в баре, в глаза бросилась какая то вывеска-реклама. Что-то там было с словосочетанием ASM… Затуманенный мозг, сразу подбросил ассоциацию: ASM — ASSEMBLER и тут же всплыло воспоминание Common Intermediate Language, а за ним IL Disassembler. Бросив друзей и бар, я побежал домой, не забыв, правда, захватить с собой пару литров пива для стимуляции.

Класс ILGenerator
Дома, почитав информацию по этому классу, я понял — это оно.

Кручу-верчу, что хочу то и ворочу

Собрав в кучу всю информацию, я приступил к делу.

Перво-наперво создав консольный проект с простейшим кодом

 interface iT
    {
        int i { get; set; }
    }
    class cT : iT
    {
        int t = 0;
        public int i
        {
            get
            {
                return t;
            }
            set
            {
                t = value;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
        }
    }


Я просмотрел во что он разворачивается с помощью Ildasm.exe (IL Disassembler)
ASM code:


.class interface private abstract auto ansi ILG.iT
{
  .method public hidebysig newslot specialname abstract virtual 
          instance int32  get_i() cil managed
  {
  } // end of method iT::get_i

  .method public hidebysig newslot specialname abstract virtual 
          instance void  set_i(int32 'value') cil managed
  {
  } // end of method iT::set_i

  .property instance int32 i()
  {
    .get instance int32 ILG.iT::get_i()
    .set instance void ILG.iT::set_i(int32)
  } // end of property iT::i
} // end of class ILG.iT

.class private auto ansi beforefieldinit ILG.cT
       extends [mscorlib]System.Object
       implements ILG.iT
{
  .field private int32 t
  .method public hidebysig newslot specialname virtual final 
          instance int32  get_i() cil managed
  {
    // 
    .maxstack  1
    .locals init ([0] int32 V_0)
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldfld      int32 ILG.cT::t
    IL_0007:  stloc.0
    IL_0008:  br.s       IL_000a

    IL_000a:  ldloc.0
    IL_000b:  ret
  } // end of method cT::get_i

  .method public hidebysig newslot specialname virtual final 
          instance void  set_i(int32 'value') cil managed
  {
    // 
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldarg.1
    IL_0003:  stfld      int32 ILG.cT::t
    IL_0008:  ret
  } // end of method cT::set_i

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.0
    IL_0002:  stfld      int32 ILG.cT::t
    IL_0007:  ldarg.0
    IL_0008:  call       instance void [mscorlib]System.Object::.ctor()
    IL_000d:  nop
    IL_000e:  ret
  } // end of method cT::.ctor

  .property instance int32 i()
  {
    .get instance int32 ILG.cT::get_i()
    .set instance void ILG.cT::set_i(int32)
  } // end of property cT::i
} // end of class ILG.cT


Немножко почесав репу, я создал класс по генерации динамических объектов на базе переданных: базового класса (любого) и списка интерфейсов:

→ Ссылка на проект на GitHub

Немного опишу проект


Формирование свойств объекта:
GET

	void CreatePropertyGetMetod(PropertyInfo property, PropertyBuilder custNamePropBldr, FieldBuilder fieldProperty, object redirectData)
		{
			#region GET_METOD
			//находим метод гет для проперти
			MethodInfo inf = property.GetGetMethod();
			//если такого нет ищем в дублированных
			if (inf == null)
			{
				if (m_doubleList.ContainsKey(property.Name))
					inf = m_doubleList[property.Name].getMetod;
			}
			//если метод найден то начинаем его делать
			if (inf != null)
			{
				//создаем построитель для метода
				MethodBuilder custNameGetPropMthdBldr =
						m_TypeBuilder.DefineMethod("get_" + property.Name,
												m_getSetAttr,
												  property.PropertyType,
												   Type.EmptyTypes);
				//создаем генератор ИЛ
				ILGenerator custNameGetIL = custNameGetPropMthdBldr.GetILGenerator();
				System.Reflection.Emit.Label end = custNameGetIL.DefineLabel();
				//начинаем формировать асмокод
				custNameGetIL.Emit(OpCodes.Nop);
				custNameGetIL.Emit(OpCodes.Ldarg_0);
				//возвращаем локальную переменную
				custNameGetIL.Emit(OpCodes.Ldfld, fieldProperty);
				//выход из проперти
				custNameGetIL.Emit(OpCodes.Ret);
				//перезаписываем метод по умолчанию
				m_TypeBuilder.DefineMethodOverride(custNameGetPropMthdBldr, inf);
				//устанавливаем этот метод
				custNamePropBldr.SetGetMethod(custNameGetPropMthdBldr);
			}
			//конец создания ГЕТ метода 
			//////////////////////////////////////////////////////////////////////////
			#endregion
		}


SET

void CreatePropertySetMetod(PropertyInfo property, PropertyBuilder custNamePropBldr, FieldBuilder fieldProperty, object redirectData)
		{
			#region SET_METOD
			//находим сет метод
			MethodInfo inf = property.GetSetMethod();
			//если нет то ищем в дублях
			if (inf == null)
			{
				if (m_doubleList != null && m_doubleList.ContainsKey(property.Name))
					inf = m_doubleList[property.Name].setMetod;
			}
			if (inf != null)
			{
				MethodBuilder custNameSetPropMthdBldr =
					m_TypeBuilder.DefineMethod("set_" + property.Name,
											   m_getSetAttr,
											   null,
											   new Type[] { property.PropertyType });
				ILGenerator custNameSetIL = custNameSetPropMthdBldr.GetILGenerator();
				//создаем локальную переменную

				custNameSetIL.Emit(OpCodes.Ldarg_0);			
				if (fieldProperty != null)
				{
					LocalBuilder loc = custNameSetIL.DeclareLocal(property.PropertyType);
					custNameSetIL.Emit(OpCodes.Ldfld, fieldProperty);
					custNameSetIL.Emit(OpCodes.Stloc_0);
					
					custNameSetIL.Emit(OpCodes.Ldarg_0);
					custNameSetIL.Emit(OpCodes.Ldarg_1);
					//присваем значение переменной класса								
					custNameSetIL.Emit(OpCodes.Stfld, fieldProperty);
					if (m_baseClass.GetInterface("iMatryoshkaCall") != null)
					{
						MethodInfo simpleShow = typeof(iMatryoshkaCall).GetMethod("CallPropertyChange");
						//CallPropertyChange(string propertyName, object CommandID = null, object oldData = null, object newData = null)
						if (simpleShow != null)
						{
							custNameSetIL.Emit(OpCodes.Ldarg_0);

							custNameSetIL.Emit(OpCodes.Ldstr, property.Name);
														
							custNameSetIL.Emit(OpCodes.Ldc_I4_0);
							custNameSetIL.Emit(OpCodes.Box, typeof(int));

							custNameSetIL.Emit(OpCodes.Ldloc_0);
							custNameSetIL.Emit(OpCodes.Box, property.PropertyType);

							custNameSetIL.Emit(OpCodes.Ldarg_0);
							custNameSetIL.Emit(OpCodes.Ldfld, fieldProperty);
							custNameSetIL.Emit(OpCodes.Box, property.PropertyType);						
							
							custNameSetIL.Emit(OpCodes.Callvirt, simpleShow);
						}
					}
			
				}	
				custNameSetIL.Emit(OpCodes.Ret);
				custNamePropBldr.SetSetMethod(custNameSetPropMthdBldr);
				m_TypeBuilder.DefineMethodOverride(custNameSetPropMthdBldr, inf);
			}
			#endregion
		}


Из текущих возможностей:

Формирование динамических классов-объектов на базе переданных: базового класса и списка интерфейсов, возможность сохранить эти объекты в отдельную библиотеку (dll), через BuilderClassesPropertyAttribyte наследника Attribute можно задавать у встроенных параметров-объектов различное наследование и поведение. Формирование и инициализация объектов-классов производиться с множеством вложенных объектов.

Планирую в будущем:

Дать возможность формировать объекты от нескольких классов и интерфейсов.Очень мне уж не хватало после С++ этого.

Комментарии (0)

© Habrahabr.ru