I am trying to run a generic method with a type I will only know at runtime. I can make this call using reflection but the performance is terrible. My research shows that you can create a delegate using reflection and then all subsequent runs will be much faster.
I would like to save these reflected delegates into a dictionary. My program would then check to see if the method was already defined and if not then it could create a new delegate.
My program has two main methods. The first one takes in a type of a class that will hold the results from a DataSet. The DataSet has multiple DataTables which need to be bound to properties. These properties are each a list of an object with the correct properties to hold the DataTable columns.
I realize that even if I make this work it might be too cleaver for long term support but it is now a puzzle that I must find an answer to :)
public static class Common
{
//http://ift.tt/2nZeqlo
private static Dictionary<Type, Delegate> _toListCache = new Dictionary<Type, Delegate>();
#region Data bind methods
/// <summary>
/// Extension Method to bind DataSet to model properties
/// </summary>
/// <typeparam name="T">Type of model</typeparam>
public static T ToModel<T>(DataSet ds)
{
//for this to function we need multiple tables with the first table being the DataSet ID
////EXAMPLE return value:
////Index DataTableName
////0 DataSetInfo
////1 ContractInfo
////2 InvoiceInfo
if (ds.Tables.Count < 2) throw new DataException("Invalide DataSet format");
var ob = Activator.CreateInstance<T>();
PropertyInfo[] properties = typeof(T).GetProperties();
DataTable setIds = ds.Tables[0];
foreach (DataRow table in setIds.Rows)
{
//skip the first ID table
if (table["DataTableName"].ToString() == "DataSetInfo") continue;
int tableIndex = (int)table["Index"];
string tableName = table["DataTableName"].ToString();
//check if property exists matching the table name
PropertyInfo property = ob.GetType().GetProperty(tableName);
if (property != null)
{
//get list type
Type propertyType = property.GetType();
if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>))
{
//Get type T out of List<T>
Type listType = propertyType.GetGenericArguments()[0];
DataTable dt = ds.Tables[tableIndex];
//call ToList with a dynamic type
if (_toListCache.ContainsKey(listType))
{
//delegate has already been defined
var list = _toListCache[listType].DynamicInvoke(dt);
property.SetValue(ob, list);
}
else
{
MethodInfo createDelegate = typeof(Common).GetMethod("AddDelegateToCache").MakeGenericMethod(listType);
var func = createDelegate.Invoke(null, new object[] { "ToList" });
var list = _toListCache[listType](dt);
property.SetValue(ob, list);
}
}
else
throw new TypeLoadException(string.Format("Property {0} is not a type of List<T>", tableName));
}
else
throw new TypeLoadException(string.Format("Property {0} does not exist.", tableName));
}
return ob;
}
/// <summary>
/// Extension Method to bind DataTable to model properties
/// </summary>
/// <typeparam name="T">Type of model</typeparam>
public static List<T> ToList<T>(DataTable dt)
{
List<string> columns = (from DataColumn dc in dt.Columns select dc.ColumnName).ToList();
FieldInfo[] fields = typeof(T).GetFields();
PropertyInfo[] properties = typeof(T).GetProperties();
List<T> lst = new List<T>();
foreach (DataRow dr in dt.Rows)
{
var ob = Activator.CreateInstance<T>();
////for performance we will only look at properties
////sets field values that match a column in the DataTable
//foreach (var fieldInfo in fields.Where(fieldInfo => columns.Contains(fieldInfo.Name)))
// fieldInfo.SetValue(ob, !dr.IsNull(fieldInfo.Name) ? dr[fieldInfo.Name] : fieldInfo.FieldType.IsValueType ? Activator.CreateInstance(fieldInfo.FieldType) : null);
//sets property values that match a column in the DataTable
foreach (var propertyInfo in properties.Where(propertyInfo => columns.Contains(propertyInfo.Name)))
propertyInfo.SetValue(ob, !dr.IsNull(propertyInfo.Name) ? dr[propertyInfo.Name] : propertyInfo.PropertyType.IsValueType ? Activator.CreateInstance(propertyInfo.PropertyType) : null);
lst.Add(ob);
}
return lst;
}
#endregion
#region Generic delegate methods
public static Func<T, object, object> AddDelegateToCache<T>(string method) where T : class
{
MethodInfo genericMethod = typeof(Common).GetMethod("GenericMethod", new Type[] { typeof(T) });
Func<T, object, object> genericMethodFunc = GenericMethod<T>(genericMethod);
_toListCache.Add(typeof(T), genericMethodFunc);
return genericMethodFunc;
}
/// <summary>
/// Runs a generic method with a dynamic type
/// </summary>
/// <typeparam name="T">Return type</typeparam>
/// <param name="method">Target generic method</param>
static Func<T, object, object> GenericMethod<T>(MethodInfo method) where T : class
{
// First fetch the generic form
MethodInfo genericHelper = typeof(Common).GetMethod("GenericMethodHelper", BindingFlags.Static | BindingFlags.NonPublic);
// Now supply the type arguments
MethodInfo constructedHelper = genericHelper.MakeGenericMethod
(typeof(T), method.GetParameters()[0].ParameterType, method.ReturnType);
// Now call it. The null argument is because it’s a static method.
object ret = constructedHelper.Invoke(null, new object[] { method });
// Cast the result to the right kind of delegate and return it
return (Func<T, object, object>)ret;
}
/// <summary>
/// used to cast object to TParam and TReturn
/// This allows fast dynamic generic methods to run
/// </summary>
static Func<TTarget, object, object> GenericMethodHelper<TTarget, TParam, TReturn>(MethodInfo method)
where TTarget : class
{
// Convert the slow MethodInfo into a fast, strongly typed, open delegate
Func<TTarget, TParam, TReturn> func = (Func<TTarget, TParam, TReturn>)Delegate.CreateDelegate
(typeof(Func<TTarget, TParam, TReturn>), method);
// Now create a more weakly typed delegate which will call the strongly typed one
Func<TTarget, object, object> ret = (TTarget target, object param) => func(target, (TParam)param);
return ret;
}
#endregion
}
Aucun commentaire:
Enregistrer un commentaire