عنوان:

‫تحلیل و بررسی قابلیت Partial Numeric Parsing در NET 11.


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۲۲ ۱۰:۱۵
آدرس: www.dntips.ir
چکیده: در نسخه‌های پیشین .NET، پردازش و پارس کردن رشته‌های حاوی اعداد در قالب‌هایی مانند CSV، JSON یا فرمت‌های سفارشی، مستلزم جداسازی پیش‌فرض (Splitting)، کپی‌سازی حافظه (Allocation) یا انجام فرآیندهای پیچیده پردازش رشته بود. هرگونه کاراکتر اضافی در انتهای بخش عددی به عنوان خطای پارس (Parse Failure) تلقی می‌شد. در .NET 11 Preview 7، مایکروسافت با معرفی متد جدید TryParsePartial در اینترفیس INumberBase، امکان پارس جزیی یا تدریجی (Partial Parsing) اعداد را فراهم کرده است. این قابلیت به توسعه‌دهنده اجازه می‌دهد بدون نیاز به کپی‌سازی داده یا برش پیش‌فرض متون، مقدار عددی اولیه را استخراج کرده و از تعداد کاراکترهای مصرف‌شده (charsConsumed) برای ادامه پردازش مابقی ورودی استفاده کند. در این مقاله، نیازمندی‌ها، ساختار فنی، نحوه پیاده‌سازی و جزئیات معماری داخلی این ویژگی مورد بررسی قرار می‌گیرد.

۱. مقدمه
با رشد روزافزون نیاز به پردازش کارآمد داده‌ها با حجم بالا (High-Performance & Low-Allocation Parsing)، فریم‌ورک دات‌نت در نسخه‌های اخیر تمرکز ویژه‌ای بر ارائه ابزارهای کار با حافظه مانند ReadOnlySpan و ReadOnlySequence داشته است. با این حال، یکی از چالش‌های همیشگی در پردازش فرمت‌های متنی ساختاریافته (نظیر فایل‌های CSV، گزارش‌های متنی یا پروتکل‌های شبکه)، استخراج مقادیر عددی از میان متون متصل به هم بوده است. پیش از .NET 11، متدهای TryParse انتظار داشتند که تمام ورودی داده‌شده منطبق با فرمت عددی درخواستی باشد. وجود هرگونه کاراکتر جداکننده (Delimiter) مانند کاما یا نقطه-کاما در انتهای عدد، منجر به شکست عملیات پارس می‌شد. توسعه‌دهندگان مجبور بودند ابتدا جداکننده را پیدا کرده، بخش عددی را برش بزنند (Slice) و سپس آن را پارس کنند. ویژگی Partial Numeric Parsing با افزودن اورلودهای جدید TryParsePartial به اینترفیس INumberBase، این فرآیند را به‌طور بنیادین ساده و بهینه‌سازی کرده است.

۲. بررسی مکانیزم و نحوه عملکرد
۲.۱. معرفی متدTryParsePartial
متد TryParsePartial به توسعه‌دهنده امکان می‌دهد یک عدد را از ابتدای ورودی پارس کند و در پارامتر خروجی charsConsumed دقیقاً تعداد کاراکترها یا بایت‌های خوانده‌شده را دریافت کند. این متد برای سه قالب ورودی اصلی پشتیبانی می‌شود:
  • string
  • ReadOnlySpan
  • ReadOnlySpan (برای متون UTF-8)

۲.۲. نمونه کد عملی
کد زیر نحوه استفاده از این قابلیت را در یک سناریوی واقعی (مانند خواندن رشته‌ای حاوی مقادیر مجزا با ;) نشان می‌دهد:
using System;
using System.Globalization;

// ورودی حاوی یک عدد و ادامه متن است
ReadOnlySpan<char> input = "123; 456";

// استفاده از متد جدید TryParsePartial
if (int.TryParsePartial(
        input,
        NumberStyles.Integer,
        CultureInfo.InvariantCulture,
        out int value,
        out int charsConsumed))
{
    Console.WriteLine($"مقدار پارس شده: {value}"); // خروجی: 123
    
    // پیش بردن نماگر حافظه به اندازه کاراکترهای مصرف‌شده
    input = input[charsConsumed..];
    
    Console.WriteLine($"مابقی ورودی: {input}"); // خروجی: ; 456
}

۲.۳. مزایای کلیدی در کارایی
  • عدم تخصیص حافظه جدید (Zero-Allocation): نیازی به ساخت رشته‌های جدید یا اسلایس پیش‌فرض قبل از پارس نیست.
  • کاهش پیچیدگی کد: عدم نیاز به جستجوی دستی کاراکترهای جداکننده (مانند IndexOf) پیش از فراخوانی پارسر.
  • یکپارچگی با Generic Math: به دلیل اضافه شدن این متد به INumberBase، تمامی انواع عددی استاندارد دات‌نت (از جمله int, double, decimal, BigInteger و...) به طور یکسان از آن پشتیبانی می‌کنند.


