仓酷云

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 852|回复: 8
打印 上一主题 下一主题

[学习教程] C#迭代器(详解C#2.0 yield)

[复制链接]
精灵巫婆 该用户已被删除
跳转到指定楼层
楼主
发表于 2015-1-16 14:19:16 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
其实Java之所以在曾经独步天下,就是因为他的跨平台、安全性,这两方面,效率可不是Java的强项,反而是他最短的一块挡板,虽然net总是用理论证明比.NET快。迭代器形式是计划形式中举动形式(behavioralpattern)的一个例子,他是一种简化工具间通信的形式,也是一种十分简单了解和利用的形式。复杂来讲,迭代器形式使得你可以猎取到序列中的一切元素而不必体贴是其范例是array,list,linkedlist大概是其他甚么序列布局。这一点使得可以十分高效的构建数据处置通道(datapipeline)--即数据可以进进处置通道,举行一系列的变更,大概过滤,然后失掉了局。现实上,这恰是LINQ的中心形式。

在.NET中,迭代器形式被IEnumerator和IEnumerable及其对应的泛型接口所封装。假如一个类完成了IEnumerable接口,那末就可以够被迭代;挪用GetEnumerator办法将前往IEnumerator接口的完成,它就是迭代器自己。迭代器类游标,他是数据序列中的一个地位纪录。迭代器只能向前挪动,统一数据序列中能够有多个迭代器同时对数据举行操纵。

在C#1中已内建了对迭代器的撑持,那就是foreach语句。使得可以举行比for轮回语句更间接和复杂的对汇合的迭代,编译器会将foreach编译来挪用GetEnumerator和MoveNext办法和Current属性,假如工具完成了IDisposable接口,在迭代完成以后会开释迭代器。可是在C#1中,完成一个迭代器是绝对来讲有点烦琐的操纵。C#2使得这一事情变得年夜为复杂,节俭了完成迭代器的很多事情。

接上去,我们来看怎样完成一个迭代器和C#2关于迭代器完成的简化,然后再枚举几个迭代器在实际生存中的例子。

1.C#1:手动完成迭代器的烦琐


假定我们必要完成一个基于环形缓冲的新的汇合范例。我们将完成IEnumerable接口,使得用户可以很简单的使用该汇合中的一切元素。我们的疏忽其他细节,将注重力仅仅会合在怎样完成迭代器上。汇合将值存储在数组中,汇合可以设置迭代的肇端点,比方,假定汇合有5个元素,你可以将肇端点设为2,那末迭代输入为2,3,4,0,最初是1.

为了可以复杂展现,我们供应了一个设置值和肇端点的机关函数。使得我们可以以上面这类体例遍历汇合:
object[]values={"a","b","c","d","e"};
IterationSamplecollection=newIterationSample(values,3);
foreach(objectxincollection)
{
Console.WriteLine(x);
}

因为我们将肇端点设置为3,以是汇合输入的了局是d,e,a,b及c,如今,我们来看怎样完成IterationSample类的迭代器:
classIterationSample:IEnumerable
{
Object[]values;
Int32startingPoint;
publicIterationSample(Object[]values,Int32startingPoint)
{
this.values=values;
this.startingPoint=startingPoint;
}
publicIEnumeratorGetEnumerator()
{
thrownewNotImplementedException();
}
}

我们还没有完成GetEnumerator办法,可是怎样写GetEnumerator部分的逻辑呢,第一就是要将游标确当前形态存在某一个中央。一方面是迭代器形式并非一次前往一切的数据,而是客户端一次只哀求一个数据。这就意味着我们要纪录客户以后哀求到了汇合中的那一个纪录。C#2编译器关于迭代器的形态保留为我们做了良多事情。

如今来看看,要保留哪些形态和形态存在哪一个中央,假想我们试图将形态保留在IterationSample汇合中,使得它完成IEnumerator和IEnumerable办法。咋一看,看起来大概,究竟数据在准确的中央,包含肇端地位。我们的GetEnumerator办法仅仅前往this。可是这类办法有一个很主要的成绩,假如GetEnumerator办法挪用屡次,那末多个自力的迭代器就会前往。比方,我们可使用两个嵌套的foreach语句,来猎取一切大概的值对。这两个迭代必要相互自力。这意味着我们必要每次挪用GetEnumerator时前往的两个迭代器工具必需坚持自力。我们仍然能够间接在IterationSample类中经由过程响应函数完成。可是我们的类具有了多个职责,这位背了单一职责准绳。

