在“ 如何仅显示IList <>的片段”问题中,答案之一具有以下代码片段:

IEnumerable<object> FilteredList()
{foreach(object item in FullList){if(IsItemInPartialList(item))yield return item;}
}

yield关键字在那里做什么? 我已经在几个地方提到过它,还有另一个问题,但是我还没有弄清楚它的实际作用。 我习惯于从一个线程向另一个线程屈服的角度考虑yield,但这在这里似乎无关紧要。


#1楼

简而言之,C#yield关键字允许多次调用代码体(称为迭代器),该代码体知道在完成之前如何返回,并在再次调用时从中断处继续执行-即,它有助于迭代器迭代器在连续调用中返回的序列中,每个项目的状态变为透明。

在JavaScript中,相同的概念称为生成器。


#2楼

收益有两个重大用途,

  1. 它有助于提供自定义迭代,而无需创建临时集合。

  2. 它有助于进行有状态的迭代。

为了更说明性地解释上述两点,我创建了一个简单的视频,您可以在此处观看


#3楼

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, 9int newFib = prevFib + currFib;//6prevFib = currFib;//7currFib = newFib;//8}
}

同样,为每个枚举维护状态。 假设我再次调用Fibs()方法,则将为其重置状态。


#4楼

乍一看,收益回报是返回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运算符允许根据需要创建项目。 这是使用它的一个很好的理由。


#5楼

列表或数组实现立即加载所有项目,而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
你好鲍勃

真好! 当客户从集合中“拉出”该项目时,仅构造了第一个联系人。


#6楼

该链接有一个简单的例子

更简单的例子在这里

public static IEnumerable<int> testYieldb()
{for(int i=0;i<3;i++) yield return 4;
}

注意,收益率回报不会从方法中回报。 您甚至可以在yield return之后放一个WriteLine

上面产生的IEnumerable为4 int 4,4,4,4

这里有一个WriteLine 。 将添加4到列表中,打印abc,然后添加4到列表中,然后完成该方法,从而真正从该方法返回(一旦该方法完成,就像没有返回的过程一样)。 但这将具有一个值,即intIEnumerable列表,它会在完成时返回。

public static IEnumerable<int> testYieldb()
{yield return 4;console.WriteLine("abc");yield return 4;
}

还要注意,当使用yield时,返回的内容与函数的类型不同。 它是IEnumerable列表中元素的类型。

您可以将yield与方法的返回类型一起使用IEnumerable 。 如果该方法的返回类型为intList<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");
}

#7楼

这是一种理解概念的简单方法:基本概念是,如果您希望可以使用“ foreach ”的集合,但是出于某些原因(例如从数据库中查询它们)将项目收集到集合中会很昂贵。 ,而且您通常不需要整个集合,然后创建一个函数,一次生成一个集合并将其交还给消费者(后者可以尽早终止收集工作)。

这样想:您去肉店买一磅切成薄片的火腿。 屠夫将10磅重的火腿放回去,放在切片机上,切成薄片,然后将一堆薄片带回来给你,并从中取出一磅。 (旧方法)。 有了yield ,屠夫将切片机带到柜台,开始切片并“ yield ”到切片上,直到量到1磅,然后为您包装好。 对于屠夫而言,“旧方式”可能更好(让他按照自己喜欢的方式组织机器),但在大多数情况下,“新方式”对于消费者来说效率更高。


#8楼

迭代。 它在“隐藏”状态下创建一个状态机,该状态机会记住您在该函数的每个其他循环中所处的位置,然后从那里开始。


#9楼

这是为对象创建枚举的非常简单的方法。 编译器将创建一个包装您的方法的类,并实现IEnumerable <object>。 如果没有yield关键字,则必须创建一个实现IEnumerable <object>的对象。


#10楼

它产生了不可计数的序列。 它实际上是在创建本地IEnumerable序列并将其作为方法结果返回


#11楼

