موتور بازی سازی Unity یک موتور نوشته شده با زبان ++C است که همهی اتفاقات زیر سیستمهایی مثل شبیه سازی فیزیک، پرداخت تصویر (Rendering) و سایر بخشهای سطح پایین آن با ++C پیاده شده است.
آنچه که ما به عنوان توسعه دهندهی بازی انجام میدهیم در لایهی Scripting است که با زبان #C نوشته میشود، همان طوری که در اینجا اشاره کردم، حافظهی مدیریت شدهی Net. یا mono از حافظهی Native و زیر سیستمهای یونیتی مجزا است.
برای این که یونیتی بتواند بین اشیاء حافظهی Native و حافظهی بخش Scripting ارتباط برقرار کند کلاسهایی پیاده کرده که خود کاری انجام نمیدهند و نقش آنها فقط ایجاد رابطه بین این دو بخش از هم مجزای حافظه است. یونیتی به این اشیاء Wrapper Objects میگوید.
کلاس GameObject یک Wrapper Object است و همهی اطلاعات مربوط آن مثل اسم شئ، لیست کامپوننت ها و ... سمت ++C هستند. تنها چیزی که درون آن ذخیره میشود یک اشارهگر به یک شئ Native است.
مدیریت طول عمر GameObject ها و همهی اشیایی که از UnityEngine.Object ارث بری میکنند کاملا به صورت صریح درون موتور و ++C انجام میشود، چه زمانی که از یک Scene به یک Scene دیگر میرویم و چه زمانی که خودمان تابع ()Object.Destroy را فراخوانی میکنیم. ولی سایر اشیاء #C به همان روشی سی شارپی مدیریت میشوند. بنابراین ممکن است یک شئ در سمت موتور از بین رفته باشد ولی ما از طریق یک Wrapper Object به آن ارجاع داشته باشیم.
یونیتی برای رفع این مشکل عملگر == را سربارگذاری کرده است، بنابراین زمانی که یک ارجاع را برای Null بودن بررسی میکنید یونیتی یک فراخوانی Native انجام میدهد و در مورد زنده بودن آن شی از موتور پرس و جو میکند. این باعث میشود که بررسی Null بودن اشیا یونیتی از اشیاء معمول سی شارپی کمی کندتر باشد.
همچنین باعث این اشتباه میشود که طول عمر اشیاء یونیتی و سی شارپی را یکی فرض کنیم.
به طور مثال کد زیر را در نظر بگیرید:
public class Example : MonoBehaviour
{
void Start()
{
GameObject go = new GameObject();
Debug.Log(go == null); // false
Object obj = new Object();
Debug.Log(obj == null); // true
}
}
در اینجا چون یک شی معادل GameObject در سمت موتور ساخته میشود عملگر == اول False باز میگرداند ولی در مورد دوم چون هیچ معادلی درون موتور ساخته نمیشود True برمیگرداند.
زمانی که فقط می خواهیم در مورد null بودن یک ارجاع پرس و جو کنیم و زنده بودن شئ متناظر آن در یونیتی برای ما اهمیت ندارد میتوانیم از تابع System.Object.ReferenceEquals استفاده کنیم که در این حالت سرعت مقایسه حداقل تا شش برابر سریعتر میشود(طبق کدهای تستی که نوشتیم).
منابع:
https://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/
https://docs.microsoft.com/en-us/dotnet/api/system.object.referenceequals?view=netframework-4.8
https://docs.unity3d.com/ScriptReference/GameObject.html
https://docs.unity3d.com/ScriptReference/MonoBehaviour.html
https://docs.unity3d.com/ScriptReference/Object-operator_eq.html
https://docs.unity3d.com/ScriptReference/Object-operator_ne.html
https://docs.unity3d.com/ScriptReference/Object-operator_Object.html