10 分钟揭秘 LINQ
在10/15分钟内了解C#到LINQ的演变过程
引言
许多优秀的文章都出色地解释了LINQ。从语法到概念再到项目,LINQ的内容已经非常丰富。本文的目的不是重复/重组这些材料。有关详细信息,请参阅好书列表。我个人最喜欢的是这本小小的口袋参考书。
本文的概念基于这个短视频。本文将以图解的形式展示我们如何发展到LINQ,以及C#从1.0到3.5的演变过程。LINQ是新事物,还是仅仅是对旧的丑陋代码的优雅语法?在本文中,我们将用不到10/15分钟的时间,回顾C#到LINQ的演变历程。让我们开始吧,揭开LINQ的神秘面纱。
我们将需要一些基础代码。我们将使用如下的City
类
class City
{
private string _name;
private string _state;
public City(string name, string state)
{
this._name = name;
this._state = state;
}
public string Name
{
get
{
return _name;
}
}
public string State
{
get
{
return _state;
}
}
}
让我们创建一个城市集合,然后使用foreach
遍历这个集合,如下所示
List<City> cities = new List<City>();
City c = new City("Santa Ana", "CA");
City c1 = new City("Irvine", "CA");
City c2 = new City("Bloomington", "IN");
cities.Add(c);
cities.Add(c1);
cities.Add(c2);
foreach (City tempCity in cities)
{
Console.WriteLine("City Name : {0} and State : {1}",tempCity.Name, tempCity.State);
}
让我们根据某些条件过滤这个集合。比如,我们只想显示加州的城市。一个非常简单的解决方案是在foreach
循环中添加一个if
条件,如下所示
if (tempCity.State == "CA")
Console.WriteLine("City Name : {0} and State : {1}", tempCity.Name, tempCity.State);
这是一个不错的解决方案。但是这段代码的问题在于“紧耦合”。C# 1.0为这个问题提供了一种基于委托的可组合解决方案。以下是重构后的代码版本
步骤 1
delegate bool IsParticularState(City c);
第二步
static bool IsCalifornia(City c)
{
return c.State == "CA" ? true : false;
}
步骤 3
PrintCityInfo(cities, new IsParticularState(IsCalifornia));
步骤 4
static void PrintCityInfo(List _cities, IsParticularState filter)
{
foreach (City localCity in _cities)
{
if (filter(localCity))
{
Console.WriteLine("City Name : {0} and State : {1}",
localCity.Name, localCity.State);
}
}
}
如步骤1所示,我们添加了一个delegate IsParticularState
。步骤2是这个delegate
的目标方法。步骤3是重构后的foreach
循环调用,它接收一个delegate
和cities
集合作为输入参数。步骤4将输出city name
和state
。
注意这里的filter
代替了简单的if
条件。filter(localCity)
是一个delegate
调用。通过这样做,我们将过滤逻辑解耦到了一个独立的方法中
static bool IsCalifornia(City c)
对于实现简单的过滤逻辑来说,这仍然是太多的代码。如果我们不必添加额外的过滤方法,那岂不是更好?是的,这正是C# 2.0匿名方法的作用所在。因此,上面的代码可以重构为如下形式
PrintCityInfo(cities,delegate(City ctemp){return (ctemp.State == "CA"?true:false);});
注意内联的delegate
和匿名方法。这是C# 2.0的一个特性——匿名,因为这个方法没有名称。
这是一个不错的改进,但仍然需要delegate
代码。如果我们想使用C# 3.0来改进这段代码呢?我们可以使用lambda表达式代替匿名方法,如下所示
PrintCityInfo(cities,ctemp=>ctemp.State=="CA");
这段代码难道不优雅吗?从C# 1.0中的7+行代码、delegate
和目标方法调用,到C# 3.0中不到半行的代码。这就是lambda表达式的强大之处。底层编译器为我们处理了所有的繁重工作。Lambda表达式是隐式变量和匿名方法的结合。在我们的例子中,ctemp
是一个隐式变量,而ctemp.State=="CA"
是一个匿名方法。
现在,LINQ登场了。如果我们想对最终结果应用多个过滤条件,或者想根据预定义的顺序(如city name
)对输出进行排序呢?在没有LINQ的情况下,我们也可以做到。但LINQ提供了一个优雅的解决方案,如下所示
PrintCityInfousingLINQ(from ctemp in cities
where ctemp.State=="CA"
select ctemp);
static void PrintCityInfousingLINQ(IEnumerable _cities)
{
foreach (City localCity in _cities)
{
Console.WriteLine("City Name : {0} and State : {1}",
localCity.Name, localCity.State);
}
}
在这里,我们定义了一个新方法PrintCityInfousingLINQ
。它与PrintCityInfo
方法相同。PrintCityInfousingLINQ
接收一个enumerator
作为输入参数,而不是接收一个集合和delegate
。另一个重要因素是泛型。泛型增加了类型安全性,并提供了所有其他有据可查的好处。
让我们按city name
对最终结果进行排序。这在TSQL中是一个典型的order by
子句。使用LINQ,可以这样写
PrintCityInfousingLINQ(from ctemp in cities
where ctemp.State=="CA"
orderby ctemp.Name
select ctemp);
没有orderby
子句,圣安娜将是第一个结果。有了order by
子句,尔湾将是第一个记录。
结论
从C# 1.0中简单的if
条件到C# 3.0中的LINQ,存在着逻辑和语法的连续演进。想象一下用……
from ctemp in cities
where ctemp.State=="CA"
orderby ctemp.Name
select ctemp;
……if
条件或delegate
来编写。LINQ在语法方面带来了急需的改进,并且支持SQL、XML和对象。有了LINQ,意图与语言语法更紧密地匹配。
在一个研究生院空旷的停车场午夜辩论中,有人说了这样一句话——“任何编程语言的力量都在于它能够非常轻松地说出非常复杂的事情。就像“树林可爱,幽暗又深邃,但我有诺言要遵守……””
LINQ是朝着这个方向迈出的又一步。你觉得呢?
更新:在向来自传统过程式编程背景的人解释LINQ时,我使用了这个例子来解释Lambda基础。希望这对其他人也有用——请告诉我——LINQPad用于测试此代码
delegate bool diseven(int i);
void Main(){
int[] mi = new int[]{2,3,4,5,1};
"Count Even Numbers".Dump();
diseven di =isEven;
int counter=0;
foreach(int i in mi)
if(di(i))
counter++;
counter.Dump("using Traditional Loop");
mi.Count(delegate(int i){
return i%2==0;
}).Dump("Using delegate");
Func<int,bool> fm = i =>i%2==0;
mi.Count(i=>fm(i)).Dump("using built in delegate Func");
mi.Count(i=>i%2==0).Dump("using Lambda");
}
bool isEven(int i)
{
return i%2==0;
}
历史
- 2008年6月26日:初始帖子
- 2014年6月22日:包含Lambda示例