Enumerable.Cast<T>用于将
IEnumerable转换为泛型版本IEnumerable<T>。转换后可尽情享用Enumerable的其它方法(如Where、Select),给我们的编码带来极大便利。
但MSDN中仅给出一个转换ArrayList的例子,很多人看了感觉现在都在用List<T>,还有谁会用ArrayList,Cast<T>没多少用处,除非处理一些之前遗留的一些代码。
其实Cast<T>并非如此简单,它可以用在很多地方。
先看MSDN中举的例子吧:
1
System.Collections.ArrayList fruits
=
new
System.Collections.ArrayList();
2
fruits.Add(
"
apple
"
);
3
fruits.Add(
"
mango
"
);
4
5
IEnumerable
<
string
>
query
=
fruits.Cast
<
string
>
();
6
foreach
(
string
fruit
in
query) Console.WriteLine(fruit);
这个例子比较简单,很容易理解。
同样.Net 1.x中的其它几个集合类也可如上使用,如Array、非泛型版的List...
打断,有没有非泛型版的List?我没太用过.Net 1.x,不太清楚,不过窗体控件中是有个List控件(ASP.Net)和一个ListView控件(WinForm)。
就以ListView为例子吧,ListView控件可以包含很多项,也可以说是一个集合,就让我们来看看它的Items属性吧!
1
public
class
ListView : Control
2
{
3
4
public
ListView.ListViewItemCollection Items {
get
; }
5
6
public
class
ListViewItemCollection : IList, ICollection, IEnumerable { }
7
8
}
ListView的Items类型是ListView.ListViewItemCollection,这个ListViewItemCollection实现了IEnumerable。
ListView.Items正是一个非泛型的集合,因此可以应用Cast<T>。
以下代码假定 listBox 数据绑定在一个Employee的集合上:
1
int
count
=
listBox.Items.Cast
<
Employee
>
().Count();
2
bool
b
=
listBox.Items.Cast
<
Employee
>
().Any(e
=>
e.FirstName
==
"
Bob
"
);
(当然,如果有Employee的集合的引用,就不用Cast了,这里只是示例)
同样Cast<T>可以用在ComboBox、DataGridView、TreeNode上:
1
//
ComboBox
2
var v1
=
comboBox.Items.Cast
<
People
>
();
3
//
DataGridView
4
var v2
=
dataGridView.SelectedRows.Cast
<
DataGridViewRow
>
();
5
var v3
=
dataGridView.SelectedColumns.Cast
<
DataGridViewColumn
>
();
6
var v4
=
dataGridView.SelectedCells.Cast
<
DataGridViewCell
>
();
7
//
TreeNode
8
var v5
=
treeNode.Nodes.Cast
<
TreeNode
>
();
这几个应用中应该第 4 行的应用最多,获取选中行是DataGridView使用最频繁的操作之一。
试看下面代码:
1
//
计算平均年龄
2
int
age
=
dataGridView.SelectedRows.Cast
<
Employee
>
().Average(p
=>
p.Age);
3
//
统计所在城市
4
string
[] cities
=
dataGridView.SelectedRows.Cast
<
Employee
>
().Select(p
=>
p.City).Distinct();
用了Cast<T>,我们的代码很精简。
Cast<T>甚至还可以用在所有控件的基类Control上,它的Controls属性也是非泛型的!
1
//
Control
2
var v6
=
control.Controls.Cast
<
Control
>
();
看来Cast<T>好像是为 Control 准备,Control 类和Control 的派生类多处使用了非泛型。
可现在都用vs2008(甚至vs2010)了,那为什么WinForm的窗体控件还用非泛型,太落后了吧!!!
确实如此,WinForm对泛型控件(Control)的支持上存在很大问题。
虽然可以定义泛型控件,也可以使用,可以运行。但会有很多麻烦的,比如窗体设计器没法显示...
那只好使用非泛型的了,好在我们有Cast<T>!
再来看看Cast<T>对继承的支持,我们定义两个类A和B,B继承自A,如下:
1
public
class
A { }
2
public
class
B : A { }
来试试如下类型转换操作:
1
//
子类集合
2
B[] bb
=
new
B[] {
new
B(),
new
B(),
new
B(),
new
B() };
3
//
转换成父类
4
A[] aa
=
bb.Cast
<
A
>
().ToArray();
5
//
再转回子类
6
B[] bb2
=
aa.Cast
<
B
>
().ToArray();
以上三个操作都可编译并运行通过,修改下再试:
1
A[] aa
=
new
A[] {
new
A(),
new
A(),
new
B() };
2
B[] bb3
=
aa2.Cast
<
B
>
().ToArray();
这次不行了,将父类cast为子类可不是随意的:
不过我们有解决办法,我们使用Enumerable.OfType<T>,是Cast<T>的亲兄弟,如下使用:
1
B[] bb
=
aa.OfType
<
B
>
().ToArray();
看了上面的,总感觉Cast<T>的内部只是执行了(T)enumerator.Current这样一个简单操作,让我们再用 int 和 double 转换验证一下:
1
int
i
=
(
int
)
1.001
;
2
double
d
=
(
double
)
10
;
3
4
int
[] ints1
=
new
int
[] {
1
,
2
,
3
,
4
,
5
};
5
double
[] ds1
=
ints1.Cast
<
double
>
().ToArray();
6
7
double
[] nums1
=
new
double
[] {
1.0001
,
2.0003
,
3.001
,
3.9997
,
4.002
};
8
int
[] nums2
=
nums1.Cast
<
int
>
().ToArray();
1、2行为强制类型转换,没问题。(当然第2行的(double)可以省略。)
第 5 行试图将整数集合转换为double集合,运行时会报错:
第7行也会报同样的错误。看来Cast<T>内部并非只是简单转换!
用Reflect反编译了一下,用到了下面这个类:
反编译后代码比较乱,加上本人水平有限,也没弄明白,还是把这个难题留给园子里的高手吧!
总结:
1. Cast<T>可广泛应用在WinForm的控件上;
2. 有类继承的集合转换上,建议用OfType<T>;
3. Cast<T>不能理解成简单类型转换。
希望:
1. 大家把自己知道的Cast<T>的扩展写在回复中,分享给大家。
2. 高手们能给出Cast<T>内部实现的简单描述,让大家用的明明白白。
本人系列文章《
c#扩展方法奇思妙用》,敬请关注!