因而,我们来创立别的一个类来完成迭代器自己。我们利用C#中的外部类来完成这一逻辑。代码以下:
classIterationSampleEnumerator:IEnumerator
{
IterationSampleparent;//迭代的工具#1
Int32position;//以后游标的地位#2
internalIterationSampleEnumerator(IterationSampleparent)
{
this.parent=parent;
position=-1;//数组元素下标从0入手下手,初始时默游标设置为-1,即在第一个元素之前,#3
}

publicboolMoveNext()
{
if(position!=parent.values.Length)//判别以后地位是不是为最初一个,假如不是游标自增#4
{
position++;
}
returnposition<parent.values.Length;
}

publicobjectCurrent
{
get
{
if(position==-1||position==parent.values.Length)//第一个之前和最初一个自后的会见不法#5
{
thrownewInvalidOperationException();
}
Int32index=position+parent.startingPoint;//思索自界说入手下手地位的情形#6
index=index%parent.values.Length;
returnparent.values[index];
}
}

publicvoidReset()
{
position=-1;//将游标重置为-1#7
}
}

要完成一个复杂的迭代器必要手动写这么多的代码:必要纪录迭代的原始汇合#1,纪录以后游标地位#2,前往元素时,依据以后游标和数组界说的肇端地位设置定迭代器在数组中的地位#6。初始化时,将以后地位设定在第一个元素之前#3,当第一次挪用迭代器时起首必要挪用MoveNext,然后再挪用Current属性。在游标自增时对以后地位举行前提判别#4,使得即便当第一次挪用MoveNext时没有可前往的元素也不至于堕落#5。重置迭代器时,我们将以后游标的地位复原到第一个元素之前#7。

除分离以后游标地位和自界说的肇端地位前往准确的值这点简单堕落外,下面的代码十分直不雅。如今,只必要在IterationSample类的GetEnumerator办法中前往我们当才编写的迭代类便可:
publicIEnumeratorGetEnumerator()
{
returnnewIterationSampleEnumerator(this);
}

值得注重的是,下面只是一个绝对复杂的例子,没有太多的形态必要跟踪,不必反省汇合在迭代的过程当中是不是产生了变更。为了完成一个复杂的迭代器,在C#1中我们完成了云云多的代码。在利用Framework自带的完成了IEnumerable接口的汇合时我们利用foreach很便利,可是当我们誊写本人的汇合来完成迭代时必要编写这么多的代码。

在C#1中,也许必要40行代码来完成一个复杂的迭代器,如今看看C#2对这一历程的改善。

2.C#2:经由过程yield语句简化迭代


2.1引进迭代块(iterator)和yieldreturn语句

C#2使得迭代变得加倍复杂--削减了良多代码量也使得代码加倍的文雅。上面的代码展现了再C#2中完成GetEnumerator办法的完全代码:
publicIEnumeratorGetEnumerator()
{
for(intindex=0;index<this.values.Length;index++)
{
yieldreturnvalues[(index+startingPoint)%values.Length];
}
}

复杂几行代码就可以够完整完成IterationSampleIterator类所必要的功效。办法看起来很一般,除利用了yieldreturn。这条语句告知编译器这不是一个一般的办法,而是一个必要实行的迭代块(yieldblock),他前往一个IEnumerator工具,你可以利用迭代块来实行迭代办法并前往一个IEnumerable必要完成的范例,IEnumerator大概对应的泛型。假如完成的长短泛型版本的接口,迭代块返的yieldtype是Object范例,不然前往的是响应的泛型范例。比方,假如办法完成IEnumerable<String>接口,那末yield前往的范例就是String范例。在迭代块中除yieldreturn外,不同意呈现一般的return语句。块中的一切yieldreturn语句必需前往和块的最初前往范例兼容的范例。举个例子,假如办法界说必要前往IEnumeratble<String>范例的话,不克不及yieldreturn1。必要夸大的一点是,关于迭代块,固然我们写的办法看起来像是在按次实行,实践上我们是让编译器来为我们创立了一个形态机。这就是在C#1中我们誊写的那部分代码---挪用者每次挪用只必要前往一个值,因而我们必要记着最初一次前往值时,在汇合中地位。当编译器碰到迭代块是,它创立了一个完成了形态机的外部类。这个类记着了我们迭代器的正确以后地位和当地变量,包含参数。这个类有点相似与我们之前手写的那段代码,他将一切必要纪录的形态保留为实例变量。上面来看看,为了完成一个迭代器,这个形态机必要按按次实行的操纵:

