Лінь, рефлексія, атрибути, динамічні складання

Невеликий досвід, отриманий завдяки ліні

Роки три тому, я працював на одну фірму. Було нас 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;
}
}
}
/// <summary>
/// Зведений опис для CreatorDBWorkClasses
/ / / < /summary>
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<ColumnDB> mListColumn = new List<ColumnDB>();
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" +
" \"З "+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 + " \"З " + 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, зацікавившись, я почитав про нього докладніше. Є багато цікавих функцій у цього класу. Фактично завдяки йому можна повністю отримати всю інформацію по класу. Конструктор, реалізовані інтерфейси, властивості, змінні, функції… Повну інформацію. І я почав експериментувати з ним, і в підсумку, отримав:

Клас для роботи з типами класів<habracut/>
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

namespace SV.Tools
{
public delegate void AddProp(string name, string val);
/// <summary>
/// структура для дублюючих параметрів
/ / / < /summary>
public struct MetodInfoDouble
{
public MethodInfo getMetod;
public MethodInfo setMetod;
}
/// <summary>
/// клас з статік функціями для роботи з класами :( ну і описалово вийшло
/ / / < /summary>
public class ClassesTools
{
/// <summary>
/// отримання даних всіх порперти класу включаючи вкладення
/ / / < /summary>
/ / / < param name="ob"></param>
/ / / < param name="delegateProp"></param>
/ / / < param name="parentName"></param>
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();
/// <summary>
/// пошук інтерфесу за іменем
/ / / < /summary>
/ / / < param name="name">ім'я інтерфейсу</param>
/ / / < returns></returns>
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<Type> 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<Type> lst = new List<Type>();
count = t.Length;
for(int b =0; b < count; b++)
{
if (t[b].Namespace == Namespace)
lst.Add(t[b]);
}
return lst;
} 
}
return null;
}
/// <summary>
/// знаходить все нтерфейсы, включаючи вкладені, видаляє дублікати
/ / / < /summary>
/ / / < param name="tp"></param>
/ / / < param name="listInterfece"></param>
public static void GetAllInterfece(Type[] tp, ref List<Type> listInterfece)
{
if (tp == null)
return;
int count = tp.Length;
for (int a = 0; a < count; a++)
{
Type rezult = listInterfece.Find(
представник(Type typ)
{
return tp[a] == typ;
}
);
if (rezult == null)
listInterfece.Add(tp[a]);
Type[] t = tp[a].GetInterfaces();
GetAllInterfece(t, ref listInterfece);
}
}
/// <summary>
/// знаходить все нтерфейсы, включаючи вкладені
/ / / < /summary>
/ / / < param name="parentClass"></param>
/ / / < returns></returns>
public static List<Type> GetAllInterfece(Type parentClass)
{
List<Type> listClasses = new List<Type>();
GetAllInterfece(new Type[] { parentClass }, ref listClasses);
return listClasses;
}
/// <summary>
/// знаходить все нтерфейсы, включаючи вкладені, видаляє дублікати
/ / / < /summary>
/ / / < param name="parentClass"></param>
/ / / < param name="listInterfece"></param>
/ / / < returns></returns>
public static List<Type> GetAllInterfece(Type parentClass, List<Type> listInterfece)
{
List<Type> listClasses = new List<Type>();
GetAllInterfece(new Type[] { parentClass }, ref listClasses);
GetAllInterfece(listInterfece.ToArray(), ref listInterfece);
return RemoveDouble(listClasses, listInterfece);
}
/// <summary>
/// видаляє дублікати в списках
/ / / < /summary>
/ / / < param name="джерела">джерело</param>
/ / / < param name="editableList">список в якому уберуться зустрічаються значення sources</param>
/ / / < returns>возращаемое значення</returns>
public static List<Type> RemoveDouble(List<Type> sources, List<Type> editableList)
{
for (int a = editableList.Count - 1; a >= 0; a--)
{
if (sources.Find((Type t) => editableList[a] == t) != null)
editableList.RemoveAt(a);
}
return editableList;
}
/// <summary>
/// пошук параметра у всіх інтерфейсах типу
/ / / < /summary>
/ / / < param name="_class">тип класу</param>
/ / / < param name="propertyName">ім'я параметра</param>
/ / / < returns>знайдений параметр</returns>
/// <remarks>
/// тестова функція
/// </remarks>
public static PropertyInfo FindProperty(Type _class, string propertyName)
{
List<PropertyInfo> 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<Type> RemoveDouble(List<Type> property)
{
List<Type> retryList = new List<Type>();
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<PropertyInfo> RemoveDouble(List<PropertyInfo> property)
{
List<PropertyInfo> retryList = new List<PropertyInfo>();
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;
}
/// <summary>
/// отримує всі параметри за типом, з видаленням дублів
/ / / < /summary>
/ / / < param name="interfeceList">родидельский тип</param>
/ / / < returns>список параметрів</returns>
/// <remarks>
/// тестова функція
/// </remarks>
public static List<PropertyInfo> GetAllProperty(Type parent)
{
List<Type> allTypes = GetAllInterfece(parent);
List<PropertyInfo> allProperty = new List<PropertyInfo>();
if (allTypes != null)
{
int count = allTypes.Count;
for(int a =0; a < count; a++)
{
allProperty.AddRange(allTypes[a].GetProperties(/*BindingFlags.Default*/));
}
}
return RemoveDouble(allProperty);
}
/// <summary>
/// пошук по імені параметра
/ / / < /summary>
/ / / < param name="name">ім'я параметра</param>
/ / / < returns></returns>
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 (p != 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 p;
}
/// <summary>
/// формує списки метаданих на підставі переданих типів
/ / / < /summary>
/ / / < param name="interfeceList">список типів інтерфейсу\класу</param>
/ / / < param name="propertyInfo">список для заповнення</param>
/ / / < param name="memberInfo">список для заповнення</param>
/ / / < param name="fieldInfo">список для заповнення</param>
/ / / < param name="metodInfo">список для заповнення</param>
/ / / < param name="eventInfo">список для заповнення</param>
public static void GetInterfaceMetadata(List<Type> interfeceList, ref List<PropertyInfo> propertyInfo,
ref List<MemberInfo> memberInfo, ref List<FieldInfo> fieldInfo, ref List<MethodInfo> metodInfo, ref List<EventInfo> 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);
}

}
/// <summary>
/// функція знаходження пвоторяющихся пропертей
/ / / < /summary>
/ / / < param name="propertyInfoInterface"></param>
/ / / < returns></returns>
public static Dictionary<string, MetodInfoDouble> RemoveDoubleProperty(List<PropertyInfo> propertyInfoInterface)
{
if (propertyInfoInterface == null)
return null;
Словник<string, MetodInfoDouble> m_doubleList = new Dictionary<string, MetodInfoDouble>();
int count = propertyInfoInterface.Count - 1;
for (int a = count; a >= 0; a--)
{
List<PropertyInfo> 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\пПовторяющийся параметр з ім'ям: " + fnd[0].Name + "\r\пВ інтерфейси:\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;
}
/// <summary>
/// функція отримання даних та параметра по імені параметра
/ / / < /summary>
/ / / < param name="propertyName">ім'я параметра</param>
/// < param name="param">передані параметри</param>
/ / / < returns>дані, які можуть бути null якщо не найденн параметр або він нульовий</returns>
public static 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;
}
/// <summary>
/// встановлення даних параметр за іменем
/ / / < /summary>
/ / / < param name="propertyName">ім'я параметра</param>
/ / / < param name="newPropertyData">нове значення</param>
/// < param name="param">передані параметри</param>
/ / / < returns>false - якщо параметр був знайдений</returns>
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;
}


}
}


Тепер я міг отримати повністю всю інформацію по кожному об'єкту та навіть відправляти і отримувати дані параметри просто по імені параметра
/ / / < summary>
/// функція отримання даних та параметра по імені параметра
/ / / < /summary>
/ / / < param name="propertyName">ім'я параметра</param>
/// < param name="param">передані параметри</param>
/ / / < returns>дані, які можуть бути null якщо не найденн параметр або він нульовий</returns>
public static 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;
}
/// <summary>
/// встановлення даних параметр за іменем
/ / / < /summary>
/ / / < param name="propertyName">ім'я параметра</param>
/ / / < param name="newPropertyData">нове значення</param>
/// < param name="param">передані параметри</param>
/ / / < returns>false - якщо параметр був знайдений</returns>
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:<habracut/>
.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 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.