تاثیر فرهنگ جاری سیستم بر روی اعداد در دات نت
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: دو دقیقه

در ویندوز 8، مایکروسافت سعی کرده‌است تا تنظیمات بومی مرتبط با ایران، با واقعیت انطباق بیشتری داشته باشد. برای مثال در فرهنگ فارسی سیستم، علامت ممیز آن / است؛ بجای . معمول.


برای آزمایش آن، سعی کنید چنین برنامه‌ای را در ویندوز 8 اجرا کنید:
using System;

namespace CultureAndNumbers
{
    class Program
    {
        static void Main(string[] args)
        {
            var number = Convert.ToDecimal("12.32");
            Console.WriteLine(number);
        }
    }
}
در اینجا سعی شده‌است یک عدد دسیمال رشته‌ای به معادل عددی آن تبدیل شود.
خروجی آن به نحو ذیل است:


بله! چون در فرهنگ جاری سیستم، علامت ممیز دیگر . نیست، رشته‌ی 12.32 نیز بی‌معنا است و قابل تبدیل به یک عدد دسیمال نخواهد بود.
همچنین باید دقت داشت تاثیر فرهنگ جاری سیستم بر روی متدهای Convert.ToDecimal و decimal.Parse یکسان است.


روشی برای آزمایش موقت فرهنگ‌های مختلف

برای اینکه بتوان فرهنگ‌های مختلف را به سادگی مورد آزمایش قرار داد، نیاز است خاصیت CurrentCulture ترد جاری برنامه را تغییر داد و پس از پایان کار، مجددا این ترد را به فرهنگ پیش از آزمایش تنظیم کرد. برای این منظور می‌توان از پیاده سازی الگوی IDisposable کمک گرفت:
    public class CultureScope : IDisposable
    {
        private readonly CultureInfo _originalCulture;

        public CultureScope(string culture)
        {
            _originalCulture = Thread.CurrentThread.CurrentCulture;
            Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
        }

        public void Dispose()
        {
            Thread.CurrentThread.CurrentCulture = _originalCulture;
        }
    }
در این حالت برای آزمایش فرهنگ فارسی نصب شده در سیستم می‌توان به صورت ذیل عمل کرد. این فرهنگ تنها در چارچوب قطعه کد using، تنظیم می‌شود و پس از آن، مجددا برنامه با فرهنگ اصلی پیش از اجرای این قطعه کد به کار خود ادامه خواهد داد:
using (var cultureScope = new CultureScope("fa-IR"))
{
   Console.WriteLine(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator);
   var number = decimal.Parse("12.32");
   Console.WriteLine(number);
}


تعیین صریح فرهنگ مورد استفاده