直观地,关键字从函数中返回一个值而不离开它,即在您的代码示例中,它返回当前item值,然后恢复循环。 更正式地说,它由编译器用来为迭代器生成代码。 迭代器是返回IEnumerable对象的函数。 MSDN上有几篇关于它们的文章 。


#12楼

yield关键字实际上在这里做了很多工作。

该函数返回一个实现IEnumerable<object>接口的IEnumerable<object> 。 如果调用函数开始foreach此对象,则会再次调用该函数,直到“屈服”为止。 这是C#2.0中引入的语法糖。 在早期版本中,您必须创建自己的IEnumerableIEnumerator对象才能执行此类操作。

理解这样的代码最简单的方法是键入示例,设置一些断点,然后看看会发生什么。 尝试逐步执行此示例:

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;
}

在逐步浏览示例时,您会发现对Integers()的第一次调用返回1 。 第二次调用返回2 ,并且行yield return 1不再执行。

这是一个真实的例子:

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);}}}}
}

#13楼

最近,Raymond Chen也对yield关键字进行了一系列有趣的文章。

  • C#中迭代器的实现及其结果(第1部分)
  • C#中迭代器的实现及其结果(第2部分)
  • C#中迭代器的实现及其结果(第3部分)
  • C#中迭代器的实现及其后果(第4部分)

虽然通常用于轻松实现迭代器模式,但可以将其推广到状态机中。 引用Raymond毫无意义,最后一部分也链接到其他用途(但是Entin博客中的示例特别好,显示了如何编写异步安全代码)。


#14楼

它正在尝试带来一些Ruby Goodness :)
概念:这是一些示例Ruby代码,可打印出数组的每个元素

 rubyArray = [1,2,3,4,5,6,7,8,9,10]rubyArray.each{|x| puts x   # do whatever with x}

阵列的每个方法实现的产率控制到呼叫者(即“放X”)与所述阵列的每个元件整齐地呈现为×。 然后,调用者可以执行x所需的任何操作。

但是.Net并非一路走来。C#似乎已将yield与IEnumerable耦合在一起,以某种方式迫使您在调用方中编写一个foreach循环,如Mendelt的响应所示。 少一点优雅。

//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];
}

#15楼

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()之前,不会执行iterator块中的任何代码。 这就是延迟执行的工作方式。 这是一个(相当愚蠢的)示例:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

此时,迭代器尚未执行。 Where子句创建一个新的IEnumerable<T> ,它包装由IteratorBlock返回的IEnumerable<T> ,但是此枚举尚未被枚举。 当您执行一个foreach循环时,会发生这种情况:

foreach (var evenNumber in evenNumbers)Console.WriteLine(eventNumber);

如果您两次枚举可枚举,则每次都会创建一个新的状态机实例,并且迭代器块将两次执行相同的代码。

请注意,像ToList()ToArray()First()Count()等LINQ方法将使用foreach循环枚举可枚举值。 例如ToList()将枚举可枚举的所有元素并将它们存储在列表中。 现在,您可以访问列表以获取可枚举的所有元素,而无需再次执行迭代器块。 使用诸如ToList()类的方法时,在使用CPU多次生成可枚举的元素与存储用于存储枚举的元素以多次访问它们之间需要权衡。


#16楼

如果我正确理解这一点,那么从实现带有yield的IEnumerable的函数的角度来看,这就是我的措辞。

  • 这是一个
  • 如果您需要其他电话,请再次致电。
  • 我会记得我已经给你的。
  • 当您再次致电时,我只能告诉您是否能再给您另一个。

#17楼

关于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等。

