C#基础之yield与Singleton

1.实例解析yiled的作用

最近参加java笔试题第一次见到yield这个关键字,既然遇见了那肯定要掌握,下面是c#中关于yield关键字的总结。yield这个关键字作用于迭代器块中,其最本质的功能有2个:一是“依次”向枚举对象提供值,二是发出迭代结束信号。这两个功能对应的语句分别是yield return和yield break。

下面有2个小例子,分别没有使用yield和有使用yield。先来看第一个,当我调试时显然执行到GetResult()方法时将会跳转到方法内部并且执行完,接着再去执行输出当前值语句。从结果可以看出第一个是0,说明返回的枚举数所在的位置在集合中是0,接着才是我想要的遍历数据,也就是说只有调用MoveNext()后枚举数才会继续向前移动得到下一个值,但是此时数据已全部加载到内存。

再来看第二个例子,当我调试到GetResultByYield()方法时我想进入到这个方法内部结果发现直接执行下一句,根本就没有进入到GetResultByYield()方法内部。此时发现result.Current是null,再加上前面根本都没执行方法内部的代码,因此我猜测此时集合都是空的。继续调试,当执行MoveNext()方法时才去执行GetResultByYield(),接着执行到yield return随即返回main()方法输出枚举数所代表的集合中的值。

从上面可以看到只有调用MoveNext()需要用的时候才去执行方法来获得结果,不用的时候并不会有任何结果。这个地方编译器会有一个状态机用来保存迭代器的状态,以保证for循环时是从上一次yield return停止的状态继续执行。这个过程就好比小方要喝一升的水,如果它用一个一升的杯子喝那么他要准备一个一升的容器去饮水机装满一升的水。

如果小方喝到一半喝不完了,那接下来剩下的水则将被回收,这样无论能不能喝完都必须准备好一升的水,就像第一个例子。现在让杯子的容积缩小为0.2升,小方喝完一杯后再去饮水机去打水,每次只喝0.2升。这样只有他要去喝的时候才去打水,如果他喝到一半不想喝了显然浪费的水比第一种方式多,这就像第二个例子。最后根据条件不再需要数据便可调用yield return来跳出while循环,如果不写yield break仍然可以正常结束迭代。

///    /// 不使用yield的时候    ///    class Program    {        static void Main(string[] args)        {            //得到一个迭代结果            var result = GetResult();            //输出当前的值            Console.WriteLine(result.Current);            Console.WriteLine("开始遍历");            while (result.MoveNext())            {                Console.WriteLine(result.Current);            }            Console.WriteLine("遍历结束");            Console.ReadLine();        }        //不使用yield来进行迭代        static IEnumeratorint> GetResult()        {            var arr = new int[] { 1, 6, 8, 12,15};            Listint> list = new Listint>();            foreach (int item in arr)            {                if (item 12)                    list.Add(item);            }            return list.GetEnumerator();        }     }///    /// 使用yield关键字    ///    class Program    {        static void Main(string[] args)        {            //得到一个迭代结果            var result = GetResultByYield();            //输出当前的值            Console.WriteLine(result.Current);            Console.WriteLine("开始遍历");            while (result.MoveNext())            {                Console.WriteLine(result.Current);            }            Console.WriteLine("遍历结束");            Console.ReadLine();          }        //使用yield来进行迭代        static IEnumerator GetResultByYield()        {            var arr = new int[] { 1,6,8,12,15};            foreach (var item in arr)            {                yield return item;                if (item == 12)                    yield break;            }        }     }

输出结果如下:

987.png

988.png

2.深入yield

将上面第二个例子放入Reflector工具中,便得到了下面三段代码。第一段是完整的Pragrom类的C#代码,第二段是d__0密封类的C#展开代码,第三段是GetResultByYield()方法的IL代码。在第一段代码中可以看到系统自动生成了一个d__0密封类,它里面声明了一些名字很奇怪的字段,不过我们可以很清楚的看到这个类里面有最重要的MoveNext()方法和Current属性。

第二段代码则是这个密封类的C#展开代码,到这里不知道读者有没有和我当初一样的疑问:为什么要自动生成一个密封类呢?答案就在第三段代码中,可以看到在GetResultByYield()方法中并没有遍历数组,甚至都没有看到创建数组的newarr指令,而是newobj创建了d__0密封类的实例对象。这也正是前面调试的时候为什么根本就没进去GetResultByYield()方法的原因,因为真真的实现代码是在密封类里面的MoveNext()方法中。前面还提到yield是按需所取,因此需要一个状态机来记录每次yield return的状态。

