عنوان:

‫بررسی هزینه‌های پنهان بررسی مقدار تهی (Null Check) در کد‌های جنریک سی‌شارپ


نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۲/۱۴ ۰۸:۳۹
آدرس: www.dntips.ir

مقدمه

برنامه‌نویسی جنریک (Generic Programming) در سی‌شارپ قابلیتی قدرتمند برای نوشتن کد انعطاف‌پذیر و قابل استفاده مجدد فراهم می‌کند. با این حال، این قدرت با خود برخی از مشکلات عملکردی ظریف را به همراه دارد، به ویژه هنگام بررسی اینکه آیا یک مقدار در یک متد جنریک تهی (null) است یا خیر. آنچه که در نگاه اول یک بررسی ساده به نظر می‌رسد، می‌تواند به یک مسئله پنهان در عملکرد و مصرف حافظه تبدیل شود. در این مقاله، به بررسی علت این موضوع و روش صحیح انجام این بررسی می‌پردازیم.

رویکرد ابتدایی: value == null

یک روش ابتدایی برای بررسی مقدار تهی ممکن است به شکل زیر باشد:
public bool IsNull<T>(T value)
{
    return value == null;
}
در نگاه اول، این کد به خوبی کار می‌کند، به خصوص برای انواع مرجع (Reference Types) مانند string، object یا کلاس‌های سفارشی. با این حال، مشکل زمانی ظاهر می‌شود که نوع T یک نوع مقداری (Value Type) مانند int، bool یا struct باشد.

مشکل جعبه‌گذاری (Boxing Problem)

در سی‌شارپ، انواع مقداری به طور معمول در پشته (Stack) قرار می‌گیرند که سریع و کارآمد است. اما هنگامیکه از عملگر == null بر روی یک نوع مقداری در یک متد جنریک استفاده می‌کنید، کامپایلر مجبور به جعبه‌گذاری (Boxing) آن می‌شود. این بدان معناست که:
  • مقدار در یک شیء (Object) پیچیده می‌شود.
  • از پشته (Stack) به هیپ (Heap) منتقل می‌شود.
  • به طور موقت مانند یک نوع مرجع (Reference Type) رفتار می‌کند.
این فرآیند زمان بیشتری می‌گیرد و حافظه بیشتری مصرف می‌کند.
مثال:
IsNull<int>(0);
این فراخوانی باعث جعبه‌گذاری (Boxing) می‌شود. اگرچه نتیجه false (زیرا 0 تهی نیست) برگردانده می‌شود، سیستم همچنان مجبور است حافظه را روی هیپ (Heap) تخصیص داده و بعداً آن را پاک کند که این امر باعث اتلاف عملکرد می‌شود.

روش صحیح: EqualityComparer<T>.Default

سی‌شارپ یک گزینه بهتر ارائه می‌دهد:
public bool IsNull<T>(T value)
{
    return EqualityComparer<T>.Default.Equals(value, default(T));
}
چرا این روش کار می‌کند؟
  • EqualityComparer<T>.Default: این ویژگی بهترین روش مقایسه مقادیر را برای نوع T فراهم می‌کند.
  • از جعبه‌گذاری (Boxing) برای انواع مقدار جلوگیری می‌کند.
  • به درستی مقدار تهی (null) را برای انواع مرجع (Reference Types) بررسی می‌کند.
  • default(T) مقدار پیش‌فرض نوع T را برمی‌گرداند:
    • null برای انواع مرجع (Reference Types).
    • false یا یک ساختار (Struct) خالی برای انواع مقداری (Value Types).
مثال‌ها:
IsNull<string>(null);    // true
IsNull<int>(0);          // true
IsNull<bool>(false);     // true
IsNull<int>(5);          // false

مزیت سی‌شارپ مدرن: is null (C# 7.0 به بعد)

از نسخه 7.0 سی‌شارپ به بعد، می‌توان از عبارت is null استفاده کرد:
public bool IsNull<T>(T value) where T : class?
{
    return value is null;
}
این روش برای انواع مرجع (Reference Types) و انواع مقدار تهی‌پذیر (Nullable Value Types) بسیار خوب عمل می‌کند و از جعبه‌گذاری (Boxing) جلوگیری می‌کند. اما برای انواع مقداری غیر تهی‌پذیر (Non-Nullable Value Types)، مقدار تهی (null) همیشه false برمی‌گرداند.

گزینه ترکیبی

اگر می‌خواهید هم مقدار تهی (null) و هم مقدار پیش‌فرض را در یک متد بررسی کنید:
public bool IsNull<T>(T value)
{
    return value is null || EqualityComparer<T>.Default.Equals(value, default(T));
}

تفاوت بین value == null، EqualityComparer<T>.Default.Equals(value, default(T)) و value is null (C# 7+)


رویکردانواع مرجع (Reference Types)انواع مقداری غیر تهی‌پذیر (Non-Nullable Value Types)انواع مقدار تهی‌پذیر (Nullable Value Types)مصرف حافظه (انواع مقدار)
value == nullسریعجعبه‌گذاری رخ می‌دهدجعبه‌گذاری رخ می‌دهدبالاتر، به دلیل جعبه‌گذاری
EqualityComparer<T>.Default.Equals(value, default(T))خوبمقایسه کارآمد مقداربررسی کارآمد مقدار تهیپایین‌تر، جلوگیری از جعبه‌گذاری
value is null (C# 7+)سریعهمیشه falseبررسی کارآمد مقدار تهیپایین‌تر، جلوگیری از جعبه‌گذاری

نتیجه‌گیری

بررسی مقدار تهی (Null Check) در کد جنریک سی‌شارپ آنقدرها هم که به نظر می‌رسد ساده نیست. استفاده از value == null می‌تواند به طور پنهان به عملکرد آسیب برساند، به ویژه در مورد انواع مقدار (Value Types) به دلیل جعبه‌گذاری (Boxing). به جای آن، از روش‌های زیر استفاده کنید:
  • EqualityComparer<T>.Default.Equals(value, default(T)) برای بررسی‌های ایمن و کارآمد در تمام انواع.
  • is null برای بررسی‌های تمیز مقدار تهی (null) در انواع مرجع (Reference Types) و انواع مقدار تهی‌پذیر (Nullable Value Types) (C# 7+).
با آگاهی از این جزئیات کوچک، می‌توانید کدی سریع‌تر، تمیزتر و با مصرف حافظه‌ی بهینه‌تری بنویسید، به خصوص در بخش‌های حساس به عملکرد.


مشاهده مطلب اصلی