• 造假造上瘾——仿造yield关键字(一)


            本篇会简单的介绍yield关键字,通过yield关键字返回的类型,以及Reflector反编译的结果来分析yield关键字。最后给出一个仿造的方法。

            首先我们看一下yield的用法,他的返回类型返回类型必须是 IEnumerableIEnumerable<T>IEnumerator或 IEnumerator<T>,这意味着yield生成的这个对象必须同时实现IEnumerableIEnumerator这2个接口。

        class Program
        {
            public static IEnumerable Easy1()
            {
                yield return 1;
            }
    
            static void Main(string[] args)
            {
                foreach (var item in Program.Easy1())
                {
                    Console.WriteLine(item);
                }
            }
        }

             上述代码通过Reflector反编译的结果如下:

    internal class Program
    {
        // Methods
        public Program();
        public static IEnumerable Easy1();
        private static void Main(string[] args);
    
        // Nested Types
        [CompilerGenerated]
        private sealed class <Easy1>d__0 : IEnumerable<object>, IEnumerable, IEnumerator<object>, IEnumerator, IDisposable
        {
            // Fields
            private int <>1__state;
            private object <>2__current;
            private int <>l__initialThreadId;
    
            // Methods
            [DebuggerHidden]
            public <Easy1>d__0(int <>1__state);
            private bool MoveNext();
            [DebuggerHidden]
            IEnumerator<object> IEnumerable<object>.GetEnumerator();
            [DebuggerHidden]
            IEnumerator IEnumerable.GetEnumerator();
            [DebuggerHidden]
            void IEnumerator.Reset();
            void IDisposable.Dispose();
    
            // Properties
            object IEnumerator<object>.Current { [DebuggerHidden] get; }
            object IEnumerator.Current { [DebuggerHidden] get; }
        }
    }
    
     

            我们可以看到,这里编译器为我们自动生成了一个叫<Easy1>d_0的类,同时我们可以看到Easy1方法返回的就是该类型:

        public static IEnumerable Easy1()
        {
            return new <Easy1>d__0(-2);
        }

            <Easy1>d_0经过简化的代码如下,为了能实际使用,我把类名换成了FakeYield:

        class FakeYield : IEnumerable, IEnumerator
        {
            private int state;
            private object current;
    
            public FakeYield(int state)
            {
                this.state = state;
            }
    
            public bool MoveNext()
            {
                switch (this.state)
                {
                    case 0:
                        this.state = -1;
                        this.current = 1;
                        this.state = 1;
                        return true;
    
                    case 1:
                        this.state = -1;
                        break;
                }
                return false;
            }
    
            IEnumerator IEnumerable.GetEnumerator()
            {
                if (this.state == -2)
                {
                    this.state = 0;
                    return this;
                }
                return new FakeYield(0);
            }
    
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }
    
            object IEnumerator.Current
            {
                get
                {
                    return this.current;
                }
            }
        }

            这里说明一下state不同值的含义,-2表示首次初始化该类,由外部调用构造函数时赋值。0表示初始状态,一切就绪。-1表示一次操作取值完成,1在这个类里是迭代完成的状态。这里我们省略了泛型接口、线程ID initIalThreadId以及IDispose接口。感兴趣的可以自己补完。

            如果对迭代器模式有所了解的话,可以看出这里MoveNext方法仅仅是返回了一次true,给current赋值1,也没干啥正经事。不过这正是Easy1方法的忠实体现……

            那我们再来看一下稍微复杂一些的yield用法:

        class Person
        {
            public string Name { get; set; }
    
            public int Age { get; set; }
    
            public IEnumerable<Person> GetPersons()
            {
                yield return new Person { Age = 27, Name = "Leo" };
                yield return new Person { Age = 26, Name = "Echo" };
                yield return new Person { Age = 25, Name = "Peter" };
            }
        }

            和之前比有2点不同,首先GetPersons是一个实例方法,其次返回值是泛型的IEnumerable<Person>。体现在代码中的话,实例方法在该迭代类中会生成一个Person的自引用,在调用该迭代类时传递Person实例对象进去。由于Person对象不在是基本类型,类中产生了一些额外的字段,当然这是因为自动生成的原因,如果人肉去写这个类,自然能够优化。

            private sealed class <GetPersons>d__3 : IEnumerable<Person>, IEnumerable, IEnumerator<Person>, IEnumerator, IDisposable
            {
                private int <>1__state;
                private Person <>2__current;
                public Person <>4__this;
                public Person <>g__initLocal0;
                public Person <>g__initLocal1;
                public Person <>g__initLocal2;
                private int <>l__initialThreadId;
    
                [DebuggerHidden]
                public <GetPersons>d__3(int <>1__state)
                {
                    this.<>1__state = <>1__state;
                    this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
                }
    
                private bool MoveNext()
                {
                    switch (this.<>1__state)
                    {
                        case 0:
                            this.<>1__state = -1;
                            this.<>g__initLocal0 = new Person();
                            this.<>g__initLocal0.Age = 0x1b;
                            this.<>g__initLocal0.Name = "Leo";
                            this.<>2__current = this.<>g__initLocal0;
                            this.<>1__state = 1;
                            return true;
    
                        case 1:
                            this.<>1__state = -1;
                            this.<>g__initLocal1 = new Person();
                            this.<>g__initLocal1.Age = 0x1a;
                            this.<>g__initLocal1.Name = "Echo";
                            this.<>2__current = this.<>g__initLocal1;
                            this.<>1__state = 2;
                            return true;
    
                        case 2:
                            this.<>1__state = -1;
                            this.<>g__initLocal2 = new Person();
                            this.<>g__initLocal2.Age = 0x19;
                            this.<>g__initLocal2.Name = "Peter";
                            this.<>2__current = this.<>g__initLocal2;
                            this.<>1__state = 3;
                            return true;
    
                        case 3:
                            this.<>1__state = -1;
                            break;
                    }
                    return false;
                }
    
                [DebuggerHidden]
                IEnumerator<Person> IEnumerable<Person>.GetEnumerator()
                {
                    if ((Environment.CurrentManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
                    {
                        this.<>1__state = 0;
                        return this;
                    }
                    Person.<GetPersons>d__3 d__ = new Person.<GetPersons>d__3(0);
                    d__.<>4__this = this.<>4__this;
                    return d__;
                }
    
                [DebuggerHidden]
                IEnumerator IEnumerable.GetEnumerator()
                {
                    return this.System.Collections.Generic.IEnumerable<YieldTest.Person>.GetEnumerator();
                }
    
                [DebuggerHidden]
                void IEnumerator.Reset()
                {
                    throw new NotSupportedException();
                }
    
                void IDisposable.Dispose()
                {
                }
    
                Person IEnumerator<Person>.Current
                {
                    [DebuggerHidden]
                    get
                    {
                        return this.<>2__current;
                    }
                }
    
                object IEnumerator.Current
                {
                    [DebuggerHidden]
                    get
                    {
                        return this.<>2__current;
                    }
                }
            }

             在这个类中,仍然有一些值得注意的地方。首先是initialThreadId,我对该字段不是很明白,没想清楚该自动生成的类会在多线程中如何使用,还请各位指点迷津。其次是Reset方法,不支持的理由我认为是在自动生成的类中,该方法永远不会被用到。最后还有Dispose方法,在Person类没有引用非托管资源的情况下,Dispose是不需要做任何事情的,但如果引用了需要释放的资源,比如打开了文件,就应该通过该方法来关闭文件。

             本文是yield关键字的第一篇,简单的讨论了yield最常见用法的生成类。下篇我们将查看通过for和foreach配合yield返回时生成的类。

  • 相关阅读:
    众皓网络(T 面试)
    骑芯供应链(T 面试)
    骑芯供应链(W 笔试)
    面试问题_一拉到底
    Java后端学习路线_备战
    docker 容器
    技术展望
    索引 命令
    索引 概念原理
    面试技能更新
  • 原文地址:https://www.cnblogs.com/manupstairs/p/2805536.html
Copyright © 2020-2023  润新知