在MoveNext()方法中由于密封类构造函数传进去的是一个0(在第三段代码中可以看到),因此第一次进入到MoveNext方法时this.__state=0。此时current字段由于没赋值因此就是null了。接着创建数组并开始一个while循环(原来foreach就是while循环),在循环中给current字段赋值并让state字段值为2,最后返回true。拿Current属性时就是拿while循环中给current赋的值,再次进入这个方法内此时state等于2于是跳转到Label_0090,也就是进入while语句块中继续循环,这就是按需所取的原理。当遇到yield break后会先执行Dispose释放资源,再执行break语句跳出循环。可以看到上述这个过程就是一个状态机,而这个密封类是为建立一个状态机来生成的,现在我们自己也可以写出一个状态机了。

internal class Program{    // Methods    public Program();    private static IEnumerator GetResultByYield();    private static void Main(string[] args);     // Nested Types    [CompilerGenerated]    private sealed class d__0 : IEnumeratorobject>, IEnumerator, IDisposable    {        // Fields        private int 1__state;        private object 2__current;        public int[] 7__wrap4;        public int 7__wrap5;        public int[] 5__1;        public int 5__2;         // Methods        [DebuggerHidden]        public d__0(int 1__state);        private void m__Finally3();        private bool MoveNext();        [DebuggerHidden]        void IEnumerator.Reset();        void IDisposable.Dispose();         // Properties        object IEnumeratorobject>.Current { [DebuggerHidden] get; }        object IEnumerator.Current { [DebuggerHidden] get; }    }}private sealed class d__0 : IEnumeratorobject>, IEnumerator, IDisposable{    // Fields    private int 1__state;    private object 2__current;    public int[] 7__wrap4;    public int 7__wrap5;    public int[] 5__1;    public int 5__2;     // Methods    [DebuggerHidden]    public d__0(int 1__state)    {        this.1__state = 1__state;    }     private void m__Finally3()    {        this.1__state = -1;    }     private bool MoveNext()    {        try        {            switch (this.1__state)            {                case 0:                    this.1__state = -1;                    this.5__1 = new int[] { 1, 6, 8, 12, 15 };                    this.1__state = 1;                    this.7__wrap4 = this.5__1;                    this.7__wrap5 = 0;                    while (this.7__wrap5 this.7__wrap4.Length)                    {                        this.5__2 = this.7__wrap4[this.7__wrap5];                        this.2__current = this.5__2;                        this.1__state = 2;                        return true;                    Label_0090:                        this.1__state = 1;                        if (this.5__2 == 12)                        {                            this.System.IDisposable.Dispose();                            break;                        }                        this.7__wrap5++;                    }                    this.m__Finally3();                    break;                 case 2:                    goto Label_0090;            }            return false;        }        fault        {            this.System.IDisposable.Dispose();        }    }     [DebuggerHidden]    void IEnumerator.Reset()    {        throw new NotSupportedException();    }     void IDisposable.Dispose()    {        switch (this.1__state)        {            case 1:            case 2:                this.m__Finally3();                break;        }    }     // Properties    object IEnumeratorobject>.Current    {        [DebuggerHidden]        get        {            return this.2__current;        }    }     object IEnumerator.Current    {        [DebuggerHidden]        get        {            return this.2__current;        }    }} .method private hidebysig static class [mscorlib]System.Collections.IEnumerator GetResultByYield() cil managed    {        .maxstack 1        .locals init (            [0] class ConsoleApplication1.Program/d__0 d__,            [1] class [mscorlib]System.Collections.IEnumerator enumerator)        L_0000: ldc.i4.0        L_0001: newobj instance void ConsoleApplication1.Program/d__0::.ctor(int32)        L_0006: stloc.0        L_0007: ldloc.0        L_0008: stloc.1        L_0009: br.s L_000b        L_000b: ldloc.1        L_000c: ret    }

3.单例模式

单例模式没什么好说的,当然如果深挖应该也是大有学问,其中我觉得比较好的一种写法如下。单例模式的代码我看过多次不过却没怎么写,结果真真写的时候再加上时间又有点紧最后写的一塌糊涂。以后写代码要兴平气和地去写,急躁的状态写不出什么好代码。当然总会有烦躁的时候,所以只能多写代码来让自己写出高质量的代码成为一种习惯!

class A    {        private static A instance = new A();        public static A Instance        {            get { return A.instance; }        }    }

以上就是C#基础之yield与Singleton的内容,更多相关内容请关注PHP中文网(www.php.cn)!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1432158.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月17日 06:17:18
下一篇 2025年12月17日 06:17:48

相关推荐