۳. جزییات معماری و تغییرات داخلی فریم‌ورک
برای درک بهتر این ویژگی، بررسی تغییرات سطح پایین و تصمیمات معماران دات‌نت خالی از لطف نیست:
۳.۱. جایگاه درINumberBaseو روش پیاده‌سازی
  • متدهای TryParsePartial به عنوان Default Interface Methods (DIM) در static virtual به اینترفیس INumberBase اضافه شده‌اند.
  • جهت حفظ پشتیبانی و سازگاری عقب‌رو (Backward Compatibility)، یک پیاده‌سازی پیش‌فرض (Fallback) برای آن‌ها قرار داده شده است که در صورت عدم اعطای پیاده‌سازی اختصاصی توسط یک نوع عددی سفارشی، متد پایه به TryParse متوسل می‌شود و در صورت موفقیت، کل طول ورودی را به عنوان charsConsumed گزارش می‌کند.

۳.۲. مدیریت Flagهای داخلی و حذفAllowTrailingInvalidCharacters
در طرح اولیه پیشنهاد این API، یک پرچم عمومی (Public Flag) به نام NumberStyles.AllowTrailingInvalidCharacters در نظر گرفته شده بود. اما در طراحی نهایی تصمیم بر آن شد که این پرچم از دسترسی عمومی خارج شده و با یک سنتینل داخلی (Internal Sentinel) به نام Number.AllowTrailingInvalidCharacters جایگزین شود.
  • نحوه عملکرد سنتینل: متد TryParsePartial پس از اعتبارسنجی NumberStyles ورودی کاربر، این سنتینل را که از بالاترین بیت حافظه (0x8000_0000) استفاده می‌کند، با استایل کاربر ترکیب (OR) می‌کند. این کار مانع از آن می‌شود که کاربر بتواند به صورت مستقیم یا غیرمجاز پرچم را از طریق متدهای عادی ارسال کند.
  • سازگاری با انواع خاص: انواعی مانند BigInteger و Complex به دلیل داشتن روتین‌های پارس مشترک و متفاوت از CoreLib، به‌گونه‌ای به‌روزرسانی شده‌اند که سنتینل فوق را پیش از اعتبارسنجی نهایی جدا کرده و پردازش کنند.

۳.۳. دامنه و محدودیت‌ها
شایان ذکر است که این قابلیت در .NET 11 منحصراً برای انواع عددی و در سطح INumberBase پیاده‌سازی شده است. اینترفیس‌های کلی‌تر پارس متن یعنی IParsable، ISpanParsable و IUtf8SpanParsable برای این نسخه خارج از scope قرار گرفته‌اند و ممکن است در نسخه‌های بعدی .NET ارتقا یابند.

۴. نکته تکمیلی: کاربرد در خط‌لوله‌های پردازشی
توسعه‌دهندگانی که با پروتکل‌های شبکه یا System.IO.Pipelines کار می‌کنند، معمولاً با داده‌های بافرشده در قالب ReadOnlySequence یا ReadOnlySpan سرکار دارند. تا پیش از این، برای خواندن یک عدد از بایت‌های ورودی شبکه، مجبور بودید مطمئن شوید که تمام بایت‌های عددی در بافر موجودند و کاراکتر انتهایی آن حتماً پردازش شده است. با TryParsePartial روی ReadOnlySpan، پردازش پروتکل‌های متنی متداول مانند RESP (پروتکل Redis) یا HTTP Headerها بسیار ساده‌تر و سریع‌تر می‌شود؛ چرا که می‌توانید طول exact مقدار عددی را در لحظه دریافت کرده و بلافاصله Offset بافر خواندن را به‌روزرسانی کنید.

۵. نتیجه‌گیری
معرفی TryParsePartial در .NET 11 Preview 7 گام مهمی در جهت تکمیل ابزارهای Generic Math و بهبود کارایی پردازش متون در دات‌نت است. این قابلیت با رفع نیاز به کپی‌سازی حافظه و جداسازی‌های دستی، امکان نگارش پارسرهای سریع‌تر، امن‌تر و خواناتر را برای توسعه‌دهندگان فراهم می‌سازد. پایداری اجرای تست‌ها (عبور موفق بیش از ۷۶ هزار تست واحد در مخزن runtime) نشان از دقت و آمادگی بالای این تغییر معماری در هسته دات‌نت دارد.