这几天研究了一下Linq,C# 3.0中的“扩展方法”特性为IEnumerable<T>增加了诸如Where、Select等查询方法,这使得“语言集成查询”成为顺其自然的事情。而C#3.0中Linq的实现也是建立在C#2.0的匿名委托的特性之上。
今天,我尝试在C#2.0中使用匿名委托模拟C#3.0中Where、Select等查询方法的实现。我将所有的查询方法作为静态方法在
GenericHepler静态类中实现。
之前,我们先定义泛型委托:
public
delegate
TResult Func
<
T, TResult
>
(T source);
这个委托在后面的实现中需要用到。
作为基础,首先,我们需要实现ForSpecification方法,该方法的含义是:对集合中满足指定条件的元素执行指定方法调用。
///
<summary>
///
ForSpecification 对集合中满足predicate条件的元素执行action。如果没有条件,predicate传入null。
///
</summary>
public
static
void
ForSpecification
<
TSource
>
(IEnumerable
<
TSource
>
collection, Action
<
TSource
>
action, Predicate
<
TSource
>
predicate)
{
if
(predicate
==
null
)
{
foreach (TSource obj in
collection)
{
action(obj);
}
return
;
}
foreach
(TSource obj
in
collection)
{
if
(predicate(obj))
{
action(obj);
}
}
}
有了ForSpecification的实现,我们就可以在其基础上实现ForEach和ForFirstSpecification:
#region
ForEach
///
<summary>
///
ForEach 对集合中的每个元素执行action。
///
</summary>
public
static
void
ForEach
<
TSource
>
(IEnumerable
<
TSource
>
collection, Action
<
TSource
>
action)
{
GenericHepler.ForSpecification
<
TSource
>
(collection, action,
null
);
}
#endregion
#region
ForFirstSpecification
///
<summary>
///
ForSpecification 对集合中第一个满足predicate条件的元素执行action。如果没有条件,predicate传入null。
///
</summary>
public
static
void
ForFirstSpecification
<
TSource
>
(IEnumerable
<
TSource
>
collection, Action
<
TSource
>
action, Predicate
<
TSource
>
predicate)
{
if
(predicate
==
null
)
{
foreach (TSource obj in
collection)
{
action(obj);
break
;
}
}
else
{
foreach
(TSource obj
in
collection)
{
if
(predicate(obj))
{
action(obj);
break
;
}
}
}
}
#endregion
有了ForSpecification,我们就可以实现查询方法Where:
#region
Where
///
<summary>
///
Where 从集合中选取符合条件的元素
///
</summary>
public
static
IList
<
TSource
>
Where
<
TSource
>
(IEnumerable
<
TSource
>
source, Predicate
<
TSource
>
predicate)
{www.elivn.com
IList
<
TSource
>
list
=
new
List
<
TSource
>
();
GenericHepler.ForSpecification(source,
delegate
(TSource ele) { list.Add(ele); } , predicate);
return
list;
}
#endregion
对于C#3.0中的Select方法,其实现需要匿名类型的支持,而C#2.0中不支持匿名类型,所以,我用泛型来代替。我使用ConvertSpecification来模拟Select实现:
#region
ConvertSpecification
///
<summary>
///
ConvertSpecification 将source中的符合predicate条件元素转换为TResult类型
///
</summary>
public
static
IList
<
TResult
>
ConvertSpecification
<
TSource, TResult
>
(IEnumerable
<
TSource
>
source, Func
<
TSource, TResult
>
converter, Predicate
<
TSource
>
predicate)
{
IList
<
TResult
>
list
=
new
List
<
TResult
>
();
GenericHepler.ForSpecification
<
TSource
>
(source,
delegate
(TSource ele) { list.Add(converter(ele)); } ,predicate);
return
list;
}
#endregion
converter委托用于从
TSource类型对象构造TResult类型的对象。
有了ConvertSpecification实现,我们就可以在其上继续实现ConvertAll和ConvertFirstSpecification:
#region
ConvertAll
///
<summary>
///
ConvertAll 将source中的每个元素转换为TResult类型
///
</summary>
public
static
IList
<
TResult
>
ConvertAll
<
TSource, TResult
>
(IEnumerable
<
TSource
>
source, Func
<
TSource, TResult
>
converter)
{
return
GenericHepler.ConvertSpecification
<
TSource, TResult
>
(source, converter,
null
);
}
#endregion
#region
ConvertFirstSpecification
///
<summary>
///
ConvertSpecification 将source中的符合predicate条件的第一个元素转换为TResult类型
///
</summary>
public
static
TResult ConvertFirstSpecification
<
TSource, TResult
>
(IEnumerable
<
TSource
>
source, Func
<
TSource, TResult
>
converter, Predicate
<
TSource
>
predicate)
{
TSource target
=
GenericHepler.GetFirstSpecification
<
TSource
>
(source, predicate);
if
(target
==
null
)
{
return
default
(TResult);
}
return
converter(target);
}
#endregion
有了上面的基础,我们还可以实现ContainsSpecification方法:
#region
ContainsSpecification
///
<summary>
///
ContainsSpecification 集合中是否包含满足predicate条件的元素。
///
</summary>
public
static
bool
ContainsSpecification
<
TSource
>
(IEnumerable
<
TSource
>
source, Predicate
<
TSource
>
predicate,
out
TSource specification)
{
specification
=
default
(TSource);
foreach
(TSource element
in
source)
{
if
(predicate(element))
{
specification
=
element;
return
true
;
}
}
return
false
;
}
#endregion
#region
ContainsSpecification
///
<summary>
///
ContainsSpecification 集合中是否包含满足predicate条件的元素。
///
</summary>
public
static
bool
ContainsSpecification
<
TSource
>
(IEnumerable
<
TSource
>
source, Predicate
<
TSource
>
predicate)
{
TSource specification;
return
GenericHepler.ContainsSpecification
<
TSource
>
(source, predicate,
out
specification);
}
#endregion
代码中的注释已经将各个方法的用途说得非常清楚,下面我们举两个例子来看看如何使用它们以发挥它们的威力!
例子一:比如,我们要从当前玩家(IPlayer)列表中找出所有年龄大于30岁的玩家的ID,通常这样做:
public
IList
<
string
>
GetOldPlayer()
{
IList
<
string
>
results
=
new
List
<
string
>
();
foreach
(IPlayer player
in
this
.playerList)
{
if
(player.Age
>
30
)
{
results.Add(player.ID);
}
}
return
results;
}
如果使用上面我们封装的API,则可以非常简单地达到目的:
public
IList
<
string
>
GetOldPlayer()
{
return
GenericHepler.ConvertSpecification
<
IPlayer,
string
>
(
this
.playerList,
delegate
(IPlayer player) {
return
player.ID; } ,
delegate
(IPlayer player) {
return
player.Age
>
30
});
}
一句搞定。
例子二:我们要从当前的玩家字典(Dictionary)中取出所有ID不是指定集合中的ID的其它玩家列表。
通常,我们可以这样做:
public
IList
<
IPlayer
>
GetPartners(
params
string
[] excludedUserIDs)
{
IList
<
IPlayer
>
partnersList
=
new
List
<
IPlayer
>
();
foreach
(
string
userID
in
this
.dicPlayers.Keys)
{
bool
exclude
=
false
;
foreach
(
string
excludedUser
in
excludedUserIDs)
{
if
(userID
==
excludedUser)
{
exclude
=
true
;
break
;
}
}
if
(
!
exclude)
{
partnersList.Add(
this
.dicPlayers[userID]);
}
}
return
partnersList;
}
使用上面我们封装的API,则非常简单:
public
IList
<
IPlayer
>
GetPartners(
params
string
[] excludedUserIDs)
{
return
GenericHepler.Where
<
IPlayer
>
(
this
.dicPlayers.Values,
delegate
(IPlayer player) {
return
!
GenericHepler.ContainsSpecification
<
string
>
(excludedUserIDs,
delegate
(
string
id) {
return
id
==
player.UserID; }); });
}
灵活地使用这些API,我们可以非常简洁地操作集合中的元素。
最后给出GenericHepler类的源码下载,其中还包含了几个未介绍的实用的API。