走进Linq--Linq横空出世篇

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

走进Linq--Linq横空出世篇
某日编程大师云游到某处,见一刚毕业不久学过两天C#和两天SQL的coder在那里发牢骚,为啥我要写这么多for,这么多if才能查询出我需要的数据,为啥我不能像SQL那样,发送一条命令告诉数据库我需要啥样的数据,它就给我返回来。

编程大师如是说:傻小子,像SQL那叫第四代编程语言,常存在于象牙塔和研究所里面的学究语言,还有个高雅的名字:函数编程。

它只需要你告诉它要什么,而不需要告诉它怎么做。

而你使用的C#语言属于命令式编程,你必须像发送命令一样一步步的告诉你的机器怎么做。

发牢骚的coder回了一句:不懂,我只是想不通,数据库能做这样的处理,为啥C#这么牛的语言不能呢。

编程大师心里想着:这是不可能的事情,因为C#它是强类型语言,)*&)(&)*)()*&%&%&^(后面省去200字)。

天色还未晚,编程大师就急匆匆的回家了,他心里一直记着那位发牢骚的coder的话:为什么不能,为什么不能。

晚上,编程大师做了一个梦,一个奇怪的梦,他的师傅“白眉”只说了三个字母:DSL。

编程大师想着,DSL,领域专用语言,师傅要对我说什么呢,难道和今天我遇见的事有关?上面这段文字是一段调侃,调节一下气氛,呵呵。

我觉得Linq就是一种DSL,在C#等常规语言上抽象起来的,面向数据处理领域的特定“语言”,当然,它的根基还是这些常规语言。

select,from,where,group等关键字本来只是在SQL里出现,现在把它们引入到C#这些常规编程语言中。

那C#等是如何做到的呢?是在CLR底层支持的么?不是。

既然“编译器”可以将C#编译成
MSIL,那为什么编译不能干更多一点事情?将这些为了领域编程而出现关键字编译成原始语法。

下面还是从实例来说明吧:
我们有一个图书类Book,先已经有一个填充有数据的Book集合,我们需要从这个集合里查找出单价小于50的书籍:
using System;
///<summary>
///图书类
///</summary>
public class Book
{
///<summary>
///图书名称
///</summary>
public string Title { get; set; }
///<summary>
///单价
///</summary>
public float Price { get; set; }
///<summary>
///作者
///</summary>
public string Author { get; set; }
///<summary>
/// ISBN号
///</summary>
public string ISBN { get; set; }
}
如是我可以写这样的代码:
public static class Helper
{
public static IList<Book> SearchBookByPrice()
{
IList<Book> books = //..//初始化一个Book集合 IList<Book> results = new List<Book>();
foreach(Book book in books)
{
if(book.Price < 50)
{
results.Add(book);
}
}
return results;
}
}
现在是根据单价查找,那如果我要按照书籍名称查找或者按照作者查找怎么办?那只有重写这个方法了。

但是你想想,我们的查找条件到最后只不过是一个true或者false,只要if()里面的表达式为true我们就将其添加到返回结果的集合中,我才不管里面的表达式详细的是什么呢,ok,那这样我们就可以进一步改进这个方法了:
public static class Helper
{
public delegate bool Condtion(Book book);
public static IList<Book> SearchBook(Condtion condition)
{
IList<Book> books = //..//初始化一个Book集合
IList < Book > results = new List<Book>();
foreach (Book book in books)
{
if (condition(book))
{
results.Add(book);
}
}
return results;
}
}
看看我们如何调用改进后的方法:
public bool ConditionTitle(Book book)
{
return book.Title == "yuyi";
}
IList<Book> results = Helper.SearchBook(new Condition(ConditionTitl e));
我们将查询条件使用委托解决了,只要传递一个接收Book作为参数,返回bool值的方法进去就可以查询满足条件的书籍了,但是,为了这个委托,我们还得先定义一个新方法,然后。