یک راه حل برای رفع این مشکل، قید صریح فرهنگ مورد استفاده است. برای مثال اگر اعداد در بانک اطلاعاتی به صورت 12.32 ثبت شده‌اند، می‌توان نوشت:
 using (var cultureScope = new CultureScope("fa-IR"))
{
   Console.WriteLine(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator);
   var number = decimal.Parse("12.32", new CultureInfo("en"));
   Console.WriteLine(number);
}
در اینجا فرهنگ انگلیسی به صورت صریح ذکر شده‌است و دیگر فرهنگ تنظیم شده‌ی fa-IR مورد استفاده قرار نخواهد گرفت.
اما این روش هم قابل اطمینان نیست. زیرا کاربر می‌تواند در کنترل پنل سیستم، به سادگی علامت ممیز را مثلا به # تغییر دهد و در این حالت باز هم برنامه کرش خواهد کرد. راه حلی که برای این مساله در دات نت وجود دارد، فرهنگی است به نام Invariant که یک کپی فقط خواندنی از فرهنگ انگلیسی را به همراه دارد و در این حالت تنظیمات اختصاصی کاربر در کنترل پنل، ندید گرفته خواهند شد:
 using (var cultureScope = new CultureScope("fa-IR"))
{
   Console.WriteLine(Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator);
   var number = decimal.Parse("12.32", CultureInfo.InvariantCulture);
   Console.WriteLine(number);
}
اینبار هر چند فرهنگ ترد جاری به fa-IR تنظیم شده‌است اما چون فرهنگ مورد استفاده CultureInfo.InvariantCulture است، از یک فرهنگ انگلیسی فقط خواندنی که تنظیمات محلی کاربر بر روی آن بی تاثیر است، استفاده خواهد شد. یک چنین کدی در تمام سیستم‌ها بدون مشکل کار می‌کند.
  • #
    ‫۹ سال و ۱۰ ماه قبل، چهارشنبه ۵ آذر ۱۳۹۳، ساعت ۲۰:۵۷
    با تشکر، نکته مهمی بود.
    و اینجاست که برنامه نویس‌ها میگن در سیستم من که درست کار می‌کنه! :)
  • #
    ‫۷ سال و ۱۰ ماه قبل، دوشنبه ۱۷ آبان ۱۳۹۵، ساعت ۱۷:۵۲
    ممنون بابت این مقاله .
    در یکی از پروژه هایی که چندین سال پیش نوشته شده بود برای قفل نرم افزار  از کدی شبیه به زیر استفاده شده بود :
    Math.Sinh(123456.12).ToString();
    قطعه کد فوق در ویندوز‌های قبل از 10 مقدار برگشتی Infinity  را داشت ولی در ویندوز 10 خروجی ∞  را بر میگرداند که باعث خطا در نرم افزار میشد.
    برای رفع آن همانطور که در مطلب فوق نیز عنوان شده، با استفاده از کد زیر قابل اصلاح می‌باشد :
    Math.Sinh(123456.12).ToString(CultureInfo.InvariantCulture);
    • #
      ‫۷ سال و ۱۰ ماه قبل، دوشنبه ۱۷ آبان ۱۳۹۵، ساعت ۱۹:۰۹
      در یکی از پروژه‌ها نیاز بود ما مختصات نقشه گوگل رو به طور اعشاری روی صفحه که با . علامت خورده بودند رو تو خروجی بفرستیم و در سیستم من از / استفاده می‌شد که از همین InvarientCulture استفاده کردیم، البته این نکته رو بگم که خود Resharper هم موقع درج عدد اعشاری در خروجی این پیشنهاد رو میده که با قبول پیشنهاد، کد بالا به طور خودکار درج میشه
    • #
      ‫۲ سال قبل، جمعه ۴ شهریور ۱۴۰۱، ساعت ۱۴:۵۶
      یک نکته‌ی تکمیلی: روش تشخیص زود هنگام مشکلات تاثیر فرهنگ جاری برنامه بر روی محاسبات رشته‌ای آن

      «Meziantou.Analyzer » بسیاری از مشکلات مرتبط با کار با رشته‌ها، عدم ذکر فرهنگ و یا نحوه‌ی مقایسه‌ی رشته‌ها را تشخیص می‌دهد. برای کار با آن فقط کافی است بسته‌ی نیوگت آن‌را به پروژه‌ی خود اضافه کنید:
      <PackageReference Include="Meziantou.Analyzer" Version="1.0.708">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
      و جهت محکم کاری، تمام اخطارها را تبدیل به خطا کنید تا مجبور به اصلاح آن‌ها شوید:
      <PropertyGroup>
          <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
          <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
          <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
          <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis>
      </PropertyGroup>
      و اگر فکر می‌کنید نیازی نیست تا موردی را الزاما اصلاح کنید (این موارد با شناسه‌هایی مانند MA0051 در خروجی کامپایلر ظاهر می‌شوند)، می‌توانید آن‌را به صورت زیر به فایل editorconfig. که در ریشه‌ی solution قرار می‌دهید، اضافه کنید و سپس severity آن‌را به suggestion تغییر دهید:
      # MA0051 : Method is too long. maximum allowed: 60.
      dotnet_diagnostic.MA0051.severity = suggestion