4

Below we have a list for a mock database call and a little program that takes that list and converts it do a DataTable. In this example I'm using a variable for the column name to access that column's values and get the average. However, I made a call to the Field method and gave it the type of int. It doesn't appear to be possible to pass a variable to the generic Field method. Is there another way to access a DataTable's column values and return something like an average without knowing the type of the column until runtime?

 public class Icd
{
    public int ConditionCode { get; set; }
    public string ConditionName { get; set; }

    public static List<Icd> GetIcdList()
    {
        return new List<Icd>()
        {
            new Icd() { ConditionCode = 111, ConditionName = "Condition 1" },
            new Icd() { ConditionCode = 222, ConditionName = "Condition 2" },
        };
    }
}
var icdList = Icd.GetIcdList();
var columnName = "ConditionCode";
DataTable dt = new DataTable();

dt =  icdList.ToList().ListToDataTable();
var avg = dt.AsEnumerable().Where(x => x[columnName] != DBNull.Value)
                            //possible to pass a variable to the field method?
                           .Average(x => x.Field<int>(columnName));
Console.WriteLine(avg); //<-- correct answer

Update: I tried to add:

Type t = typeof(int)

and do

x => x.Field<t>(columnName)

but that gives me the error:

The type or namespace 't' could not be found

ListToDataTable helper method:

public static DataTable ListToDataTable<T>(this IList<T> data)
{
    DataTable dt = new DataTable();
    PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
    for (int i = 0; i < props.Count; i++)
    {
        PropertyDescriptor prop = props[i];
        dt.Columns.Add(prop.Name, prop.PropertyType);
    }
    object[] values = new object[props.Count];
    foreach (T t in data)
    {
        for (int i = 0; i < values.Length; i++)
        {
            values[i] = props[i].GetValue(t);
        }
        dt.Rows.Add(values);
    }
    return dt;
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
wootscootinboogie
  • 8,461
  • 33
  • 112
  • 197

2 Answers2

2

I think you may use the dynamic type here.

For example:

var avg = dt.AsEnumerable().Where(x => x[columnName] != DBNull.Value)
                            //possible to pass a variable to the field method?
                           .Average(x => x.Field<dynamic>(columnName));

I have done minimal testing, and it seems to work. Others are welcome to comment on this.

Cheers

Luc Morin
  • 5,302
  • 20
  • 39
  • I thought the same thing :) I'm going to write a `TryGetAverage` method that will call `GetAverage` if the try method is a float, double, int, etc. type. – wootscootinboogie Mar 19 '14 at 19:35
1

Generic types must be known at compile time. If you want to make that variable you need to use reflection:

// Get the method information
MethodInfo method = typeof(T).GetMethod("Field");

// here hardcoded for int, but you could use any type
var types = new Type[] {typeof(int)};

// Create a generic instance of that method
MethodInfo genericMethod = method.MakeGenericMethod(types);

var avg = dt.AsEnumerable().Where(x => x[columnName] != DBNull.Value)
                        // Use the generic method with target x and parameter columnName
                       .Average(x => genericMethod.Invoke(x, columnName);
Kenneth
  • 28,294
  • 6
  • 61
  • 84
  • Not sure how much reflection would actually slow things down, but what I think I'm going to do is cast the column type at dynamic and use a `TryGetAverage` that will execute if all of the values are ints, doubles, floats, etc. – wootscootinboogie Mar 19 '14 at 19:27