太麻烦了,为此C# 2.0为我们提供了匿名方法,专门针对这些只有“一句话方法”:
IList<Book> results = Helper.SearchBook(delegate(Book book) { retur
n book.Title == "yuyi"; });
代码是减少不少,可这种写法还是不“人性化”,这还是一种人向计算机妥协的结果,才产生了这种怪异的写法,如是C# 3.0给我们提供了Lambda表达式:
IList<Book> results = Helper.SearchBook(book => book.Title == "yuyi ");
代码更短了,写法也越来越向人类语言靠近了(这也许就是计算机语言的发展历史,随着计算机越来越智能,总有一天必会是计算机向人类妥协)。

不过这里还有一点不爽,每一次调用这个查找方法都要带Helper,要是IList<Book>自己就这个方法该多好。

这个想法很好,C# 3.0里还为我们提供了扩展方法:
public static class Helper
{
public delegate bool Condtion(Book book);
public static IList<Book> Where(this IList<Book> books,Condtio
n condition)
{
IList < Book > results = new List<Book>();
foreach (Book book in books)
{
if (condition(book))
{
results.Add(book);
}
}
return results;
}
}
仔细比较一下这个实现与刚才的实现有何不同(我们还给它起了一个更好听的名字Where,是不是和SQL更像了),现在我们可以这样调用了:
IList<Book> results = books.Where(book => book.Title == "yuyi"); Books是一个IList<Book>,这行代码是多么的自然而优雅。

依葫芦画瓢,我们可以到处这样书写代码了,不仅仅可以查找书籍,还可以查找帐户,一切处理集合查找的方法我都希望这样做,终于有一天你厌烦了,查找书,查找帐户,等等,他们之间没有什么差异,为什么我们要做这么多重复的工作呢,所有的IList<T>都继承自IEnumerable<T>,我们为啥不给IEnu merable<T>添加一个Where方法,这样我们不就一劳永逸了么,现在该是泛型施展才华的地方了:
public static class Helper
{
public delegate bool Condtion<T>(T t);
public static IEnumerable<T> FindBy<T>(this IEnumerable<T> item s, Condtion<T> condition)
{
foreach (T t in items)
{
if (condition(t))
{
//C# 2.0里出现的一个关键字,返回一个迭代器
yield return t;
}
}
}
}
现在,不管是IList<Book>还是IList<Account>都可以使用这个Where方法了
但是做集合操作的时候我们不仅仅需要Where,还需要OrderBy,Group等等,我想把所有的SQL能干的都移植过来。

当然微软也意识到了这点,如是在.net 3.5里,微软发布了我们梦寐以求的Linq,将查询集成到语言里面来。

它给IEnumerable<T>添加了很多扩展方法,这样你可以很自然的去调用。

你可以使用Reflector打开System.Core.dll,打开System. Linq命名空间,在这个命名空间里有一个Enumerable类,这里面就是微软为我们添加的扩展方法,看看,是不是SQL里所有的东西都可以在这里找到了。

好了,就此搁笔吧,这一篇作为我的走进Linq系列的开篇,在接下来我会为你把Linq大卸八块。

走进Linq-辉煌的背后
罗马不是一天建成的,千里之行始于足下,美丽的Linq也不是一蹴而就的。

Linq是给一些语言特性披上了一层漂亮的外衣。

那纺织Linq漂亮的外衣又需要哪些金针银线呢?
在本篇有四个小节,每个小节分别阐述一个语言特性,这些特性都将为Linq而服务,没有它们也没有未来的Linq。

在文中不仅仅写到了这些特性的用法,还揭示了他们背后发生的事情,也加上了我对这些特性的一些理解。

扩展方法没有扩展方法,Linq的实现肯定不会再像现在这么优雅,在本篇中我将首先描述扩展方法的应用,然后从IL层面解释扩展方法的实现,最后给出一些应用扩展方法的原则
匿名方法和Lambda表达式Lambda表达式将函数式编程风格带进了C#这种命令编程语言中,Lambda表达式可以编译成表达式树,将表达式树说成Linq的根基我想一点都不为过吧
匿名类型与隐式类型局部变量如果没有隐式类型局部变量,使用Linq查询的时候不会再像现在这么轻松吧
对象集合初始化器这个可以减少很多无意义的代码
这些文章我都发布在新手区,这里只是做个索引,如果感兴趣的可以去拍两下砖。

这一篇就算为后面的Linq铺路吧,精彩无需等待:。

