1

好的,我知道为什么我们不在 Unity 的单一行为上使用构造函数。对于几乎所有用例,Start 和 Awake 都非常适合。通常。

但是,有一个很棒的 C# 功能只能与构造函数一起使用——只读字段。在我的特殊情况下,我与许多开发人员一起从事一个项目,并编写了一个抽象的 MonoBehavior,它将被许多不同的人子类化和重写很多次。而且我希望一个字段在对象的整个生命周期中都像常量一样(或者它会引入奇怪的、难以检测的错误),但在不同的子类中具有不同的值——换句话说,一个只读字段的经典用例。(我不想使用属性,因为它们没有保持不变的语言强制义务。)

那么——我可以安全地使用 MonoBehavior 的构造函数吗?会不会有一条奇怪的龙从路边某个地方的巢穴里出来?如果我选择使用它们,我应该知道什么?

4

3 回答 3

2

我认为 Unity 希望您远离使用构造函数的主要原因是构造函数没有在主线程上调用,并且在将序列化数据恢复到对象之前调用构造函数。

因此,如果您在构造函数中设置的只读字段依赖于序列化字段中的数据,那么它们将无法正常工作。如果他们不这样做,那么您可以在初始化时分配它们。

您还可以使用容器对象来保留您的只读值,但没有什么可以阻止其他人稍后重新分配该容器。

using UnityEngine;
using System.Collections;

public class ReadOnlyTest : MonoBehaviour {

    public string part1 = "alpha";  // change these values in the editor and 
    public string part2 = "beta";  // see the output of the readonly variable "combined"

    public readonly string combined;

    // just assign to readonly vars.
    public readonly string guid = System.Guid.NewGuid().ToString();
    public readonly float readOnlyFloat = 2.0f;


    public class ReadOnlyContainer {
        public readonly int readOnlyInt;
        public readonly float readOnlyFloat;
        public readonly string readOnlyString;

        public ReadOnlyContainer(int _int, float _flt, string _str) {
            readOnlyInt = _int;
            readOnlyFloat = _flt;
            readOnlyString = _str;
        }

        public override string ToString() {
            return string.Format("int:{0} float:{1} string:{2}", readOnlyInt, readOnlyFloat, readOnlyString);
        }
    }

    public ReadOnlyTest() {
        combined = part1 + part2;
    }

    public ReadOnlyContainer container;

    void Awake() {
        if (container == null) {
            container = new ReadOnlyContainer(Random.Range(-100,100), Time.realtimeSinceStartup, System.Guid.NewGuid().ToString());
        }
    }

    void Start () {
        Debug.Log(container.ToString());

        Debug.Log("combine1: " + combined);

        Debug.Log("guid: " + guid);
    }   
}
于 2013-10-03T20:31:02.360 回答
1

许多统一类是通过反射创建的,无法正确统一非默认构造函数;因此限制。

@Calvin 的回答指出了一个非常好的选择:创建不是从 MonoBehaviour 派生的类;这些可以像任何其他 C# 一样具有构造函数。只要您的代码可以容忍丢失的实例,您就可以将这些类放入 MonoBehaviours 的字段中。如果您使用@Calvin 回答中的典型准单例模式,您将始终在需要时获得一个实例,并且您可以将“第一次给我一个实例”逻辑推送到可以在派生类中覆盖的方法中自定义行为。

如果您想要类似常量的行为,在派生类中使用不同值的选项可能更容易定义方法而不是字段。该方法实际上是只读的,并且根据@Jerdak 的回答,它具有更多可预测的突变。

如果你必须有构造函数,最后一个选择是使用 monobehavior 作为最小占位符,并在你自己的类中编写所有有趣的东西,然后将 Monobehavior 中的所有工作委托给你的类。

using UnityEngine;
using System.Collections;

public class OuterPlaceholder: MonoBehaviour {

      public InnerBehavior _Inner;
      public void Awake() {
           if (_Inner == null) {
           _Inner= new InnerBehavior(4);
        }
    }

     public void Update()
     { 
        _Inner.DoUpdate(this);
     }

}

public class InnerBehavior 
{
     public readonly int UpConstant;
     public InnerBehavior (int up)
     {
     UpConstant = up;
     }

     public void DoUpdate(MonoBehaviour owner)
     {
      owner.transform.Translate(Vector3.up * UpConstant * Time.deltaTime);
     }

}

如果您确定随着项目的发展您将获得大量复杂的继承,则此选项可能效果最佳。

最后:将字段命名为 _ReadOnlyField 或 _DoNotWrite 或任何告诉用户不要乱用它的名称是完全可以的。所有 Python 程序员都有可能做一些更糟糕的事情,而且大多数时候看起来都很好:)

于 2013-10-04T05:03:37.607 回答
0

脚本参考

如果您尝试为脚本组件定义构造函数,则会干扰 Unity 的正常运行,并可能导致项目出现重大问题。

MonoBehaviours 在序列化过程中被构建了很多次,Unity 在编辑器中经常这样做,我怀疑还有很多事情要做,以便将 C 层连接到 C#。最终行为是未定义的,所以最好不要尝试。

关于“但在不同的子类中具有不同的值”,来自 MSDN

对声明 [readonly] 引入的字段的赋值只能作为声明的一部分或在同一类的构造函数中发生。

所以派生类中没有修改。

于 2013-10-03T19:50:11.233 回答