给DataTable添加类Linq操作

即将要过去的这一周,工作的一部分内容是写报表的打印方法。打印方法最重要的部分,是给报表绑定数据源,在这个过程中需要对DataTable做大量的处理。但是,在处理DataTable的时候,只能使用基本的for-if-else处理方式,而不能使用类似.Net中集合类原生支持的Linq操作。作为一个深入体验过Linq的简洁特性的人,这一点就非常心塞了。后来,我想到,何不使用扩展方法来扩展DataTable类,使其能够支持SelectWhereMax等类似的操作呢?
由于是参照.Net集合类中的类似方法,因此起初我也力求百分百的还原,包括方法的参数。以Where方法为例,最初的版本我实现了如下签名的方法。

1
2
3
public static DataTable Where(this DataTable table,Func<DataRow,bool> predicate){
        //code here
}

调用方式为

DataTable _table=table.Where(r=>(string)r["name"] == "maslke");

这个方法能够达到预定的目的。但是,一个比较让人在意的问题是,没有办法验证参数的合法性。即,编译器无法知道使用者调用的字段是否在DataTable中存在。后来,经过考虑之后,将方法签名改成了如下形式:

public static DataTable Where<T>(this DataTable table,string name, Func<T, bool> predicate)

调用方式为

DataTable _table=table.Where<string>("name",r=>r == "maslke");

虽然这种方式相比Net集合类的Where方法,签名多了一个字符串形参,方法本身也变成了一个泛型方法,略显繁琐,但是能够对参数的合法性进行判断。综合取舍之后,还是决定采用这种方式。
决定了这一点之后,下面的工作就顺利的多了。根据实际的需求,在Where方法之后,相继实现了SelectMaxMinAllAnyForEachCount方法。
但是在实现Sum方法的时候,却遇到了些麻烦。按照我的设想,我写下了如下的代码来实现Sum方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static T Sum<T>(this DataTable table, Func<string> selector)
        {
            if (table == null) throw new ArgumentNullException("table");
            if (selector == null) throw new ArgumentNullException("selector");
            string name=selector();
            DataColumnCollection columns=table.Columns;
            if(!columns.Contains(name)) throw new ArgumentException("invalid selector");
            if(columns[name].DataType != typeof(T)) throw new ArgumentException("T");
            T sum = default(T);
            for (int i = 0; i < table.Rows.Count; i++) {
                sum += (T)table.Rows[i][name];
            }
            return sum;
        }

但是,上面的这段代码编译通不过,编译器会报"operator '+=' can't be applied to operands of type of 'T' and 'T' "。起初,我百思不得其解,一直以为一定有一种方式可以实现类似的操作,只不过我不知道而已。后来,在「CLR Via C#」书中找到了答案,这是由于CLR功能限制导致,它不支持将+=等运算符用于泛型数据类型。于是只好放弃,另外实现了下面的两个方法来替代。

public static int Sum(this DataTable table, Func<DataTable, IEnumerable<int>> selector);
public static double Sum(this DataTable table, Func<DataTable, IEnumerable<double>> selector);

暂时第一步的工作,从周五写到了现在,算是暂告一段路。下面的计划是,打算重构一下这个类,只保留必要的操作,将那些功能上重复或是不重要的操作都去掉,算是从繁到简吧。再有,就是做一下性能测试了。
虽然代码写的很渣,但我还是将它放到了github上,链接在此:LinqToDataTable

2014/12/15

后来,在重新规划这个类的时候,对Sum函数又有了新的看法。在上面的版本中,参数繁琐,实质是绕了远路。这种实现方式,其实就是table.Select<T>(name).Sum()。 好一些的实现方式为

public static int Sum(this DataTable table, Func<int, string> selector);
public static double Sum(this DataTable table, Func<double, string> selector);

需要注意的是,在调用的时候我们需要手动指定参数的类型,比如table.Sum((int r)=>"age")。如果不指定参数类型,比如table.Sum(r=>"age"),在这种情况下编译器无法成功推断出我们到底要调用哪个函数table.Sum((int r) =>"age")或是table.Sum((double r)=>"age"),我们只好手动的指定一下了。

2014-12-13 22:50Coding