走进Linq-Linq大观园
文章发布后大家有些人叫做,心里窃喜,不过压力也大增,我很想按照简洁明快的文风写下去,不过讲技术的文章很难不落于沉闷,所以我努力了。

(题外话:这几天猛看幽默小说,想把文字写的幽默一点,开个玩笑,呵呵)
经过几天的闭关编程大师又有了一些新的觉悟了,不管对DSL还是命令式编程和函数式编程都有了新的理解。

如是他又接着了漫长的云游。

第一站当然就是那个曾经让他结下心结的那个刚毕业的coder.
大师:“嘿,这几日可好,还在发牢骚么?”
Coder:“不了,正好你来了,让你看看我的程序”,Coder将他的电脑屏幕转向大师,期盼的眼神表明他急切的期望得到大师的夸奖。

如是大师看到了如下一些代码:
///<summary>
///一个通用的泛型委托,代表接受一个参数并有一个返回值的方法
///</summary>
///<typeparam name="TInput">输入参数类型</typeparam>
///<typeparam name="TOutput">返回值类型</typeparam>
///<param name="input">输入参数</param>
///<returns>返回值</returns>
public delegate TOutput MyDelegate<TInput,TOutput>(TInput input);
///<summary>
///这个类是包含有对IEnumerable<T>接口的一系列扩展方法
///因为在.net里所有的集合类都实现了IEnumerable<T>接口
///所以对该接口的扩展将扩散到所有集合
///</summary>
public static class Extension
{
public static IEnumerable<TInput> Where<TInput>(this IEnumera ble<TInput> self, MyDelegate<TInput, bool> filter)
{
foreach (TInput item in self)
if (filter(item))
yield return item;
}
public static IEnumerable<TOutput> Select<TInput, TOutput>(th is IEnumerable<TInput> self, MyDelegate<TInput, TOutput> selector)
{
foreach(TInput item in self)
yield return selector(item);
}
//下面有更多的SQL风格的移植
}
下面是我做的个小测试代码:
public class Program
{
public static void Main()
{
IList<Book> books = new List<Book> {
new Book { Title = "Inside COM", ISBN = "123-456-789",Pr ice=20 },
new Book { Title = "Inside C#", ISBN = "123-356-d89",Pri ce=100 },
new Book { Title = "Linq", ISBN = "123-d56-d89", Pric
e = 120 }
};
var result = books.Where(book => book.Title == "Linq").Se lect(book => new {
Key = book.Title,Value=book.Price
});
}
}
Coder一边展示着代码,一边念叨着,这里是因为使用了“扩展方法”所以可以这样写,这里使用了Lambda表达式,它可以简化匿名方法的写法,这里……
编程大师一边听着coder的讲解,一遍频频点头:“傻小子,不错啊,有点当年我的影子,按照你这样下去罗马也可以建成了,Linq也是可以写出来的呀。


Coder听到大师的话兴奋异常,不过他从这句话里还是捕捉到了一个陌生的词汇:Linq。

他用诧异的眼神看着大师,问道:”啥是Linq,是谁家又创造了个新词汇?”
大师笑着说,其实你刚才做的微软已经帮你做了,还给它起了一个非常洋气的名字:Linq,中文名字呢就叫做语言集成查询。

在.net 3.5发布的时候,微软新发布了几个dll,其中有一个就叫做System.Core.dll,在这
个dll下对一些System命名空间做了进一步扩展。

在System.Core.dll下的System命名空间下你会发现有这么几个泛型的委托:
//无参,有一个返回值
public delegate TResult Func<TResult>();
//有一个参数和一个返回值,和你那个MyDelegate一样
public delegate TResult Func<T, TResult>(T arg);
//两个参数一个返回值
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
//三个参数一个返回值
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T 3 arg3);
//四个参数一个返回值
public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg 2, T3 arg3, T4 arg4);
有了这几个泛型的委托基本上都能应付了吧。

还是在这个dll下有个System.Linq命名空间,这是Linq的核心命名空间,这里面有个名为Enumerable的静态类,它里面的方法都是对IEnumerable<T>(这个接口可是Linq的中心人物啊)这个接口的扩展方法。

