属性包是特定 .Net 对象类型的属性集合,可用于访问和设置该类型对象实例的数据。
特定类型的属性包是一个伴生对象,可基于该类型的实例实现高效的数据遍历算法。默认情况下,Unity 使用反射为某个类型生成属性包。这种反射方法非常便利,并且在属性包尚未注册时,仅缓慢地执行一次。
为了提高性能,可以选择通过使用 [Unity.Properties.GeneratePropertyBag] 标记类型来生成代码。此外,要激活代码生成,必须使用 [assembly: Unity.Properties.GeneratePropertyBagsForAssembly] 标记程序集。加载域时,代码生成的属性包会自动注册。
在反射和代码生成两种情况下,属性包都会为以下内容生成属性:
[SerializeField]、[SerializeReference] 或 [CreateProperty] 标签的私有或内部字段[Unity.Properties.CreateProperty] 标记的公共、私有或内部属性属性包不会为带有 [DontCreateProperty] 标签的公共、私有或内部字段生成属性。
如果字段为只读或属性只有 getter,则生成的属性为只读。
还可以使用 [Unity.Properties.CreateProperty(ReadOnly = true)] 将生成的属性设置为只读。
为方便起见而使用序列化属性在属性包中创建属性并非总是首选方法。Unity 的序列化系统只能对字段和自动属性进行操作,因此很难有效地实现验证或传播更改。
以下示例将 Unity 序列化系统与 Unity 属性系统相结合:
using UnityEngine;
using Unity.Properties;
public class MyBehaviour : MonoBehaviour
{
// Serializations go through the field, but we don't want to create a property for it.
[SerializeField, DontCreateProperty]
private int m_Value;
// For the property bag, use the property instead of the field. This ensures that
// the value stays within the appropriate bounds.
[CreateProperty]
public int value
{
get => m_Value;
set => m_Value = value;
}
// This is a similar example, but for an auto-property.
[field: SerializeField, DontCreateProperty]
[CreateProperty]
public float floatValue { get; set; }
}
与 Unity 序列化系统不同,属性包中的属性不符合 [SerializeField] 值类型的条件。相反,“结构”类型被识别为“值”类型,而“类”类型被识别为“引用”类型。
在 Unity 序列化过程中,虽然支持多态性,但必须使用 [SerializeReference] 属性来显式地选择启用这一特性。否则,实例将序列化为“值”类型。值得注意的是,UnityEngine.Object 类型是此规则的例外情况,因为它们会自动序列化为“引用”类型。
Unity Properties 使用 .Net 反射来创建强类型的属性包和属性,这在首次为特定容器类型请求属性包时可能会面临性能上的损失。
通过反射为字段成员创建属性时,在使用__ IL2CPP__种由 Unity 开发的脚本后端,可在为某些平台构建项目时替代 Mono。更多信息
See in Glossary 构建时,这些属性可能会导致生成垃圾。发生此分配的原因是直接使用 System.Reflection.FieldInfo 导致不可避免地发生了装箱 (boxing) 操作。
为了避免反射,可以在编译期间通过代码生成属性包。但是,请注意,此优化方式可能会导致编译时间延长。要启用程序集的代码生成,请使用 [Unity.Properties.GeneratePropertyBagsForAssemblyAttribute] 标记程序集,并使用 [Unity.Properties.GeneratePropertyBagAttribute] 标记各个类型。要使属性包能够访问内部字段、私人字段和属性,请将类型设置为 partial。