vendredi 24 mars 2017

How to save and run dynamic generic methods

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