看看,是不是在SQL里能用的这里都能找到了?
Select,Where,OrderBy, OrderByDescending,Average,Distinct
所以你现在很简单的就可以写出像下面这样的代码了:
result = books.Where(book=>book.Title.StartsWith("I"))
.OrderBy(book=>book.Price)
.Select(book=>new{Key=book.Title,Value=book.Price});
编程大师接着说:如果就这样算了,我觉得Linq也不过尔尔,增加一些扩展方法而已,但是现在微软比我们想象的走的更远,现在你不仅仅能对程序中的一些集合对象做这样的查询了,你想想我们平时的程序可以归结为怎样一个等式?
还没等coder说出口大师就在键盘上敲下:
程序=代码+数据
编程大师如是接着说:那这些数据平时都来源于哪里?
Coder:程序中自己构造的一些集合对象,像我刚才的代码中那样,还有数据库,这个使我们平时用到最多的,还有XML存储,还有WebService,这个来源于远程的数据,还有什么RSS啦等等,很多了。

编程大师:嗯,是的。

数据的来源非常广泛,就说我们平常用的三个吧,内存中的集合对象、XML存储和数据库。

对于内存中的集合对象我们有语言自身的支持,XML我们有XML的一些API,比如XPath,对于数据库我们有,可实际上从抽象层面我们对这些数据的操作都是相同的,你想不想屏蔽掉存储的细节,在高层有一个统一的API访问这些数据呢?至于数据存储在哪里对于你是透明的,也许它存在于你内存中,也许在万网的机房也许在美国西雅图,但是对于你来说这些都无需关心,你的代码都一样。

Coder:听起来是个很美妙的事情,这不会是在做梦吧。

大师:不是在做梦,今天你已经有了这些方法
在.net 3.5里微软还发布了另外两个dll:
System.Data.Linq.dll
System.Xml.Linq.dll
在System.Data.Linq.dll里,对数据库的查询做了支持,不过目前微软提供的只支持Sql S erver,感谢开源社区,现在有了DbLinq,它提供了对MySql,Oracle,Sql Server,PostgreSql, Sqlite的支持。

System.Xml.Linq.dll在更高层次对Xml的访问做了支持
这样你从微软这里获得了:
Linq to Objects 对内存中的集合的支持
Linq to Xml 对Xml的支持
Linq to SQL 对Sql Server的支持
这是一张从Linq in Action那本书里的截图,该图很好的在一个大的层次上揭示了Linq 的视图。

C#、等一系列.net语言在一些语言特性和Linq对语言的扩展上对Linq家族提供了支持。

未来我们将会实现Linq in Everywhere,Linq将成为你的变成习惯。

C#对Linq的语言层面支持
使用
result = books.Where(book=>book.Title.StartsWith("I"))
.OrderBy(book=>book.Price)
.Select(book=>new{Key=book.Title,Value=book.Price});
这种方式编写代码,虽然减少了不少工作量,并且编程风格也更加人性化,不过现在C#为我们提供了一些列非常有用的关键字。

上面的代码现在你可以这样来编写:
result = from book in books
where book.Title.StartsWith("I")
orderby book.Price
select new { Key=book.Title,Value = book.Price};
from关键字后面跟着的是Func委托的输入参数,in关键字后面跟着一个IEnumerable类型,where,orderby,select对应着那些扩展方法,那些扩展方法接受的委托的输入参数就是from后面的book。

实际上你可以把这些代码写成一行意思就更明显了:
result = from book in books where book.Title.StartsWith("I") orderb
y book.Price select new { Key=book.Title,Value = book.Price};
从books集合里枚举一个book,如果这个book的标题是以”I”开头的就把它加入到返回集合中,并把返回集合按照book的价钱排序将上面的代码编译后的程序集用Reflector反编译,生成的代码是:
books.Where<Book>(delegate(Book book)
{
return book.Title.StartsWith("I");
}).OrderBy<Book, float>(delegate(Book book)
{
return book.Price;
}).Select(delegate(Book book)
{
return new { Key = book.Title, Value = book.Price };
});
看来这种方式写和扩展方法调用的方式没有什么差别,那为什么不呢。