1,它必要一些初始的形态
2,当MoveNext被挪用时,他必要实行GetEnumerator办法中的代码来筹办下一个待前往的数据。
3,当挪用Current属性是,必要前往yielded的值。
4,必要晓得甚么时分迭代停止是,MoveNext会前往false

上面来看看迭代器的实行按次。

2.2迭代器的实行流程

以下的代码,展现了迭代器的实行流程,代码输入(0,1,2,-1)然后停止。
classProgram
{
staticreadonlyStringPadding=newString(,30);
staticIEnumerable<Int32>CreateEnumerable()
{
Console.WriteLine("{0}CreateEnumerable()办法入手下手",Padding);
for(inti=0;i<3;i++)
{
Console.WriteLine("{0}入手下手yield{1}",i);
yieldreturni;
Console.WriteLine("{0}yield停止",Padding);
}
Console.WriteLine("{0}Yielding最初一个值",Padding);
yieldreturn-1;
Console.WriteLine("{0}CreateEnumerable()办法停止",Padding);
}

staticvoidMain(string[]args)
{
IEnumerable<Int32>iterable=CreateEnumerable();
IEnumerator<Int32>iterator=iterable.GetEnumerator();
Console.WriteLine("入手下手迭代");
while(true)
{
Console.WriteLine("挪用MoveNext办法……");
Booleanresult=iterator.MoveNext();
Console.WriteLine("MoveNext办法前往的{0}",result);
if(!result)
{
break;
}
Console.WriteLine("猎取以后值……");
Console.WriteLine("猎取到确当前值为{0}",iterator.Current);
}
Console.ReadKey();
}
}

为了展现迭代的细节,以上代码利用了while轮回,一般情形下一样平常利用foreach。和前次分歧,此次在迭代办法中我们前往的是IEnumerable;工具而不是IEnumerator;工具。一般,为了完成IEnumerable接口,只必要前往IEnumerator工具便可;假如自是想从一个办法中前往一些列的数据,那末利用IEnumerable.以下是输入了局:



从输入了局中能够看出一下几点:
1,直到第一次挪用MoveNext,CreateEnumerable中的办法才被挪用。
2,在挪用MoveNext的时分,已做好了一切操纵,前往Current属性并没有实行任何代码。
3,代码在yieldreturn以后就中断实行,守候下一次挪用MoveNext办法的时分持续实行。
4,在办法中能够有多个yieldreturn语句。
5,在最初一个yieldreturn实行完成后,代码并没有停止。挪用MoveNext前往false使得办法停止。

第一点尤其主要:这意味着,不克不及在迭代块中写任安在办法挪用时必要当即实行的代码--好比说参数考证。假如将参数考证放在迭代块中,那末他将不克不及够很好的起感化,这是常常会招致的毛病的中央,并且这类毛病不简单发明。

上面来看怎样中断迭代,和finally语句块的特别实行体例。

2.3迭代器的特别实行流程

在一般的办法中,return语句一般有两种感化,一是前往挪用者实行的了局。二是停止办法的实行,在停止之前实行finally语句中的办法。在下面的例子中,我们看到了yieldreturn语句只是长久的加入了办法,在MoveNext再次挪用的时分持续实行。在这里我们没有写finally语句块。怎样真实的加入办法,加入办法时finnally语句块怎样实行,上面来看看一个对照复杂的布局:yieldbreak语句块。

利用yieldbreak停止一个迭代

一般我们要做的是使办法只要一个加入点,一般,多个加入点的程序会使得代码不容易浏览,出格是利用trycatchfinally等语句块举行资本清算和非常处置的时分。在利用迭代块的时分也会碰到如许的成绩,但假如你想早点加入迭代,那末利用yieldbreak就可以到达想要的效果。他可以即刻停止迭代,使得下一次挪用MoveNext的时分前往false。

