[Из песочницы] Лень, рефлексия, атрибуты, динамические сборки
Небольшой опыт, полученный благодаря лени
Года три назад, работал я на одну фирму. Было нас 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)
.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
Немного опишу проект
Формирование свойств объекта:
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
}
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 можно задавать у встроенных параметров-объектов различное наследование и поведение. Формирование и инициализация объектов-классов производиться с множеством вложенных объектов.
Планирую в будущем:
Дать возможность формировать объекты от нескольких классов и интерфейсов.Очень мне уж не хватало после С++ этого.