IEnumerable<object> FilteredList()
{
foreach(object item in FullList)
{
if(IsItemInPartialList(item))
yield return item;
}
}
yield关键字在那里有什么作用?我已经在几个地方提到过它,还有另一个问题,但是我还没有弄清楚它的实际作用。我习惯于从一个线程向另一个线程屈服的角度考虑yield,但这在这里似乎无关紧要。
#1 楼
yield
关键字实际上在这里做了很多。函数返回一个实现
IEnumerable<object>
接口的对象。如果调用函数开始对此对象进行调用,则会再次调用该函数,直到“屈服”为止。这是C#2.0中引入的语法糖。在早期版本中,您必须创建自己的foreach
和IEnumerable
对象才能执行此类操作。理解此类代码的最简单方法是键入示例,设置一些断点并查看会发生什么。尝试逐步执行以下示例:
public void Consumer()
{
foreach(int i in Integers())
{
Console.WriteLine(i.ToString());
}
}
public IEnumerable<int> Integers()
{
yield return 1;
yield return 2;
yield return 4;
yield return 8;
yield return 16;
yield return 16777216;
}
逐步执行示例时,将发现对
IEnumerator
的第一个调用返回Integers()
。第二个调用返回1
,并且行2
不再执行。这是一个真实的示例:
public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
using (var connection = CreateConnection())
{
using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
{
command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return make(reader);
}
}
}
}
}
评论
在这种情况下,这会更容易,我只是在这里使用整数来显示收益率的工作原理。关于使用yield return的好处是,这是实现迭代器模式的非常快速的方法,因此对事情的评估很懒。
–孟德尔
08年12月22日在8:35
同样值得注意的是,您可以使用产量折让;当您不想再退回任何物品时。
–罗里
2011年5月17日18:13
yield不是关键字。如果是,则无法使用yield作为标识符,因为int yield = 500;
–白兰地
15年2月19日在19:15
@Brandin是因为所有编程语言都支持两种类型的关键字,即保留关键字和上下文关键字。 yield属于后面的类别,这就是为什么C#编译器不禁止您的代码的原因。此处有更多详细信息:ericlippert.com/2009/05/11/reserved-and-contextual-keywords您会很高兴地知道,还有一些保留词未被语言识别为关键字。例如Java中的goto。此处有更多详细信息:stackoverflow.com/questions/2545103/…
– RBT
16年5月19日在0:27
“如果调用函数开始对此对象进行遍历,则该函数将再次被调用,直到它“屈服”为止。对我来说听起来不对。我总是在“农作物丰收”而不是“汽车向行人出产”的背景下想到c#yield关键字。
–扎克
16年5月31日在15:28
#2 楼
迭代。它会在状态表下创建一个状态机,该状态机会记住您在该函数的每个其他循环中的位置,并从那里开始。#3 楼
Yield有两个很好的用途,它有助于提供自定义迭代而无需创建临时集合。
它有助于进行有状态迭代。
为了更生动地解释上述两点,我创建了一个简单的视频,您可以在这里观看
评论
该视频有助于我清楚地了解产量。 @ShivprasadKoirala的代码项目文章C#Yield的用途是什么?同样的解释也是一个很好的来源
– ush
2015年4月30日在21:46
我还要补充一点,第三点是yield是创建自定义IEnumerator的“快速”方法(而不是让类实现IEnumerator接口)。
– Mr.Tourkos
18/12/7在15:19
我观看了您的视频Shivprasad,它清楚地说明了yield关键字的用法。
–撕裂Aurstad
19 Mar 5 '19在8:51
感谢您的视频!很好解释!
– Roblogic
19年11月25日在10:47
很棒的视频,但是很奇怪...使用yield的实现显然更干净,但是必须实质上是在内部创建自己的临时内存或/和List以便跟踪状态(或更确切地说是创建状态机)。那么,“ Yield”是否在做其他事情,而不是使实现更简单,使外观看起来更好,还是还有其他东西?效率如何,使用Yield运行代码比没有使用效率更高/更快?
– toughQuestions
3月19日15:10
#4 楼
最近,Raymond Chen也对yield关键字进行了一系列有趣的文章。C#中迭代器的实现及其结果(第1部分)
C#中迭代器的实现及其后果(第2部分)
C#中迭代器的实现及其结果(第3部分)
C#中迭代器的实现及其后果(第4部分)
它通常用于轻松实现迭代器模式,但可以推广到状态机中。引用Raymond毫无意义,最后一部分也链接到其他用途(但是Entin博客中的示例特别好,显示了如何编写异步安全代码)。
评论
这需要进行表决。甜蜜的他如何解释操作员和内部的目的。
– sajidnizami
2011年6月14日上午10:45
第1部分解释了“收益回报”的句法糖。很好的解释!
– Dror Weiss
13年2月11日在11:04
#5 楼
乍一看,收益回报是返回IEnumerable的.NET糖。没有收益,则立即创建集合的所有项目:
class SomeData
{
public SomeData() { }
static public IEnumerable<SomeData> CreateSomeDatas()
{
return new List<SomeData> {
new SomeData(),
new SomeData(),
new SomeData()
};
}
}
使用yield的代码相同,它逐项返回:
class SomeData
{
public SomeData() { }
static public IEnumerable<SomeData> CreateSomeDatas()
{
yield return new SomeData();
yield return new SomeData();
yield return new SomeData();
}
}
使用yield的优点是,如果使用数据的函数只需要第一个集合中的项目,其余项目将不会创建。
yield运算符允许根据需要创建项目。那是使用它的一个很好的理由。
#6 楼
yield return
与枚举器一起使用。在yield语句的每次调用中,控制权都返回给调用者,但它可以确保保持被调用者的状态。因此,当调用方枚举下一个元素时,它将在yield
语句之后立即在该语句的被调用方方法中继续执行。让我们尝试通过一个示例来理解这一点。在此示例中,与每行相对应,我提到了执行流程的顺序。
static void Main(string[] args)
{
foreach (int fib in Fibs(6))//1, 5
{
Console.WriteLine(fib + " ");//4, 10
}
}
static IEnumerable<int> Fibs(int fibCount)
{
for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
{
yield return prevFib;//3, 9
int newFib = prevFib + currFib;//6
prevFib = currFib;//7
currFib = newFib;//8
}
}
此外,每个枚举的状态都得到维护。假设我再次调用
Fibs()
方法,那么状态将被重置。评论
设置prevFib = 1-第一个斐波那契数是“ 1”,而不是“ 0”
– fubo
2015年3月3日在8:51
#7 楼
列表或数组实现立即加载所有项目,而yield实施提供了延迟执行解决方案。实践中,通常需要根据需要执行最少的工作量,以减少操作量。应用程序的资源消耗。
例如,我们可能有一个应用程序可以处理数据库中的数百万条记录。在延迟执行基于拉的模型中使用IEnumerable时,可以实现以下好处:由于记录数量的增加,可伸缩性,可靠性和可预测性可能会提高不会显着影响应用程序的资源需求。
性能和响应性可能会提高,因为可以立即开始处理,而不是等待整个集合先加载。
由于可以停止,启动,中断或失败应用程序,因此利用率可能会提高。与仅实际使用一部分结果的预取所有数据相比,只有进行中的项目会丢失。
在添加恒定工作负载流的环境中,可以进行连续处理。
这里是先构建列表(如列表)与使用yield之间的比较。
列表示例
public class ContactListStore : IStore<ContactModel>
{
public IEnumerable<ContactModel> GetEnumerator()
{
var contacts = new List<ContactModel>();
Console.WriteLine("ContactListStore: Creating contact 1");
contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
Console.WriteLine("ContactListStore: Creating contact 2");
contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
Console.WriteLine("ContactListStore: Creating contact 3");
contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
return contacts;
}
}
static void Main(string[] args)
{
var store = new ContactListStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection.");
Console.ReadLine();
}
控制台输出
ContactListStore:创建联系人1
ContactListStore:创建联系人2
ContactListStore:创建联系人3
准备遍历集合。
注意:整个集合已加载到内存中,甚至不需要列表中的单个项目。
良率示例
public class ContactYieldStore : IStore<ContactModel>
{
public IEnumerable<ContactModel> GetEnumerator()
{
Console.WriteLine("ContactYieldStore: Creating contact 1");
yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
Console.WriteLine("ContactYieldStore: Creating contact 2");
yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
Console.WriteLine("ContactYieldStore: Creating contact 3");
yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
}
}
static void Main(string[] args)
{
var store = new ContactYieldStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection.");
Console.ReadLine();
}
控制台输出
准备遍历集合。
注意:集合根本没有执行。这是由于IEnumerable的“延迟执行”性质。
让我们再次调用该集合,并在我们获取该集合中的第一个联系人时恢复其行为。
static void Main(string[] args)
{
var store = new ContactYieldStore();
var contacts = store.GetEnumerator();
Console.WriteLine("Ready to iterate through the collection");
Console.WriteLine("Hello {0}", contacts.First().FirstName);
Console.ReadLine();
}
控制台输出
准备遍历集合
ContactYieldStore:创建联系人1
Hello Bob
好!当客户从集合中“拉出”该项目时,仅构造了第一个联系人。
评论
这个答案需要更多关注!谢谢
–leon22
19-10-18在5:49
@ leon22绝对+2
–snr
3月26日4:19
#8 楼
直观地讲,关键字从函数中返回一个值而不离开它,即在您的代码示例中,它返回当前的item
值,然后继续循环。更正式地讲,编译器使用它为迭代器生成代码。迭代器是返回IEnumerable
对象的函数。 MSDN上有几篇关于它们的文章。评论
好吧,确切地说,它不会恢复循环,而是会暂停循环,直到父级调用“ iterator.next()”为止。
– Alex
13年7月10日在12:15
@jitbit这就是为什么我“直观地”和“更正式地”使用的原因。
–康拉德·鲁道夫(Konrad Rudolph)
13年7月10日在12:16
#9 楼
这是理解该概念的简单方法:基本思想是,如果您想要可以使用“
foreach
”的集合,但是由于某些原因将项目收集到集合中会很昂贵(例如查询它们)数据库),并且通常不需要整个集合,然后创建一个函数,一次生成一个集合,然后将其交还给使用者(后者可以尽早终止收集工作)。 > 这样想:您去肉店买一磅火腿片。屠夫将10磅重的火腿放回去,放在切片机上,切成薄片,然后将一堆薄片拿回来给你,并从中取出一磅。 (旧方法)。
使用
yield
,屠夫将切片机带到柜台,开始切片,然后将每个切片“屈服”到秤上,直到测量到1磅重,然后将其包裹起来,您就可以完成。对于屠夫来说,旧方法可能更好(让他按照自己喜欢的方式组织机器),但对于大多数消费者而言,新方法显然更有效。#10 楼
使用yield
关键字,您可以在迭代器块上的形式创建IEnumerable<T>
。该迭代器块支持延迟执行,如果您不熟悉该概念,可能看起来很神奇。但是,最终,只是代码执行而没有任何怪异的技巧。迭代器块可以描述为语法糖,其中编译器生成状态机,该状态机可以跟踪枚举的枚举已进行。要枚举可枚举,通常使用
foreach
循环。但是,foreach
循环也是语法糖。因此,您从真实代码中删除了两个抽象,这就是为什么最初可能很难理解它们如何协同工作的原因。假设您有一个非常简单的迭代器块:
IEnumerable<int> IteratorBlock()
{
Console.WriteLine("Begin");
yield return 1;
Console.WriteLine("After 1");
yield return 2;
Console.WriteLine("After 2");
yield return 42;
Console.WriteLine("End");
}
实际的迭代器块通常具有条件和循环,但是当您检查条件并展开循环时,它们仍最终以
yield
语句与其他代码交错的方式结束。要枚举迭代器块,使用了
foreach
循环:foreach (var i in IteratorBlock())
Console.WriteLine(i);
这里是输出(在这里没有惊喜):
Begin 1 After 1 2 After 2 42 End
如上所述,
foreach
是语法糖:IEnumerator<int> enumerator = null;
try
{
enumerator = IteratorBlock().GetEnumerator();
while (enumerator.MoveNext())
{
var i = enumerator.Current;
Console.WriteLine(i);
}
}
finally
{
enumerator?.Dispose();
}
为了弄清这一点,我创建了一个删除了抽象的序列图:
编译器生成的状态机也实现了枚举器,但是为了使图更清楚,我将它们显示为单独的实例。 (从另一个线程枚举状态机时,实际上确实得到了单独的实例,但是这里的细节并不重要。)
每次调用迭代器块时,都会创建一个状态机的新实例。但是,在第一次执行
enumerator.MoveNext()
之前,不会执行迭代器块中的任何代码。这就是延迟执行的工作方式。这是一个(相当愚蠢的)示例:var evenNumbers = IteratorBlock().Where(i => i%2 == 0);
此时,迭代器尚未执行。
Where
子句创建了一个新的IEnumerable<T>
,它包装了IEnumerable<T>
返回的IteratorBlock
,但是此枚举尚未被枚举。执行foreach
循环时会发生这种情况:foreach (var evenNumber in evenNumbers)
Console.WriteLine(eventNumber);
如果枚举两次,则每次都会创建一个新的状态机实例,并且迭代器块将执行两次相同的代码。
请注意,诸如
ToList()
,ToArray()
,First()
,Count()
等的LINQ方法将使用foreach
循环枚举可枚举值。例如,ToList()
将枚举可枚举的所有元素并将它们存储在列表中。现在,您可以访问列表以获取可枚举的所有元素,而无需再次执行迭代器块。在使用ToList()
之类的方法时,使用CPU多次生成可枚举的元素与使用内存存储枚举的元素以多次访问它们之间需要权衡。#11 楼
如果我正确理解这一点,那么从实现带有yield的IEnumerable的函数的角度来看,这就是我的措辞。这里是一个。
如果需要另一个,请再次调用。
我会记住我已经给你的。
我只知道当你再次打电话时我能否再给你一个。
评论
简单而辉煌
–哈里
4月22日10:10
#12 楼
简而言之,C#yield关键字允许多次调用代码体(称为迭代器),该代码体知道在完成之前如何返回,并在再次调用时从中断处继续执行-即,它有助于迭代器在迭代器连续调用返回的序列中,每个项目都变成透明状态。在JavaScript中,相同的概念称为Generators。
评论
最好的解释呢。这些也是python中相同的生成器吗?
– petrosmm
18年6月15日在13:14
#13 楼
这是为对象创建枚举的非常简单的方法。编译器将创建一个包装您的方法的类,并实现IEnumerable#14 楼
它产生了不可计数的序列。它实际上是在创建本地IEnumerable序列,并将其作为方法结果返回#15 楼
关于Yield关键字的一个主要观点是懒惰执行。现在,我所说的惰性执行是在需要时执行。更好的说法是举一个例子示例:不使用Yield,即没有懒惰执行。
public static IEnumerable<int> CreateCollectionWithList()
{
var list = new List<int>();
list.Add(10);
list.Add(0);
list.Add(1);
list.Add(2);
list.Add(20);
return list;
}
示例:使用Yield,即是懒惰执行。
public static IEnumerable<int> CreateCollectionWithYield()
{
yield return 10;
for (int i = 0; i < 3; i++)
{
yield return i;
}
yield return 20;
}
现在当我同时调用这两个方法时。
var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();
您会注意到listItems里面有5个项目(调试时将鼠标悬停在listItems上)。
而yieldItems仅引用了方法而不是项目。
这意味着它尚未执行在方法内部获取项目的过程。一种非常有效的仅在需要时获取数据的方法。
产量的实际实现可以在ORM中看到,例如Entity Framework和NHibernate等。
#16 楼
该链接有一个简单的示例甚至更简单的示例也在这里
public static IEnumerable<int> testYieldb()
{
for(int i=0;i<3;i++) yield return 4;
}
请注意,收益率不会从该方法中返回。您甚至可以在
WriteLine
之后放置一个yield return
上面的方法产生的IEnumerable为4 int 4,4,4,4
,此处带有
WriteLine
。将添加4到列表中,打印abc,然后添加4到列表中,然后完成该方法,从而真正从该方法返回(一旦该方法完成,就像没有返回的过程一样)。但这将具有一个值,即IEnumerable
的int
列表,该值将在完成时返回。public static IEnumerable<int> testYieldb()
{
yield return 4;
console.WriteLine("abc");
yield return 4;
}
还请注意,当使用yield时,返回的是与功能类型不同。它是
IEnumerable
列表中元素的类型。您可以使用yield,并将该方法的返回类型设为
IEnumerable
。如果该方法的返回类型是int
或List<int>
,并且您使用yield
,则它将不会编译。您可以使用不带yield的IEnumerable
方法返回类型,但似乎没有IEnumerable
方法返回类型就不能使用yield。要使其执行,必须以特殊方式调用它。 static void Main(string[] args)
{
testA();
Console.Write("try again. the above won't execute any of the function!\n");
foreach (var x in testA()) { }
Console.ReadLine();
}
// static List<int> testA()
static IEnumerable<int> testA()
{
Console.WriteLine("asdfa");
yield return 1;
Console.WriteLine("asdf");
}
评论
注意-如果试图理解SelectMany,它将使用yield和泛型。此示例可能有助于公共静态IEnumerable
– barlop
16年5月1日在2:16
看起来很好解释!这可能是公认的答案。
– pongapundit
18年7月11日在15:37
@pongapundit谢谢,我的回答当然很简单明了,但我本人并没有使用yield,其他回答者比我有更多的使用它的经验和知识。我在这里写的yield可能是因为我挠头试图找出此处和该dotnetperls链接的一些答案!但是由于我不知道收益率那么好(除了我提到的简单的东西),而且还没有使用太多,也不了解其用途,所以我认为这不应被接受。
– barlop
18年7月12日在13:09
#17 楼
它正在尝试带来一些Ruby优点:)概念:这是一些示例Ruby代码,可打印出数组的每个元素 rubyArray = [1,2,3,4,5,6,7,8,9,10]
rubyArray.each{|x|
puts x # do whatever with x
}
数组的each方法实现产生控制权数组的每个元素都以x整齐地呈现给调用者(“放入x”)。然后,调用者可以使用x做任何事情。
.Net并不能一路走来。C#似乎已经将yield与IEnumerable耦合在一起,从而迫使您编写孟德尔的回应中显示了调用者中的一个foreach循环。少一点优雅。
//calling code
foreach(int i in obCustomClass.Each())
{
Console.WriteLine(i.ToString());
}
// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
for(int iLooper=0; iLooper<data.Length; ++iLooper)
yield return data[iLooper];
}
评论
-1这个答案对我来说听起来不正确。是的,C#良率与IEnumerable耦合,并且C#缺少Ruby的“块”概念。但是C#具有lambda,可以允许实现ForEach方法,与Ruby的每个方法非常相似。但是,这并不意味着这样做是一个好主意。
–森纳
13年5月15日在19:27
更好的是:public IEnumerable
–ata
2014-10-15 14:19
评论
只需MSDN链接即可在此处msdn.microsoft.com/zh-cn/library/vstudio/9k7k7cf0.aspx这不足为奇。造成混淆的事实是,我们有条件将“返回”视为函数输出,而在其之前没有“收益”。
我已经阅读了文档,但恐怕我还是不明白:(