上面的代码演示了从1迭代到100,可是工夫超时的时分就中断了迭代。
staticIEnumerable<Int32>CountWithTimeLimit(DateTimelimit)
{
try
{
for(inti=1;i<=100;i++)
{
if(DateTime.Now>=limit)
{
yieldbreak;
}
yieldreturni;
}
}
finally
{
Console.WriteLine("中断迭代!");Console.ReadKey();
}
}
staticvoidMain(string[]args)
{
DateTimestop=DateTime.Now.AddSeconds(2);
foreach(Int32iinCountWithTimeLimit(stop))
{
Console.WriteLine("前往{0}",i);
Thread.Sleep(300);
}
}

下图是输入了局,能够看出迭代语句一般停止,yieldreturn语句和一般办法中的return语句一样,上面来看看finally语句块是甚么时分和怎样实行的。


Finally语句块的实行

一般,finally语句块在当办法实行加入特定地区时就会实行。迭代块中的finally语句和一般办法中的finally语句块纷歧样。就像我们看到的,yieldreturn语句中断了办法的实行,而不是加入办法,依据这一逻辑,在这类情形下,finally语句块中的语句不会实行。

但当碰着yieldbreak语句的时分,就会实行finally语句块,这根一般办法中的return一样。一样平常在迭代块中利用finally语句来开释资本,就像利用using语句一样。

上面来看finally语句怎样实行。

不论是迭代到了100次大概是因为工夫到了中断了迭代,大概是抛出了非常,finally语句总会实行,可是在有些情形下,我们不想让finally语句块被实行。

只要在挪用MoveNext后迭代块中的语句才会实行,那末假如不失落用MoveNext呢,假如挪用几回MoveNext然后中断挪用,了局会怎样呢?请看上面的代码?
DateTimestop=DateTime.Now.AddSeconds(2);
foreach(Int32iinCountWithTimeLimit(stop))
{
if(i>3)
{
Console.WriteLine("前往中^");
return;
}
Thread.Sleep(300);
}

在forech中,return语句以后,由于CountWithTimeLimit中有finally块以是代码持续实行CountWithTimeLimit中的finally语句块。foreach语句会挪用GetEnumerator前往的迭代器的Dispose办法。在停止迭代之前挪用包括迭代块的迭代器的Dispose办法时,形态时机实行在迭代器局限内处于停息形态下的代码局限内的一切finally块,这有点庞大,可是了局很简单注释:只要利用foreach挪用迭代,迭代块中的finally块会准期看的那样实行。上面能够用代码考证以上结论:
IEnumerable<Int32>iterable=CountWithTimeLimit(stop);
IEnumerator<Int32>iterator=iterable.GetEnumerator();

iterator.MoveNext();
Console.WriteLine("前往{0}",iterator.Current);

iterator.MoveNext();
Console.WriteLine("前往{0}",iterator.Current);
Console.ReadKey();

代码输入以下:


上图能够看出,中断迭代没有打印出来,当我们手动挪用iterator的Dispose办法时,会看到以下的了局。在迭代器迭代停止前停止迭代器的情形很少见,也很少不利用foreach语句而是手动来完成迭代,假如要手动完成迭代,别忘了在迭代器表面利用using语句,以确保可以实行迭代器的Dispose办法进而实行finally语句块。


上面来看看微软对迭代器的一些完成中的特别举动:

2.4迭代器实行中的特别举动

假如利用C#2的编译器将迭代块编译,然后利用ildsam大概Reflector检察天生的IL代码,你会发明在幕后编译器回味我们天生了一些嵌套的范例(nestedtype).下图是利用Ildsam来检察天生的IL,最上面两行是代码中的的两个静态办法,下面蓝色的<CountWithTimeLimit>d_0是编译器为我们天生的类(尖括号只是类名,和泛型有关),代码中能够看出该类完成了那些接口,和有哪些办法和字段。也许和我们手动完成的迭代器布局相似。


真实的代码逻辑其实MoveNext办法中实行的,个中有一个年夜的switch语句。侥幸的是,作为一位开辟职员没需要懂得这些细节,但一些迭代器实行的体例仍是值得注重的:
1,在MoveNext办法第一次实行之前,Current属性老是前往迭代器前往范例的默许的值。比方IEnumeratble前往的是Int32范例,那末默许初始值是0,以是在挪用MoveNext办法之前挪用Current属性就会前往0。
2,MoveNext办法前往false后,Current属性老是前往最初迭代的谁人值。
3,Reset办法一样平常会抛出非常,而在本文入手下手代码中,我们手动完成一个迭代器时在Reset中可以准确实行逻辑。
4,编译器为我们发生的嵌套类会同时完成IEnumerator的泛型和非泛型版本(得当的时分还会完成IEnumerable的泛型和非泛型版本).