废话那么多了,还是来几个HelloWorld式的程序吧
HelloWorld Linq
(下面所有程序的Book就是本系列文章中第一篇所出现的Book类)
有一个Book集合,但是这个集合具体存储哪里我们并不清楚,也许在内存,也许在数据库,也许在XML存储,我们要做的就是把价格大于50的给揪出来,然后按照价格排序。

Linq to Objects(从内存中的集合里查找)
数据准备阶段
//这样的一个集合,存储在内存中
IList<Book> books = new List<Book> {
new Book { Title = "Inside COM", ISBN = "123-456-789",Price=20 }, new Book { Title = "Inside C#", ISBN = "123-356-d89",Price=100 }, new Book { Title = "Linq", ISBN = "123-d56-d89", Price = 120 } };
数据查询阶段
var result = from book in books
where book.Price > 50
orderby book.Price
select new { Key = book.Title, Value = book.Price }; foreach (var item in result)
Console.WriteLine("Key:{0}-Value:{1}",item.Key,item.V alue.ToString());
Linq to SQL(集合存储在Sql Server)
数据准备阶段
建立数据库表
输入数据
改写一下Book类,这个类是一个映射类,和数据库表做映射,更多内容后面会详细讲解[Table]
public class Book
{
///<summary>
///图书名称
///</summary>
[Column]
public string Title { get; set; }
///<summary>
///单价
///</summary>
[Column(DbType = "numeric(5, 2)")]
public float Price { get; set; }
///<summary>
///作者
///</summary>
[Column]
public string Author { get; set; }
///<summary>
/// ISBN号
///</summary>
[Column]
public string ISBN { get; set; }
}
数据查询阶段
DataContext db = new DataContext("Data Source=localhost;Initial Catal og=db;User ID=sa;Password=sa");
var result = from book in db.GetTable<Book>()
where book.Price > 50
orderby book.Price
select new { Key = book.Title, Value = book. Price };
foreach (var item in result)
Console.WriteLine("Key:{0}-Value:{1}",item.Key,item.V alue.ToString());
最后程序运行的结果都是:
真所谓殊途同归啊,不管数据是如何存储的,查询的方式却99%一致。

总结
本篇旨在给大家一个对Linq的大局观认识,没有详细的深入,就算一个总览吧。

精彩无需等待,祝大家编程愉快。

不能不说的C#特性-对象集合初始化器
本系列文章连接:
不能不说的C#特性-对象集合初始化器
不能不说的C#特性-匿名类型与隐式类型局部变量
不能不说的C#特性-扩展方法
不能不说的C#特性-匿名方法和Lambda表达式
不能不说的C#特性-迭代器(上)及一些研究过程中的副产品
不能不说的C#特性-迭代器(下),yield以及流的延迟计算
在写一些实体类的时候,我们往往在写构造方法的时候思考很长时间,除了一个无参构造器外还在想需要写几个构造器呢?哪些参数是需要初始化的。

现在你再也不需要为这事烦恼了。

C# 3.0为你提供了对象集合初始化器:
///<summary>
///图书类
///</summary>
public class Book
{
///<summary>
///图书名称
///</summary>
public string Title { get; set; }
///<summary>
///单价
///</summary>
public float Price { get; set; }
///<summary>
///作者
///</summary>
public string Author { get; set; }
///<summary>
/// ISBN号
///</summary>
public string ISBN { get; set; }
}
//对象初始化器
Book book = new Book { Title="Inside COM",ISBN="123-456-789"};现在你想初始化几个就初始化几个,不需要出现这种情况:
public Book():this("")
{
}
public Book(string title):this(title,0)
{
}
public Book(string title, float price):this(title,price,"")
{
}
public Book(string title, float price, string isbn)
{
this.Title = title;
this.Price = price;
this.ISBN = isbn;
}
这一串的构造方法都是为了应付不同的初始化情况。

好了,来看看对象初始化器编译器在后面为我们做了些什么呢?
使用Reflector反编译程序集:
Book <>g__initLocal0 = new Book();
<>g__initLocal0.Title = "Inside COM";
<>g__initLocal0.ISBN = "123-456-789";
Book book = <>g__initLocal0;
C#编译器生成了一个新的局部变量<>g__initLocal0,调用Book的默认无参构造方法初始化它,然后对它的属性进行赋值,最后将这个局部变量赋值给book。

