最近写代码为了为了省事儿用了几个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(1, count)) { using (DisposableObject childDisposableObject = new DisposableObject(string.Format("ChildDisposableObject{0}", number))) { //if (number == 4) //{ // throw new Exception("异常。"); //} if (number != 2) { yield return number * 10; } 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(5)) { 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(-2); 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