最近写代码为了为了省事儿用了几个yield return,因为我不想New一个List<T>或者T[]对象再往里放元素,就直接返回IEnumerable<T>了。我的代码里还有很多需要Dispose的对象,所以又用了几个using。写着写着我有点心虚了——这样混合使用靠谱吗?

今天我花时间研究一下,并在这里作个笔记,跟大家分享。笔者水平有限,有哪些理解错误或做的不到位的地方,还请各位专家点拨。

这是我写的方法,循环外面一个using,整个方法里代码执行后释放一个对象。循环里面又一个using, 每次循环yield return后要释放一个对象。那是不是任何情况下这些[被创建了的需要释放的]DisposableObject对象最后都会被释放呢?

        private static IEnumerable<int> GetNumbers(int count)
{
using (DisposableObject parentDisposableObject = new DisposableObject("ParentDisposableObject"))
{
foreach (int number in Enumerable.Range(, count))
{
using (DisposableObject childDisposableObject = new DisposableObject(string.Format("ChildDisposableObject{0}", number)))
{
//if (number == 4)
//{
// throw new Exception("异常。");
//}
if (number != )
{
yield return number * ;
}
else
{
Console.WriteLine(" 循环{0} else 代码执行了", number.ToString());
}
Console.WriteLine(" 循环{0}else下面的代码执行了", number.ToString());
}
}
}
}
}

需要释放资源的类定义如下,创建对象和释放时都有输出。

class DisposableObject : IDisposable
{
private string _value;
public DisposableObject(string value)
{
_value = value;
Console.WriteLine("Create Object {0}", _value);
}
public void Dispose()
{
Console.WriteLine("Disposable Object {0}", _value);
}
}

这里调用下:

        static void Main(string[] args)
{
foreach (int number in GetNumbers())
{
Console.WriteLine("结果 {0}", number.ToString());
}
}

看看运行结果:

我们可以看到:1、循环外面的对象和循环里面的DisposableObject对象都被释放了,这个让我很高兴,要的就是这个效果;2,如果yield return后面还有代码,[yield] return后还会继续执行;3,if-else有作用,不满足条件可以不把该项作为结果返回,不想执行某段代码可以放{}里。这个运行的结果我很满意,就是我想要的!

下面我把抛异常的代码注释去掉,看看循环内抛出的异常后能否正常释放对象。

结果很完美,担忧是多余的,该释放的DisposableObject对象都被释放了!

那么我们简单研究下yield return吧,我写了下面最简单的代码:

        private static IEnumerable<int> GetNumbers(int[] numbers)
{
foreach (int number in numbers)
{
yield return number*10;
}
}

把项目编译再反编译成C#2.0,发现代码变成了这个样子:

private static IEnumerable<int> GetNumbers(int[] numbers)
{
<GetNumbers>d__0 d__ = new <GetNumbers>d__0(-);
d__.<>3__numbers = numbers;
return d__;
}

这里的<GetNumbers>d__0是个自动生成的类(看来这是高热量的语法糖,吃的是少了,程序集却发胖了!),它实现了IEnumerable<T>,IEnumerator<T>等接口,而上面方法其实就是返回了一个封装了迭代器块代码的计数对象而已,如果您仅仅调用了一下上面这个方法,它可能不会执行循环中的代码,除非触发了返回值的MoveNext方法,这就是传说中的延迟求值吧!

[CompilerGenerated]
private sealed class <GetNumbers>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
// Fields
private int <>1__state;
private int <>2__current;
public int[] <>3__numbers;
public int[] <>7__wrap3;
public int <>7__wrap4;
private int <>l__initialThreadId;
public int <number>5__1;
public int[] numbers; // Methods
[DebuggerHidden]
public <GetNumbers>d__0(int <>1__state);
private void <>m__Finally2();
private bool MoveNext();
[DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator();
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator();
[DebuggerHidden]
void IEnumerator.Reset();
void IDisposable.Dispose(); // Properties
int IEnumerator<int>.Current { [DebuggerHidden] get; }
object IEnumerator.Current { [DebuggerHidden] get; }
} Expand Methods