没有准确完成Reset办法是有缘故原由的--编译器不晓得必要利用如何的逻辑来重新设置迭代器。良多人以为不该该有Reset办法,良多汇合其实不撑持,因而挪用者不该该依附这一办法。

完成别的接口没有害处。办法中前往IEnumerable接口,他完成了五个接口(包含IDisposable),作为一个开辟者不必忧虑这些。同时完成IEnumerable和IEnumerator接口其实不罕见,编译器为了使迭代器的举动老是一般,而且为可以在以后的线程中仅仅必要迭代一个汇合就可以创立一个独自的嵌套范例才这么做的。

Current属性的举动有些乖僻,他保留了迭代器的最初一个前往值而且制止了渣滓接纳期举行搜集。

因而,主动完成的迭代器办法有一些小的缺点,可是明智的开辟者不会碰到任何成绩,利用他可以节俭良多代码量,使得迭代器的利用水平比C#1中要广。上面来看在实践开辟中迭代器简化代码的中央。

3.实践开辟中利用迭代的例子


3.1从工夫段中迭代日期

在触及到工夫区段时,一般会利用轮回,代码以下:
for(DateTimeday=timetable.StartDate;day<timetable.EndDate;day=day.AddDays(1))
{
……
}

轮回偶然没有迭代直不雅和有体现力,在本例中,能够了解为“工夫区间中的每天”,这恰是foreach利用的场景。因而上述轮回假如写成迭代,代码会更美妙:
foreach(DateTimedayintimetable.DateRange)
{
……
}

在C#1.0中要完成这个必要下必定工夫。到了C#2.0就变得复杂了。在timetable类中,只必要增加一个属性:
publicIEnumerable<DateTime>DateRange
{
get
{
for(DateTimeday=StartDate;day<=EndDate;day=day.AddDays(1))
{
yieldreturnday;
}
}
}

只是将轮回挪动到了timetable类的外部,可是经由这一修改,使得封装变得更加优秀。DateRange属性只是遍用时间区间中的每天,每次前往一天。假如想要使得逻辑变得庞大一点,只必要修改一处。这一小小的修改使得代码的可读性年夜年夜加强,接上去能够思索将这个Range扩大为泛型Range<T>。

3.2迭代读取文件中的每行

读取文件时,我们常常会誊写如许的代码:
using(TextReaderreader=File.OpenText(fileName))
{
Stringline;
while((line=reader.ReadLine())!=null)
{
……
}
}

这一过程当中有4个环节:
1,怎样猎取TextReader
2,办理TextReader的性命周期
3,经由过程TextReader.ReadLine迭代一切的行
4,对行举行处置

能够从两个方面临这一历程举行改善:可使用托付--能够写一个具有reader和一个代办署理作为参数的帮助办法,利用代办署理办法来处置每行,最初封闭reader,这常常被用来展现闭包和代办署理。另有一种更加文雅更切合LINQ体例的改善。除将逻辑作为办法参数传出来,我们可使用迭代来迭代一次迭代一行代码,如许我们就能够利用foreach语句。代码以下:
staticIEnumerable<String>ReadLines(StringfileName)
{
using(TextReaderreader=File.OpenText(fileName))
{
Stringline;
while((line=reader.ReadLine())!=null)
{
yieldreturnline;
}
}
}

如许就能够利用以下foreach办法来读取文件了:
foreach(StringlineinReadLines("test.txt"))
{
Console.WriteLine(line);
}

办法的主体部分和之前的一样,利用yieldreturn前往了读取到的每行,只是在迭代停止后有点分歧。之前的操纵,先翻开文档,每次读取一行,然后在读取停止时封闭reader。固然”当读取停止时”和之后方法中利用using类似,但当利用迭代时这个历程加倍分明。

