IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity3D的协程1——初步理解背后的迭代器 -> 正文阅读

[游戏开发]Unity3D的协程1——初步理解背后的迭代器

Unity协程的概念:

? ? ? ? 协程存在于许多编程语言中,Unity3D在调用我们编写的C#脚本时,会将它们统一放在一条主线程当中调度,所有的游戏对象、游戏组件都在这条主线程中。其他的线程并不能访问这些数据,所以对于我们所写的所有脚本来说,Unity是单线程的。

? ? ? ? 既然Unity3D不能多线程,那肯定需要一种机制来模拟多线程,来解决这种问题。这个机制便是协程。

要理解什么是协程,先让我们看看迭代器:

迭代器:

? ? ? ? 让我们先来看看下面的代码

List<int> arr = new List<int>(){ 0, 1, 2, 3, 4 };
foreach (int i in arr){
    Debug.log(i);
}

不知道各位有没有想过,这个foreach到底做了什么,arr又是因为什么,能够遍历这个数组中的所有元素?

让我们查看List<>的元数据:

public class List<T> : ICollection<T>, IEnumerable<T>, IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection, IList

List这个泛型类继承了很多的接口,有一个貌似与我们要探讨的问题有关——?IEnumerable和?IEnumerable<T>。

让我们继续深入,看看它们的代码:

IEnumerable:

using System.Runtime.InteropServices;

namespace System.Collections
{
    public interface IEnumerable
    {
        IEnumerator GetEnumerator();
    }
}

我们可以看到,继承了这个接口的类必须实现一个方法——返回一个IEnumerator的方法,这个IEnumerator又是什么呢?继续深入下去:

这是IEnumerator的代码

namespace System.Collections
{
    public interface IEnumerator
    {
        object Current { get; }

        bool MoveNext();
        void Reset();
    }
}

IEnumerator又是一个接口,函数返回了一个接口,事情有点开始绕了。这个接口中有一个Current、一个返回bool型的MoveNext方法、还有个Reset()方法

此处我查阅了微软官方的文档,有对IEnumerator接口的详细解释。?

IEnumerator官方文档解释

?看了这段文字,可能还是有很多人云里雾里,不知所云,没关系,让我们把上面的例子详细理解一下:

1.首先,微软定义了一个Person类:

// Simple business object.
public class Person
{
    public Person(string fName, string lName)
    {
        this.firstName = fName;
        this.lastName = lName;
    }

    public string firstName;
    public string lastName;
}

这个类有姓、名两个字符串字段和一个构造函数为它们初始化值。

2.接着,定义了一个People类,并让他继承IEnumerator接口

// Collection of Person objects. This class
// implements IEnumerable so that it can be used
// with ForEach syntax.
public class People : IEnumerable
{
    private Person[] _people;
    public People(Person[] pArray)
    {
        _people = new Person[pArray.Length];

        for (int i = 0; i < pArray.Length; i++)
        {
            _people[i] = pArray[i];
        }
    }

    // Implementation for the GetEnumerator method.
    IEnumerator IEnumerable.GetEnumerator()
    {
        return (IEnumerator)GetEnumerator();
    }

    public PeopleEnum GetEnumerator()
    {
        return new PeopleEnum(_people);
    }
}

????????People这是Person的容器,内部一个Person类型的数组_people,实现IEnumerator接口是为了让他能够被Foreach语句调用,构造函数接收一个Person类型的数组,并将其复制进_people中,至此都没什么好留意的。

? ? ? ? 需要注意的是,这里出现了两个GetEnumerator方法,上面的是实现接口之用,这个类中还是可以有一个与接口中方法同名的方法成员。

????????下面它实现了IEnumerator接口中的GetEnumerator()方法,返回GetEnumerator,并将它强制转换为IEnumerator接口。下面的GetEnumerator方法返回的则是一个PeopleEnum对象,这个PeopleEnum又是什么呢,让我们继续往下看:

4.PeopleEnum类,实现了IEnumerator接口,离真相越来越近了

// When you implement IEnumerable, you must also implement IEnumerator.
public class PeopleEnum : IEnumerator
{
    public Person[] _people;

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    int position = -1;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position < _people.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public Person Current
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

这个类还和People类一样,有一个Person类型的数组,和一个为它初始化的构造函数,下面实现了IEnumerator——MoveNext,Reset,Current两个方法和一个属性,结合上面官方文档的注释和博主一步一步的调试,终于算是搞懂了这是怎么一回事。

PeopleEnum中有一个标记位置的参数position,默认为-1,调用MoveNext()方法时,会将这个位置值+1,然后判断是否到了数组的尽头,并将判断的结果返回,如果没有到数组末尾,返回true,表示可以继续下一轮,一旦返回false则停止遍历。

Reset()方法便是直接将position重置为-1;

Current为只读,返回_people数组中的第position位元素

5.这是Main()函数中的内容:

    static void Main()
    {
        Person[] peopleArray = new Person[3]
        {
            new Person("John", "Smith"),
            new Person("Jim", "Johnson"),
            new Person("Sue", "Rabon"),
        };

        People peopleList = new People(peopleArray);
        foreach (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);
    }

? ? ? ? 首先是初始化一个Person数组并将他赋值给People类中,接下来进入了foreach语句,程序首先是通过peopleList进入了People类的GetEnumerator方法中:

    IEnumerator IEnumerable.GetEnumerator()
    {
        return (IEnumerator)GetEnumerator();
    }

然后进入自己实现的GetEnumerator方法中:

    public PeopleEnum GetEnumerator()
    {
        return new PeopleEnum(_people);
    }

将自身的_people数组传入并返回,因为PeopleEnum本身实现了IEnumerator接口,所以将它的返回时转换成IEnumerator类型也是合理的,这就返回了一个_people的枚举器。结合上面对该接口中三个成员的描述与下面的单步调试过程,程序的运行逻辑便清晰明了了:

得到了枚举器之后,程序会首先进入MoveNext()方法,position++,为0,返回ture,表示可以继续遍历,之后访问Current属性,返回_people数组的第0位元素“John Smith”将它赋值给p,然后把p打印出来。

然后再进入MoveNext方法,position++,为1,返回true,可以继续遍历,Current返回第1位元素,打印。

再次进入MoveNext方法,position++,为2,返回true,可以继续遍历,Current返回第2位元素,打印。

再次进入MoveNext方法,position++,为3,这时返回false,此时退出foreach语句。

如果我们将MoveNext中的返回值改为false,那么控制台不会打印任何信息,进一步验证了我的想法。

总结一下思路:能被foreach语句遍历的类必须继承?IEnumerable,表示这个是一个可以被枚举的类,继承该接口的类必须实现一个GetEnumerator方法,该方法返回一个枚举器IEnumerator,foreach凭借其实现的MoveNext,Current便可以遍历我们想要遍历的内容啦。

大致的结构便是这样的:

?????????最后的最后,其实继承Enumerator的并不一定要是一个额外的类,完全可以是一个自己的结构体成员,就像List的元数据那样:

public List<T>.Enumerator GetEnumerator();
public struct Enumerator : IEnumerator<T>, IEnumerator, IDisposable
{
    public T Current { get; }
    public void Dispose();
    public bool MoveNext();
}

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-09-27 14:26:05  更:2021-09-27 14:27:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/28 3:32:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码