通过MSIL查看上面的foreach循环会调用MoveNext方法。

entrypoint
.maxstack
.locals init (
[] int32 number,
[] class [mscorlib]System.Collections.Generic.IEnumerator`<int32> CS$$)
L_0000: ldc.i4.
L_0001: call class [mscorlib]System.Collections.Generic.IEnumerable`<int32> ConsoleApplication1.Program::GetNumbers(int32)
L_0006: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`<!> [mscorlib]System.Collections.Generic.IEnumerable`<int32>::GetEnumerator()
L_000b: stloc.
L_000c: br.s L_0026
L_000e: ldloc.
L_000f: callvirt instance ! [mscorlib]System.Collections.Generic.IEnumerator`<int32>::get_Current()
L_0014: stloc.
L_0015: ldstr "\u7ed3\u679c\uff1a{0}"
L_001a: ldloca.s number
L_001c: call instance string [mscorlib]System.Int32::ToString()
L_0021: call void [mscorlib]System.Console::WriteLine(string, object)
L_0026: ldloc.
L_0027: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_002c: brtrue.s L_000e
L_002e: leave.s L_003a
L_0030: ldloc.
L_0031: brfalse.s L_0039
L_0033: ldloc.
L_0034: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0039: endfinally
L_003a: ret
.try L_000c to L_0030 finally handler L_0030 to L_003a

而循环里面的执行内容都在MoveNext方法里。

private bool MoveNext()
{
try
{
switch (this.<>1__state)
{
case :
this.<>1__state = -;
this.<parentDisposableObject>5__1 = new DisposableObject("ParentDisposableObject");
this.<>1__state = ;
this.<>7__wrap5 = Enumerable.Range(, this.count).GetEnumerator();
this.<>1__state = ;
while (this.<>7__wrap5.MoveNext())
{
this.<number>5__2 = this.<>7__wrap5.Current;
this.<childDisposableObject>5__3 = new DisposableObject(string.Format("ChildDisposableObject{0}", this.<number>5__2));
this.<>1__state = ;
if (this.<number>5__2 == )
{
throw new Exception("异常。");
}
if (this.<number>5__2 == )
{
goto Label_00D0;
}
this.<>2__current = this.<number>5__2 * ;
this.<>1__state = ;
return true;
Label_00C7:
this.<>1__state = ;
goto Label_00E8;
Label_00D0:
Console.WriteLine("循环{0}:else 内代码执行了 ", this.<number>5__2.ToString());
Label_00E8:
Console.WriteLine("循环{0}:else下面的代码执行了 ", this.<number>5__2.ToString());
this.<>m__Finally7();
}
this.<>m__Finally6();
this.<>m__Finally4();
break; case :
goto Label_00C7;
}
return false;
}
fault
{
this.System.IDisposable.Dispose();
}
}

接着再看下using,也来个最简单的。

            using (DisposableObject parentDisposableObject = new DisposableObject("MainDisposableObject"))
{
Console.WriteLine("执行...");
//throw new Exception("异常。");
}

然后我们看一下对应的MSIL:

    .entrypoint
.maxstack
.locals init (
[] class ConsoleApplication1.DisposableObject parentDisposableObject)
L_0000: ldstr "MainDisposableObject"
L_0005: newobj instance void ConsoleApplication1.DisposableObject::.ctor(string)
L_000a: stloc.
L_000b: ldstr "\u6267\u884c..."
L_0010: call void [mscorlib]System.Console::WriteLine(string)
L_0015: leave.s L_0021
L_0017: ldloc.
L_0018: brfalse.s L_0020
L_001a: ldloc.
L_001b: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0020: endfinally
L_0021: ret
.try L_000b to L_0017 finally handler L_0017 to L_0021

再换一种C#写法试试:

            DisposableObject parentDisposableObject = new DisposableObject("MainDisposableObject");
try
{
Console.WriteLine("执行...");
//throw new Exception("异常。");
}
finally
{
parentDisposableObject.Dispose();
}

对应的MSIL代码:

    .entrypoint
.maxstack
.locals init (
[] class ConsoleApplication1.DisposableObject parentDisposableObject)
L_0000: ldstr "MainDisposableObject"
L_0005: newobj instance void ConsoleApplication1.DisposableObject::.ctor(string)
L_000a: stloc.
L_000b: ldstr "\u6267\u884c..."
L_0010: call void [mscorlib]System.Console::WriteLine(string)
L_0015: leave.s L_001e
L_0017: ldloc.
L_0018: callvirt instance void ConsoleApplication1.DisposableObject::Dispose()
L_001d: endfinally
L_001e: ret
.try L_000b to L_0017 finally handler L_0017 to L_001e

看看两段MSIL多像啊,特别是最后一句!

最后我们看看yield return 和 using混合使用时,自动生成的<GetNumbers>d__0类是如何保证需要释放资源的DisposableObject对象被释放的,看后我不禁感慨:C#的编译器真是鬼斧神工啊!

 private bool MoveNext()
{
try
{
switch (this.<>1__state)
{
case :
this.<>1__state = -;
this.<parentDisposableObject>5__1 = new DisposableObject("ParentDisposableObject");
this.<>1__state = ;
this.<>7__wrap5 = Enumerable.Range(, this.count).GetEnumerator();
this.<>1__state = ;
while (this.<>7__wrap5.MoveNext())
{
this.<number>5__2 = this.<>7__wrap5.Current;
this.<childDisposableObject>5__3 = new DisposableObject(string.Format("ChildDisposableObject{0}", this.<number>5__2));
this.<>1__state = ;
if (this.<number>5__2 == )
{
throw new Exception("异常。");
}
if (this.<number>5__2 == )
{
goto Label_00D0;
}
this.<>2__current = this.<number>5__2 * ;
this.<>1__state = ;
return true;
Label_00C7:
this.<>1__state = ;
goto Label_00E8;
Label_00D0:
Console.WriteLine("循环{0}:else 内代码执行了 ", this.<number>5__2.ToString());
Label_00E8:
Console.WriteLine("循环{0}:else下面的代码执行了 ", this.<number>5__2.ToString());
this.<>m__Finally7();
}
this.<>m__Finally6();
this.<>m__Finally4();
break; case :
goto Label_00C7;
}
return false;
}
fault
{
this.System.IDisposable.Dispose();
}
}
 public DisposableObject <parentDisposableObject>5__1;

 public DisposableObject <childDisposableObject>5__3;

 private void <>m__Finally4()
{
this.<>1__state = -;
if (this.<parentDisposableObject>5__1 != null)
{
this.<parentDisposableObject>5__1.Dispose();
}
} private void <>m__Finally7()
{
this.<>1__state = ;
if (this.<childDisposableObject>5__3 != null)
{
this.<childDisposableObject>5__3.Dispose();
}
} void IDisposable.Dispose()
{
switch (this.<>1__state)
{
case :
case :
case :
case :
try
{
switch (this.<>1__state)
{
case :
case :
case :
try
{
switch (this.<>1__state)
{
case :
case :
try
{
}
finally
{
this.<>m__Finally7();
}
break;
}
}
finally
{
this.<>m__Finally6();
}
break;
}
}
finally
{
this.<>m__Finally4();
}
break; default:
return;
}
}

C#中的using和yield return混合使用的更多相关文章

  1. C#中,什么时候用yield return

    yield关键字用于遍历循环中,yield return用于返回IEnumerable<T>,yield break用于终止循环遍历. 有这样的一个int类型的集合: static Lis ...

  2. using和yield return

    C#中的using和yield return混合使用 最近写代码为了为了省事儿用了几个yield return,因为我不想New一个List<T>或者T[]对象再往里放元素,就直接返回IE ...

  3. 可惜Java中没有yield return

    项目中一个消息推送需求,推送的用户数几百万,用户清单很简单就是一个txt文件,是由hadoop计算出来的.格式大概如下: uid caller 123456 12345678901 789101 12 ...

  4. yield学习续:yield return迭代块在Unity3D中的应用——协程

    必读好文推荐: Unity协程(Coroutine)原理深入剖析 Unity协程(Coroutine)原理深入剖析再续 上面的文章说得太透彻,所以这里就记一下自己的学习笔记了. 首先要说明的是,协程并 ...

  5. C#中yield return用法分析

    这篇文章主要介绍了C#中yield return用法,对比使用yield return与不使用yield return的流程,更直观的分析了yield return的用法,需要的朋友可以参考下. 本文 ...

  6. C#中的yield return与Unity中的Coroutine(协程)(下)

    Unity中的Coroutine(协程) 估计熟悉Unity的人看过或者用过StartCoroutine() 假设我们在场景中有一个UGUI组件, Image: 将以下代码绑定到Image using ...

  7. C#中的yield return与Unity中的Coroutine(协程)(上)

    C#中的yield return C#语法中有个特别的关键字yield, 它是干什么用的呢? 来看看专业的解释: yield 是在迭代器块中用于向枚举数对象提供值或发出迭代结束信号.它的形式为下列之一 ...

  8. C#中的yield return用法演示源码

    下边代码段是关于C#中的yield return用法演示的代码. using System;using System.Collections;using System.Collections.Gene ...

  9. Unity中yield return null和yield return WaitForEndOfFrame的区别

    2017/07/04修改 - 对WaitForEndOfFrame的LateUpdate时序进行说明. 测试结论: 1.如果只是等待下一帧执行,用yield return null即可.调用顺序在Up ...

随机推荐

  1. adb无法使用,提示error: unknown host service的解决办法

    此时,需要辨别电脑的5037端口被哪个应用程序占用的方法:(使用adb时需要5037端口是空闲的) 1. 打开命令行,输入命令:netstat -ano |findstr "5037&quo ...

  2. Redis从基础命令到实战之列表类型(List)

    经过上一篇基于Redis散列类型的改造后,实战练习中的商品管理已经具备了增加.修改整体.修改部分属性和分页查询功能,但仍然不支持删除商品的功能.这是因为商品总数是以一个自增数字记录的,且关联了新商品k ...

  3. seaJS循环依赖的解决原理

    seajs模块的六个状态. var STATUS = {  'FETCHING': 1, // The module file is fetching now. 模块正在下载中  'FETCHED': ...

  4. 【转】关于LinQ的动态Or查询

    /// <summary> /// 构造函数使用True时:单个AND有效,多个AND有效:单个OR无效,多个OR无效:混合时写在AND后的OR有效 /// 构造函数使用False时:单个 ...

  5. 机器学习--Classifier comparison

    最近在学习机器学习,学习和积累和一些关于机器学习的算法,今天介绍一种机器学习里面各种分类算法的比较 #!/usr/bin/python # -*- coding: utf-8 -*- "&q ...

  6. Windows服务二:测试新建的服务、调试Windows服务

    一.测试Windows服务 为了使Windows服务程序能够正常运行,我们需要像创建一般应用程序那样为它创建一个程序的入口点.像其他应用程序一样,Windows服务也是在Program.cs的Main ...

  7. Picture Segmentation Using A Recursive Region Splitting Method

    首先 区域分割的分割方法 首先得到一个区域,(一般已原图作为第一个区域) 然后得到该区域的直方图,选取最合适的峰值作为阈值,然后选择连接区域,然后把这些区域加入下一步要处理的区域中,然后重复第一步,直 ...

  8. 设置mysql utf8

    1.修改mysql 配置文件my.cnf 标签[mysqld]下添加即可 character-set-server = utf8

  9. Ubuntu 安装 mysql 并修改数据库目录

    . . . . . 今天折腾了一下午的时间,恢复了无数次虚拟机快照,终于在 Ubuntu 上把 mysql 安装好了. mysql 是从官网下载的:mysql-server_5.7.16-1ubunt ...

  10. hdu 1398 Square Coins (母函数)

    Square Coins Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tota ...