  • c++中的SFINAE技术是什么_c++模板编程中的SFINAE原理与应用

    SFINAE 是“替换失败不是错误”的原则,指模板实例化时若参数替换导致错误,只要存在其他合法候选,编译器不报错而是继续重载决议。它用于条件启用模板、类型检测等场景,如通过 decltype 或 enable_if 控制函数重载,实现类型特征判断。尽管 C++20 引入 Concepts 简化了部分…

    2026年5月10日
    000
  • c#文件怎么打开

    打开 C# 文件有三种方法:Visual Studio:启动 Visual Studio,通过“文件”菜单打开 C# 文件。文本编辑器:使用文本编辑器打开 C# 文件,将其视为普通文本。.NET Core 命令行工具:使用 csc.exe 命令行工具编译 C# 文件,生成可执行文件。 如何打开 C#…

    2026年5月10日
    000
  • c++如何实现UDP通信_c++基于UDP的网络通信示例

    UDP通信基于套接字实现,适用于实时性要求高的场景。1. 流程包括创建套接字、绑定地址(接收方)、发送(sendto)与接收(recvfrom)数据、关闭套接字;2. 服务端监听指定端口,接收客户端消息并回传;3. 客户端发送消息至服务端并接收响应;4. 跨平台需处理Winsock初始化与库链接,编…

    2026年5月10日
    100
  • 函数指针在 C++ 多态中的作用:揭示多态背后的真相

    函数指针在 C++ 多态中的作用:揭示多态背后的真相 简介 多态是面向对象编程的一项强大功能,它允许对象在运行时以不同的方式表现。C++ 中的多态实现依赖于函数指针。本文将深入探讨函数指针在多态中的作用,并通过一个实战案例展示如何利用它们。 函数指针 立即学习“C++免费学习笔记(深入)”; 函数指…

    2026年5月10日
    000
  • C++框架与Java框架在易用性方面的比较

    c++++ 框架的易用性低于 java 框架,具体原因如下:c++ 框架学习曲线陡峭,需要深入理解 c++ 语言。易出错且调试困难。而 java 框架具有以下易用性优势:学习曲线低,尤其适合 java 初学者。提供丰富的库和工具,简化开发。运行时异常处理,简化异常处理。 C++ 框架与 Java 框…

    2026年5月10日
    000
  • c++中头文件和源文件的区别_c++头文件与源文件作用对比

    头文件声明接口,源文件实现逻辑。头文件含类、函数声明及宏定义,通过#include被多文件共享,用include守卫防重;源文件实现具体功能,编译为目标文件后由链接器合并。声明与实现分离提升模块化与编译效率,模板和内联函数因需编译时可见故常置于头文件,命名空间避免符号冲突,整体结构使项目更清晰易维护…

    2026年5月10日
    000
  • C++ 函数重载在事件驱动的编程中的应用

    在事件驱动的编程中,函数重载可创建具有不同参数签名的相似功能,为单一函数名提供多样化功能。它包含以下优点:代码可读性:使用单一函数名表示相关任务。可维护性:避免重复编写类似逻辑。可重用性:跨项目和应用程序 reutilizar。 C++ 函数重载在事件驱动的编程中的应用 在事件驱动的编程中,函数重载…

    2026年5月10日
    000
  • C++ 函数性能优化对系统稳定性的影响

    标题:C++ 函数性能优化对系统稳定性的影响 简介 函数性能优化是 C++ 程序员提高程序效率的关键技术。本文将探讨函数性能优化对系统稳定性的影响,并提供实战案例来证明这一点。 性能优化对稳定性的作用 立即学习“C++免费学习笔记(深入)”; 函数性能优化不仅可以提升程序速度,还可以提高系统的稳定性…

    2026年5月10日
    000
  • WebAssembly中导入JavaScript函数:无胶水代码集成指南

    本文深入探讨了在WebAssembly模块中直接导入和使用JavaScript函数的机制,特别是当使用Emscripten的STANDALONE_WASM和SIDE_MODULE编译模式时。文章详细分析了TypeError: import object field ‘GOT.mem&#8…

    2026年5月10日
    000
  • C++如何编译和链接_C++从源码到可执行文件的过程解析

    c++kquote>预处理展开宏和头文件,编译生成汇编代码,汇编转为机器码,链接合并目标文件与库生成可执行程序。 当你写完一段C++代码,比如一个简单的hello world程序,最终能运行起来,背后其实经历了一系列步骤:预处理、编译、汇编和链接。这个过程将人类可读的源码转换成机器可以执行的程…