这就是为何foreach迭代停止后会挪用迭代器的dispose办法这么主要的缘故原由了,这个操纵可以包管reader可以失掉开释。迭代办法中的using语句块相似与try/finally语句块;finally语句在读取文件停止大概当我们显现挪用IEnumerator<String>的Dispose办法时城市实行。大概偶然候会经由过程ReadLine().GetEnumerator()的体例前往IEnumerator<String>,举行手动迭代而没有挪用Dispose办法,就会发生资本泄露。一般会利用foreach语句来迭代轮回,以是这个成绩很少会呈现。可是仍是有需要意想到这个潜伏的成绩。

该办法封装了前三个步骤,这大概有点刻薄。将性命周期和办法举行封装是有需要的,如今扩大一下,假设我们要从收集上读取一个流文件,大概我们想利用UTF-8编码的办法,我们必要将第一个部分暴漏给办法挪用者,使得办法的挪用署名大抵以下:
staticIEnumerable<String>ReadLines(TextReaderreader)

如许有良多欠好的中央,我们想对reader有相对的把持,使得挪用者可以在停止后能举行资本清算。成绩在于,假如在第一次挪用MoveNext()之前呈现毛病,那末我们就没无机会举行资本清算事情了。IEnumerable<String>本身不克不及开释,他存储了某个形态必要被清算。另外一个成绩是假如GetEnumerator被挪用两次,我们本意是前往两个自力的迭代器,然后他们却利用了不异的reader。一种办法是,将前往范例改成IEnumerator<String>,但如许的话,不克不及利用foreach举行迭代,并且假如没有实行到MoveNext办法的话,资本也得不到清算。

侥幸的是,有一种办法能够办理以上成绩。就像代码不用当即实行,我们也不必要reader当即实行。我们能够供应一个接话柄现“假如必要一个TextReader,我们能够供应”。在.NET3.5中有一个代办署理,署名以下:
publicdelegateTResultFunc<TResult>()

代办署理没有参数,前往和范例参数不异的范例。我们想取得TextReader工具,以是可使用Func<TextReader>,代码以下:
using(TextReaderreader=provider())
{
Stringline;
while((line=reader.ReadLine())!=null)
{
yieldreturnline;
}
}

3.3利用迭代块和迭代前提来对汇合举行举行惰性过滤

LINQ同意对内存汇合大概数据库等多种数据源用复杂壮大的体例举行查询。固然C#2没有对查询表达式,lambda表达及扩大办法举行集成。可是我们也能到达相似的效果。

LINQ的一个中心的特性是可以利用where办法对数据举行过滤。供应一个汇合和过滤前提代办署理,过滤的了局就会在迭代的时分经由过程惰性婚配,每婚配一个过滤前提就前往一个了局。这有点像List<T>.FindAll办法,可是LINQ撑持对一切完成了IEnumerable<T>接口的工具举行惰性求值。固然从C#3入手下手撑持LINQ,可是我们也能够利用已有的常识在必定水平上完成LINQ的Where语句。代码以下:
publicstaticIEnumerable<T>Where<T>(IEnumerable<T>source,Predicate<T>predicate)
{
if(source==null||predicate==null)
thrownewArgumentNullException();
returnWhereImpl(source,predicate);
}

privatestaticIEnumerable<T>WhereImpl<T>(IEnumerable<T>source,Predicate<T>predicate)
{
foreach(Titeminsource)
{
if(predicate(item))
yieldreturnitem;
}
}

IEnumerable<String>lines=ReadLines("FakeLinq.cs");
Predicate<String>predicate=delegate(Stringline)
{
returnline.StartsWith("using");
};

如上代码中,我们将全部完成分为了两个部分,参数考证和详细逻辑。固然看起来奇异,可是关于毛病处置来讲是很有需要的。假如将这两个部分办法放到一个办法中,假如用户挪用了Where<String>(null,null),将不会产生任何成绩,最少我们等候的非常没有抛出。这是因为迭代块的惰性求值机制发生的。在用户迭代的时分第一次挪用MoveNext办法之前,办法主体中的代码不会实行,就像在2.2节中看到的那样。假如你想孔殷的对办法的参数举行判别,那末没有一个中央可以延缓非常,这使得bug的追踪变得坚苦。尺度的做法如上代码,将办法分为两部分,一部分像一般办法那样对参数举行考证,另外一部分代码利用迭代块对主体逻辑数据举行惰性处置。