C#中使用的yield关键字是什么?相关推荐

  1. c# yield关键字原理

    https://www.cnblogs.com/blueberryzzz/p/8678700.html c# yield关键字原理详解 1.yield实现的功能 yield return: 先看下面的 ...

  2. python生成器yield原理_生成器yield关键字详解

    鉴于yield关键字的原理大家理解的都不是很深刻,今天我们主要就这一课题进行探讨. 生成器可以用什么方式得到? 方法一: 利用推导式的方式得到生成器# 列表推导式 list1 = [i for i i ...

  3. Python生成器实现及yield关键字

    Python生成器实现及yield关键字 我在另一篇文章中介绍了Python迭代器,https://blog.csdn.net/weixin_43790276/article/details/9034 ...

  4. python function if yield_Python中的yield关键字

    Python中的yield关键字 这是stackoverflow上一个关于yield关键字的问题以及它被推荐次数最高的一个答案 问题: Python中的yield关键字是什么?它是用来做什么的? 例如 ...

  5. 从range和xrange的性能对比到yield关键字(中)

    2019独角兽企业重金招聘Python工程师标准>>> 上节提出了range和xrange的效率问题,这节我们来探究其中的原因 yield的使用 我们看下面的程序: #coding: ...

  6. python里的关键字有哪些_Python中的yield关键字做了什么?

    Python中的yield关键字做了什么 要理解yield做了什么,就必须明白生成器(generators)为何物,而在明白生成器之前还要知道迭代器(iterables). 1.迭代器 当我们创建一个 ...

  7. Java yield详解_Java 中的 yield 关键字

    从 Java 14 开始,yield 关键字已添加到 Java 语言中,用于实现 switch 表达式. 它用于从 switch 表达式中的 case 返回值. 例如: int x = switch ...

  8. Python 中的黑暗角落(一):理解 yield 关键字

    Python 是非常灵活的语言,其中 yield 关键字是普遍容易困惑的概念. 此篇将介绍 yield 关键字,及其相关的概念. 迭代.可迭代.迭代器 迭代(iteration)与可迭代(iterab ...

  9. Python中的yield关键字及表达式、生成器、生成器迭代器、生成器表达式详解

    文章目录 1. yield关键字及表达式.生成器.生成器迭代器.生成器表达式 1.1 yield关键字及表达式(yield expression) 1.1.1 yield关键字 1.1.2 yield ...

最新文章

  1. c++引用另一个类的方法_VlookUp函数使用方法,一张表引用另一张表的数据。
  2. Go 2将添加错误处理和泛型
  3. 在CSDN中增加图片版权保护的方法
  4. 英特尔披露人工智能战略
  5. 关于 /dev/null 与 /dev/zero
  6. VC++动态创建和删除菜单(转)
  7. Hadoop之HDFS概述
  8. 深入理解计算机操作系统:链接笔记
  9. 空间皮肤代码_不废话,看我20行代码搞定色块提取与定位…….
  10. 程序员到底会不会修电脑?
  11. 学习TeXworks编辑器(二)TAB补全详解与自定义补全命令总结
  12. 腾讯测试王者荣耀网速的软件,腾讯游戏启用账号时长共享功能 开启人脸识别测试有效果吗?以《王者荣耀》为试点...
  13. ai怎么画路线_AI换脸的本质是把颜值和表情分开
  14. 【学术杂谈】博士毕业都去哪儿了,谈谈学术的这条路该如何走
  15. FixedLengthFrameDecoder 固定长度解码器,解决 TCP 粘包
  16. 全网首发:LINUX编译JNA:编译libffi
  17. 链路层发现协议LLDP
  18. 基2FFT算法matlab程序编写,基2时抽8点FFT的matlab实现流程及FFT的内部机理
  19. vb文件服务器例程,利用VB进行服务器编程实例汇总.docx
  20. C语言自学之路五(选择语句详解)

热门文章

  1. Android 页面进行镜像反转-面试
  2. Android ProgressBar 不能在Button上面显示
  3. Java Socket 编程
  4. Android之ActivityManagerService详解(APP启动过程)
  5. 创建型模式--原型模式
  6. codeforces524E
  7. windows 如何配置 Go 环境(Zip archive 方式)?
  8. 2018牛客网暑假ACM多校训练赛(第二场)E tree 动态规划
  9. 洛谷1941飞扬的小鸟
  10. ado.net操作数据库常用方法集锦