看到这里,我们应该想到,要使用对象初始化器,那么这个对象必须有一个无参构造方法,如果你给这个方法写了一个有参构造方法而将它的默认无参构造方法覆盖了并且没有提供一个新的无参构造方
法,那么使用对象初始化器编译的时候是不会通过的(不过想不通,为啥C#编译器生成这么一个奇怪的局部变量名字,还有为啥不直接使用book呢)。

像下面的代码不更好:
Book book = new Book();
book.Title = "Inside COM";
book.ISBN = "123-456-789";
后来我发现我是在debug模式下编译的,换到release模式下变成了这样:
Book <>g__initLocal0 = new Book();
<>g__initLocal0.Title = "Inside COM";
<>g__initLocal0.ISBN = "123-456-789";
被优化了。

上面介绍的就是对象初始化器了,那什么是集合初始化器呢?
IList<Book> books = new List<Book>();
//这里就使用了对象初始化器,学以致用吧
books.Add(new Book { Title = "Inside COM", ISBN = "123-456-789",Price
=20 });
books.Add(new Book { Title = "Inside C#", ISBN = "123-356-d89",Price= 100 });
books.Add(new Book { Title = "Linq", ISBN = "123-d56-d89", Price = 12
0 });
这样的代码没少写吧,实际上也许比这更复杂,有了C# 3.0我们睡觉都想笑:
IList<Book> books = new List<Book> {
new Book { Title = "Inside COM", ISBN = "123-456-789",Price=20 }, new Book { Title = "Inside C#", ISBN = "123-356-d89",Price=100 }, new Book { Title = "Linq", ISBN = "123-d56-d89", Price = 120 } };
还是像刚才一样,我们来欣赏一下C#编译器为我们生成的代码:
List<Book> <>g__initLocal0 = new List<Book>();
Book <>g__initLocal1 = new Book();
<>g__initLocal1.Title = "Inside COM";
<>g__initLocal1.ISBN = "123-456-789";
<>g__initLocal1.Price = 20f;
<>g__initLocal0.Add(<>g__initLocal1);
Book <>g__initLocal2 = new Book();
<>g__initLocal2.Title = "Inside C#";
<>g__initLocal2.ISBN = "123-356-d89";
<>g__initLocal2.Price = 100f;
<>g__initLocal0.Add(<>g__initLocal2);
Book <>g__initLocal3 = new Book();
<>g__initLocal3.Title = "Linq";
<>g__initLocal3.ISBN = "123-d56-d89";
<>g__initLocal3.Price = 120f;
<>g__initLocal0.Add(<>g__initLocal3);
从上面的代码来看,编译器自动的调用了List的无参构造方法,然后实例化一个个的Book,再一个个的Add进去,和我们原来的做法没有什么不同,但是,这是编译器为我们做的,所以简省了我们很多的编码工作。

对象集合初始化器就算介绍完了。

有人也许会说,不就是个syntx sugar么,有什么。

是的,确实是个语法糖。

在编译器发展早期,编译器科学家门一直在想方设法的优化编译器生成的代码,这个时候,编译器做的主要是对机器优化,因为那个时候机器的时间非常宝贵,机器运算速度也不快,今天我们有了足够好的机器了(但并不是说我们可以不关注性能的编写程序),而且作为编写软件的人来说,比机器的时间宝贵得多,所以今天的编译器也在向人优
化了,从编程语言的发展之路来讲,今天的编程语言比昨天的语言更高级,也更人性化了,我们只要编写更少的代码,更符合人的思维的代码,而只要关注我们值的关注的地方。

体力活儿就交给编译器吧。

附加:
刚开始想想这对象集合初始化器也许就一鸡肋,没啥用,不就减少一点点代码么,像这种简单的初始化工作,大部分代码生成器都可以来干。

后来在研究匿名类型的时候突然发现,如果没有这个对象初始化器,匿名类型是不是要复杂一些?或者就是难以实现?
var test = new{Key="test",Value="test"};如果没有对象初始化器,匿名类型该怎么办?
不能不说的C#特性-匿名类型与隐式类型局部变量
本系列文章连接:
不能不说的C#特性-对象集合初始化器
不能不说的C#特性-匿名类型与隐式类型局部变量
不能不说的C#特性-扩展方法
不能不说的C#特性-匿名方法和Lambda表达式
不能不说的C#特性-迭代器(上)及一些研究过程中的副产品
不能不说的C#特性-迭代器(下),yield以及流的延迟计算
在本篇中我要介绍两个概念,我觉得这两个东西必须一起来介绍,这样才能连贯。

C# 2.0里我们已经匿名方法了,现在类型也玩起匿名来了,怪不得大家“举报”的时候都
喜欢匿名,为啥?因为匿名被举报人就找不着报复对象了呗,是的,匿名就是把名字隐藏起来,没有名字谁还能找得到你啊。

匿名类型
在C#里有这样一些类型,它是作为临时储存数据的,生命周期只在这个方法内,方法结束了,这个类型的生命周期也没有了。

那么这里我们就可以使用一个匿名类型。

var KeyPair = new{Key=”yuyi”,Value=”20”};
这个KeyPair就是一个匿名类型,注意KeyPair这里是一个变量名,并不是类的名字。

嗯,前面还有一个var,这又是什么呢?这是C# 3.0里面的隐式局部变量。

隐式类型局部变量
还是先介绍一下隐式类型局部变量吧:
在C# 3.0里多了一个关键字var,他表示这样的一种类型:C#编译器可以根据上下文推断的出来
比如var I = 5;编译器可以根据后面的赋值推断的出来i应该是个整型。

既然是局部变量,那么它就只能用在方法内部了,注意C#是强类型的,引入了一个var并不是像javascript 那样,变成了一个弱类型的语言。

在编译器第一次编译后var就会被确定的类型所替代的。

所以对于隐式类型局部变量要注意以下几点:
1.它只能存在于方法内部
2.它不是一个新的类型,只是一个关键字,或者叫做一个占位符,在C#编译器编译
后它就会被确定的类型所替代
3.它是编译器根据上下文推断出来的,所以所有一切不能被编译器推断出来的用法都
是错误的。

比如不能这样使用:var nullValue = null;因为null啥也不是,他是一个空指针,是一个不确定的东西。

也不能这样使用:var I = 5;I = “abc”;编译器根据第一个赋值会推断出它是一个整型,但是随后又将一个字符串赋值给它,这是怎么回事呢?
对于var我的建议是不到逼不得已的时候不用,那什么是逼不得已呢?来看我们的匿名类型吧。

回到匿名类型
刚才说了,匿名类型是没有名字的类型,没有名字你怎么来称呼它,怎么来声明它?但是匿名类型真的是没有名字的么?
看看C#编译器又在我们背后干了些什么:
使用ILDASM打开编译过的程序集,发现多了一个类型:
<>f__AnonymousType0<<Key>j__TPar, <Value>j__TPar>
这个类型是直接继承自System.Object的,并且是internal seald(只在程序集内可见,并且不能被继承)。

有心的你也许会发现,这个类型还是一个泛型类型,那么只要我们在使用一个匿名类型的时候参数个数,参数名称不发生变化,编译器是不会为我们产生更多的类型的:var KeyPair1 = new { Key="yuyi",Value="Programer"};
var KeyPair2 = new { Key="y",Value=3};
var KeyPair3 = new { Key=4,Value="abc"};
上面三个匿名类型,编译器只会为我们在背后产生一个新类型,一个泛型的新类型。

如果我们将这个匿名类型内的属性名修改一下:对
var KeyPair1 = new { Key="yuyi",Value="Programer"};
var KeyPair2 = new { Key="y",Value1=3};
就会产生两个新泛型了:
<>f__AnonymousType0<<Key>j__TPar, <Value>j__TPar>
<>f__AnonymousType1<<Key>j__TPar, <Value1>j__TPar>
看看,这个命名还是有规律可循哦。

如果你给这个匿名类型添加一个新属性呢?
这样又产生了一个新类型了:。

相关文档
最新文档