不要在构造函数中初始化任何变量”为什么
1个回答
展开全部
首先你应该明确这个规定的限制范围:只是针对MonoBehaviour的派生类,为什么,因为其他的类就是正常的C#类,你在里面写Awake方法,就是个普通的叫Awake的方法,并不会被自动调用。
原因也很简单,MonoBehaviour代表的是一个组件,在Unity3D里面组件是必须依附于GameObject存在的,对于这种类你永远都不会使用new来创建一个实例,而是使用AddComponent或GetComponent方法来获取。Unity3D的开发人员并不想让你知道这个类是具体什么时候new出来的,你只要关心我想要的时候能够拿到一个引用就可以了。
你引用的这条限制,在我理解来看实际是说:“MonoBehaviour的生命周期并不是从构造函数开始的,而是从Awake开始的”。在构造函数中初始化变量,一般来说目的是将类的状态设置到一个“初始状态”,对于MonoBehaviour来说,你相当于在这个类的生命周期之外设定了它的状态,此时这个代码就是不严谨的,而应该放在Awake里面去。
因此,在我的理解来看,你应该避免做的是在构造函数里设定任何跟类的内部状态有关的事情。举例来说,如果我在做一个计数器,有一个count变量,这个变量就明显是内部状态,count=0这句话就应该放在Awake中。另外,程序不是一个类组成的,在实际工程中,有时候一个类的状态依赖于其他类的状态,你举例的GUI.Button方法可以看做设定了GUI类的内部状态,这个代码也不应该放在构造函数中(这个例子不好之处在于GUI类的方法只能在onGUI中调用,不能在任何其他地方,包括Awake中使用,如果换用一个其它全局变量可能更能说明问题)
另一方面,我们可以做一些不牵扯到类的内部状态的事情,举个例子:
class A{
public int count;
public int price;
public string name;}class AComponent : MonoBehaviour{
A inner;
public int Count {get{return inner.count;} set{ inner.count = value;}}
public int Price {...}
public String Name {...}
public AComponent(){
inner = new A();
}
public Awake(){
Count=0;
Price=10;
Name="Something";
}}
在这样一个类的构造方法中我创建了一个A对象给了inner,原因在于我这个类的内部状态并不在inner本身,而是在于inner的内部状态,此时创建出来inner不是为了设定状态,而是为了让几个属性永远都能用而已。实际状态设定还是在Awake中。
当然这个new也可以写在Awake中,因为毕竟实际用到这几个属性的时候Awake应该是已经调用过了,但这两者还是有略微的区别,就是何时创建这个对象的问题。如果你做过手游,应该会知道手机环境上new一个东西的效率并不是很高(由于这个东西不是临时对象,所以不讨论GC问题),写在构造函数里会在资源加载时调用,写在Awake里会在第一次使用(例如它依附的GameObject第一次变成active)时调用。
假设这个A类占用很大内存,甚至可能会出现内存不够的crash,那么这两者就有区别了,写在new中,这个申请内存会在资源加载时,此时游戏大概会有一个loading的界面,卡一点是可以接受的,如果crash掉了,也会给用户一个明显的暗示就是内存不够导致的crash,是不是关一些后台程序再来试试。而放在Awake中,可能发生的是用户第一次触发某个功能,放某个技能,就会卡一下,如果crash掉了,那就是在说我这个功能没做好,所以崩了,这种体验比起上一种是要差一些的。
结论:大部分情况下把所有的初始化写在Awake里就行,写在构造函数里并不是完全没有应用场景,但比较少见,是一种具体问题具体分析的优化策略,写的时候需要脑子清楚,知道自己为什么要这么写。
原因也很简单,MonoBehaviour代表的是一个组件,在Unity3D里面组件是必须依附于GameObject存在的,对于这种类你永远都不会使用new来创建一个实例,而是使用AddComponent或GetComponent方法来获取。Unity3D的开发人员并不想让你知道这个类是具体什么时候new出来的,你只要关心我想要的时候能够拿到一个引用就可以了。
你引用的这条限制,在我理解来看实际是说:“MonoBehaviour的生命周期并不是从构造函数开始的,而是从Awake开始的”。在构造函数中初始化变量,一般来说目的是将类的状态设置到一个“初始状态”,对于MonoBehaviour来说,你相当于在这个类的生命周期之外设定了它的状态,此时这个代码就是不严谨的,而应该放在Awake里面去。
因此,在我的理解来看,你应该避免做的是在构造函数里设定任何跟类的内部状态有关的事情。举例来说,如果我在做一个计数器,有一个count变量,这个变量就明显是内部状态,count=0这句话就应该放在Awake中。另外,程序不是一个类组成的,在实际工程中,有时候一个类的状态依赖于其他类的状态,你举例的GUI.Button方法可以看做设定了GUI类的内部状态,这个代码也不应该放在构造函数中(这个例子不好之处在于GUI类的方法只能在onGUI中调用,不能在任何其他地方,包括Awake中使用,如果换用一个其它全局变量可能更能说明问题)
另一方面,我们可以做一些不牵扯到类的内部状态的事情,举个例子:
class A{
public int count;
public int price;
public string name;}class AComponent : MonoBehaviour{
A inner;
public int Count {get{return inner.count;} set{ inner.count = value;}}
public int Price {...}
public String Name {...}
public AComponent(){
inner = new A();
}
public Awake(){
Count=0;
Price=10;
Name="Something";
}}
在这样一个类的构造方法中我创建了一个A对象给了inner,原因在于我这个类的内部状态并不在inner本身,而是在于inner的内部状态,此时创建出来inner不是为了设定状态,而是为了让几个属性永远都能用而已。实际状态设定还是在Awake中。
当然这个new也可以写在Awake中,因为毕竟实际用到这几个属性的时候Awake应该是已经调用过了,但这两者还是有略微的区别,就是何时创建这个对象的问题。如果你做过手游,应该会知道手机环境上new一个东西的效率并不是很高(由于这个东西不是临时对象,所以不讨论GC问题),写在构造函数里会在资源加载时调用,写在Awake里会在第一次使用(例如它依附的GameObject第一次变成active)时调用。
假设这个A类占用很大内存,甚至可能会出现内存不够的crash,那么这两者就有区别了,写在new中,这个申请内存会在资源加载时,此时游戏大概会有一个loading的界面,卡一点是可以接受的,如果crash掉了,也会给用户一个明显的暗示就是内存不够导致的crash,是不是关一些后台程序再来试试。而放在Awake中,可能发生的是用户第一次触发某个功能,放某个技能,就会卡一下,如果crash掉了,那就是在说我这个功能没做好,所以崩了,这种体验比起上一种是要差一些的。
结论:大部分情况下把所有的初始化写在Awake里就行,写在构造函数里并不是完全没有应用场景,但比较少见,是一种具体问题具体分析的优化策略,写的时候需要脑子清楚,知道自己为什么要这么写。
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询