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

Her*_*rms 781 c# yield

在" 我如何仅公开IList的片段<>问题"中,其中一个答案包含以下代码段:

IEnumerable<object> FilteredList()
{
    foreach(object item in FullList)
    {
        if(IsItemInPartialList(item))
            yield return item;
    }
}
Run Code Online (Sandbox Code Playgroud)

yield关键字有什么作用?我已经看到它在几个地方被引用,另外一个问题,但我还没弄清楚它实际上做了什么.我习惯于在一个线程产生另一个线程的意义上考虑收益率,但这似乎并不重要.

Men*_*elt 697

yield关键字实际上在这里做了很多.该函数返回一个实现IEnumerable接口的对象.如果一个调用函数开始对该对象进行预处理,则再次调用该函数,直到它"产生"为止.这是C#2.0中引入的语法糖.在早期版本中,您必须创建自己的IEnumerable和IEnumerator对象来执行此类操作.

理解这样的代码的最简单方法是键入一个示例,设置一些断点并查看会发生什么.

尝试单步执行此操作,例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

当您单步执行该示例时,您将发现对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);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在这种情况下,这将更容易,我只是在这里使用整数来显示收益率回报是如何工作的.使用yield return的好处是它是实现迭代器模式的一种非常快速的方法,因此对事物进行了懒惰的评估. (107认同)
  • 另外值得注意的是,当您不想再返回任何项目时,可以使用`yield break;`. (104认同)
  • `'如果一个调用函数开始对该对象进行预处理,则再次调用该函数,直到它"产生"'`.听起来不对我.我总是在"农作物丰收"的背景​​下考虑c#yield关键词,而不是"汽车对行人的收益". (6认同)
  • `yield`不是关键字.如果是那时我就不能使用yield作为标识符,如`int yield = 500;` (5认同)
  • @BrianLeeming从技术上来说,调用`Integers()`会返回一个`IEnumerable <int>`;) (3认同)
  • @Brandin,因为所有编程语言都支持两种类型的关键字,即保留和上下文.yield落在后面的类别中,这就是C#编译器不禁止你的代码的原因.更多详细信息:https://ericlippert.com/2009/05/11/reserved-and-contextual-keywords/您会很高兴知道还有一些保留字,这些字不会被语言识别为关键字.例如,在java中转到goto.更多细节:http://stackoverflow.com/questions/2545103/is-there-a-goto-statement-in-java (3认同)
  • @pongapundit也许您需要更多地训练自己的直觉。首先,编程语言的许多高级功能是违反直觉的。 (3认同)
  • Zack,在这种情况下,return 是提供者,而 yield 是控制流暂停和控制本身被放弃的标记。IEnumerable 将控制权交给调用上下文/循环,例如您的汽车和行人,而 return 携带当前状态有效负载,即您示例中的作物。是的 return 在很大程度上放弃了控制权,但 yield 就像在 return 点创建的 goto,在下一次调用时被遵循;在最后一个产生控制的地方恢复,而不是在传统返回的情况下放弃它。 (2认同)

Joe*_*orn 358

迭代.它创建了一个"幕后"的状态机,可以记住你在函数的每个附加周期中的位置并从中获取.


Shi*_*ala 196

产量有两大用途,

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

  2. 它有助于进行有状态迭代. 在此输入图像描述

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

  • 该视频帮助我清楚地了解`yield`.@ ShivprasadKoirala的代码项目文章[C#Yield的用途是什么?](http://www.codeproject.com/Articles/575713/What-is-the-use-of-csharp-Yield-keyword)同样的解释也是一个很好的来源 (7认同)
  • 很棒的视频,但想知道......使用yield的实现显然更干净,但它本质上必须在内部创建自己的临时内存或/和列表,以便跟踪状态(或者更确切地说创建状态机)。那么,“Yield”除了让实现更简单、让事情看起来更好之外,还有其他作用吗?还是还有其他作用呢?效率如何,使用 Yield 运行代码与不使用 Yield 相比,效率/速度是更高还是更低? (6认同)

Sve*_*end 133

最近,Raymond Chen还在yield关键字上发表了一系列有趣的文章.

虽然它名义上用于轻松实现迭代器模式,但可以推广到状态机.没有必要引用Raymond,最后一部分也链接到其他用途(但Entin的博客中的例子非常好,显示了如何编写异步安全代码).

  • 第1部分解释了"收益率回报"的句法糖.优秀的解释! (3认同)

Mar*_*eli 86

乍一看,yield return是一个返回IEnumerable的.NET糖.

如果没有yield,则会立即创建集合的所有项:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

使用yield的相同代码,它逐项返回:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}
Run Code Online (Sandbox Code Playgroud)

使用yield的优点是,如果消耗数据的函数只需要集合的第一项,则不会创建其余项.

yield操作符允许根据需要创建项目.这是使用它的一个很好的理由.


RKS*_*RKS 38

yield return与枚举器一起使用.在每次调用yield语句时,控制权返回给调用者,但它确保维持被调用者的状态.因此,当调用者枚举下一个元素时,它会在语句后面的yield语句中继续执行callee方法.

让我们试着通过一个例子来理解这一点.在这个例子中,对应于每一行,我已经提到了执行流程的顺序.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