    2026年5月10日
    000
  • c++中sizeof运算符的用法和常见陷阱 _c++ sizeof使用技巧及陷阱解析

    sizeof运算符在编译时计算类型或对象的字节大小,返回size_t类型,常用于获取数据大小、数组元素个数及内存操作;但存在数组传参退化为指针导致失效、对指针无法获知动态内存大小、表达式不求值、结构体因对齐产生填充等常见陷阱;需结合模板、显式传参、对齐控制等方式规避问题,提升代码可移植性和安全性。 …

    2026年5月10日
    000
  • C#如何进行网络编程?Socket与TCP/IP通信编程实例详解

    C#通过Socket类实现TCP通信,首先服务器绑定IP和端口并监听,客户端发起连接,双方通过Send/Receive收发数据,最后关闭连接。 C# 进行网络编程主要依赖于 System.Net 和 System.Net.Sockets 命名空间,其中最核心的是使用 Socket 类实现基于 TCP…

    2026年5月10日
    000
  • C++ 函数递归详解:递归查找列表中的元素

    递归查找列表元素的步骤如下:递归基础条件:如果列表为空,则元素不存在。递归过程:使用递归调用查找列表的剩余部分,并调整返回的索引。检查列表的第一个元素:如果第一个元素与所查找的元素相等,则元素位于索引 0 处。找不到:如果递归和第一个元素检查都没有找到,则元素不存在。 C++ 函数递归详解:递归查找…

    2026年5月10日
    000
  • C++怎么使用C++17的并行算法库_C++ std::execution与多核性能优化

    c++kquote>C++17通过std::execution策略引入并行算法支持,需编译器(如GCC 8+)和线程库(如TBB)配合;提供seq、par、par_unseq三种策略控制执行模式;可用于sort、for_each等算法提升大数据性能,但需避免数据竞争,推荐使用reduce等安全…

    2026年5月10日
    000
  • c++ lambda表达式怎么写 c++匿名函数用法详解

    答案是lambda表达式可简洁定义匿名函数,用于STL算法等场景。其语法包含捕获列表、参数列表、mutable、返回类型和函数体,如[=](int x) { return x > 0; }可值捕获外部变量并用于判断正数。 在C++中,lambda表达式是一种创建匿名函数的简洁方式,常用于需要传…

    2026年5月10日
    200
  • C++框架的Unlicense许可类型简介

    unlicense 许可证类型为免费且宽松,允许用户在不附加任何限制的情况下使用、修改和分发软件。它旨在最大限度地减少限制和允许最大的自由度,具有以下好处:简洁易懂高度开放无保证 C++ 框架的 Unlicense 许可证类型简介 了解 Unlicense Unlicense 是一个自由和宽松的软件…

    2026年5月10日
    000
  • 利用日志记录增强 C++ 函数的调试能力

    如何利用日志记录增强 c++++ 函数的调试能力?使用 glog 库进行日志记录: 安装 glog,并在代码中使用 glog 头文件和 initgooglelogging() 初始化日志记录。添加日志记录语句: 使用 log() 宏在要记录的代码块中添加日志记录语句,以记录函数开始、结束或其他重要事…

    2026年5月10日
    000
  • C++ 函数模板如何使用并在实际场景中应用?

    函数模板允许您定义可以处理不同类型参数的函数的通用版本。语法为:template,其中 t 是类型参数。要使用函数模板,请指定所需的参数类型,例如:max(10, 20)。函数模板在排序等实际应用中很有用,例如:template void sort(t arr[], int size)。它们具有通用…

    2026年5月10日
    000
  • C++ 并发编程中内存访问问题及解决方法?

    在 c++++ 并发编程中,共享内存访问问题包括数据竞争、死锁和饥饿。解决方案有:原子操作:确保对共享数据的访问是原子性的。互斥锁:一次只允许一个线程访问临界区。条件变量:线程等待某个条件满足。读写锁:允许多个线程并发读取,但只能允许一个线程写入。 C++ 并发编程中的内存访问问题及解决方案 在多线…

    2026年5月10日
    000
  • c++如何实现函数的重载_c++函数重载实现方法

    函数重载通过参数列表差异实现,如类型、数量或顺序不同,编译器根据实参选择对应函数,返回类型不同不能单独用于重载。 在C++中,函数重载允许在同一作用域内定义多个同名函数,只要它们的参数列表不同(参数个数、类型或顺序不同),编译器会根据调用时传入的实参来选择匹配的函数。函数重载不能仅通过返回类型的不同…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信