迭代块的主体很直不雅,对汇合中的逐一元素,利用predict代办署理办法举行判别,假如满意前提,则前往。假如不满意前提,则迭代下一个,直到满意前提为止。假如要在C#1中完成这点逻辑就很坚苦,出格是完成其泛型版本。

前面的那段代码演示了利用之前的readline办法读取数据然后用我们的where办法来过滤猎取line中以using开首的行,和用File.ReadAllLines及Array.FindAll<String>完成这一逻辑的最年夜的不同是,我们的办法是完整惰性和流线型的(Streaming)。每次只在内存中哀求一行并对其举行处置,固然假如文件对照小的时分没有甚么不同,可是假如文件很年夜,比方上G的日记文件,这类办法的上风就会展现出来了。

4总结


C#对很多计划形式举行了直接的完成,使得完成这些形式变得很简单。绝对来针对某一特定的计划形式间接完成的的特征对照少。从foreach代码中看出,C#1对迭代器形式举行了间接的撑持,可是没有对举行迭代的汇合举行无效的撑持。对汇合完成一个准确的IEnumerable很耗时,简单堕落也很很单调。在C#2中,编译器为我们做了良多事情,为我们完成了一个形态机来完成迭代。

本文还展现了和LINQ类似的一个功效:对汇合举行过滤。IEnumerable<T>在LINQ中最主要的一个接口,假如想要在LINQToObject上完成本人的LINQ操纵,那末你会由衷的叹息这个接口的壮大功效和C#言语供应的迭代块的用途。

本文还展现了实践项目中利用迭代块使得代码加倍易读和逻辑性更好的例子,但愿这些例子使你对了解迭代有所匡助。有理由相信是能提供更出色的性能。很多平台无法支持复杂的编译器,因此需要二次编译来减少本地编译器的复杂度。当然可能做不到java编译器那么简易。
柔情似水 该用户已被删除
沙发
发表于 2015-1-18 12:51:51 来自手机 | 只看该作者
关于ASP.NET功能上,ASP.NET比微软以前的ASP(96年出现)有更强大的library,更好的稳定性。ASP.NET可以使用.NETFramework中所有组件(也就是说.NET能实现的,ASP.NET一样能实现)。
再见西城 该用户已被删除
板凳
发表于 2015-1-25 23:25:30 | 只看该作者
那么,ASP.Net有哪些改进呢?
admin 该用户已被删除
地板
发表于 2015-2-4 13:49:51 | 只看该作者
现在的ASP.net分为两个版本:1.1和2.0Asp.net1.1用VS2003(visualstudio2003)编程。Asp.net2.0用VS2005(visualstudio2005)编程。现在一般开发用的是VS2003。
再现理想 该用户已被删除
5#
发表于 2015-2-10 01:29:27 | 只看该作者
Servlet的形式和前面讲的CGI差不多,它是HTML代码和后台程序分开的。它们的启动原理也差不多,都是服务器接到客户端的请求后,进行应答。不同的是,CGI对每个客户请求都打开一个进程(Process)。
兰色精灵 该用户已被删除
6#
发表于 2015-2-28 15:16:34 | 只看该作者
在调试JSP代码时,如果程序出错,JSP服务器会返回出错信息,并在浏览器中显示。这时,由于JSP是先被转换成Servlet后再运行的,所以,浏览器中所显示的代码出错的行数并不是JSP源代码的行数。
愤怒的大鸟 该用户已被删除
7#
发表于 2015-3-10 01:20:37 | 只看该作者
ASP是把代码交给VBScript解释器或Jscript解释器来解释,当然速度没有编译过的程序快了。
因胸联盟 该用户已被删除
8#
发表于 2015-3-17 04:10:05 | 只看该作者
由于CGI程序每响应一个客户就会打开一个新的进程,所以,当有多个用户同时进行CGI请求的时候,服务器就会打开多个进程,这样就加重了服务器的负担,使服务器的执行效率变得越来越低下。
灵魂腐蚀 该用户已被删除
9#
发表于 2015-3-23 19:34:21 | 只看该作者
代码的可重用性差:由于是面向结构的编程方式,并且混合html,所以可能页面原型修改一点,整个程序都需要修改,更别提代码重用了。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|仓酷云 鄂ICP备14007578号-2

GMT+8, 2024-12-23 21:55

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表