此外,每个枚举都保持状态.假设,我有另一个Fibs()方法调用,然后状态将被重置为它.

  • 对于那些想知道的人,纤维=斐波纳契数. (4认同)
  • set prevFib = 1 - 第一个斐波那契数是“1”,而不是“0” (3认同)

Kon*_*lph 31

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

  • @jitbit这就是我使用"直觉"和"更正式"的原因. (8认同)
  • 好吧,确切地说它不会恢复循环,它会暂停它直到父调用"iterator.next()". (4认同)

Str*_*dom 27

列表或数组实现立即加载所有项,而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();
    }
Run Code Online (Sandbox Code Playgroud)

控制台输出
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();
}
Run Code Online (Sandbox Code Playgroud)

控制台输出
准备迭代整个集合.

注意:集合根本没有执行.这是由于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();
}
Run Code Online (Sandbox Code Playgroud)

控制台输出
准备迭代集合
ContactYieldStore:创建联系人1
Hello Bob

太好了!当客户端将项目"拉出"集合时,仅构建了第一个联系人.

  • 这个答案需要更多关注!谢谢 (3认同)

kmo*_*ote 25

这是一个理解这个概念的简单方法:基本思想是,如果你想要一个可以使用" foreach"的集合,但由于某种原因(比如从数据库中查询它们)将项目收集到集合中是昂贵的,并且您通常不需要整个集合,然后您创建一个函数,一次构建一个项目并将其返回给消费者(然后可以提前终止收集工作).

可以这样想:你去肉类柜台,想要买一磅切好的火腿.屠夫把一个10磅重的火腿放在后面,把它放在切片机上,切成整片,然后将一堆切片带回给你,并测出一磅.(旧方式).随着yield,屠夫将切片机带到柜台,然后开始切片并"切割"每个切片到秤上,直到它测量到1磅,然后为你包裹它,你就完成了.对于屠夫而言,旧方式可能更好(让他按照自己喜欢的方式组织他的机器),但对于消费者而言,新方式在大多数情况下显然更有效.


Mar*_*age 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");
}
Run Code Online (Sandbox Code Playgroud)

真正的迭代器块通常具有条件和循环,但是当您检查条件并展开循环时,它们仍然最终作为yield与其他代码交错的语句.

要枚举迭代器块,使用foreach循环:

foreach (var i in IteratorBlock())
    Console.WriteLine(i);
Run Code Online (Sandbox Code Playgroud)

这是输出(这里没有惊喜):

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();
}
Run Code Online (Sandbox Code Playgroud)

为了解开这个问题,我创建了一个删除了抽象的序列图:

C#迭代器块序列图

编译器生成的状态机也实现了枚举器,但为了使图更清晰,我将它们显示为单独的实例.(当从另一个线程枚举状态机时,您实际上会获得单独的实例,但这里的详细信息并不重要.)

每次调用迭代器块时,都会创建一个新的状态机实例.但是,迭代器块中的所有代码都不会执行,直到enumerator.MoveNext()第一次执行.这是延迟执行的工作原理.这是一个(相当愚蠢)的例子:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);
Run Code Online (Sandbox Code Playgroud)

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

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);
Run Code Online (Sandbox Code Playgroud)

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

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


Cas*_*mer 14

如果我理解正确的话,这就是我如何从使用yield实现IEnumerable的函数的角度来说明这一点.

  • 这是一个.
  • 如果您需要另一个,请再次致电
  • 我会记得我已经给你的东西.
  • 我再次打电话时,我才会知道是否能给你另一个.

  • 简单而精彩 (4认同)

Boi*_*ked 10

简单地说,C#yield关键字允许对一个代码体(称为迭代器)的多次调用,它知道如何在它完成之前返回,并且当再次调用时,继续它停止的地方 - 即它有助于迭代器对迭代器在连续调用中返回的序列中的每个项目变为透明状态.

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


max*_*pan 10

关于 Yield 关键字的一大要点是Lazy Execution。现在我所说的懒惰执行是在需要时执行。一个更好的表达方式是举个例子

示例:不使用 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;
}
Run Code Online (Sandbox Code Playgroud)

示例:使用 Yield 即延迟执行。

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

    yield return 20;
}
Run Code Online (Sandbox Code Playgroud)

现在当我调用这两种方法时。

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();
Run Code Online (Sandbox Code Playgroud)

您会注意到 listItems 里面有 5 个项目(调试时将鼠标悬停在 listItems 上)。而 yieldItems 只会引用方法而不是项目。这意味着它没有执行在方法中获取项目的过程。一种仅在需要时获取数据的非常有效的方法。yield 的实际实现可以在 ORM 中看到,如实体框架和 NHibernate 等。


小智 6

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


aku*_*aku 6

它正在产生可枚举的序列。它所做的实际上是创建本地 IEnumerable 序列并将其作为方法结果返回


bar*_*lop 5

这个链接有一个简单的例子

更简单的例子在这里

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

请注意,yield return 不会从该方法返回。你甚至可以WriteLine在后面加上一个yield return

上面生成了 4 个整数 4,4,4,4 的 IEnumerable

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

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

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

您可以将yield 与方法的返回类型一起使用IEnumerable。如果该方法的返回类型是intorList<int>并且您使用yield,则它将无法编译。您可以使用IEnumerable没有yield的方法返回类型,但似乎您不能在没有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");
}
Run Code Online (Sandbox Code Playgroud)