مقدمه

ما با فرآیند تبدیل نوع (Type Casting) بین انواع داده‌ی پایه‌ای در C# آشنا هستیم. برای مثال، می‌دانیم که می‌توانیم یک int را به صورت ضمنی به double تبدیل کنیم، اما برای تبدیل double به int به یک تبدیل صریح نیاز داریم. اما اگر بخواهیم بین انواع داده‌ی سفارشی خودمان (کلاس‌ها و ساختارها) یا بین یک نوع سفارشی و یک نوع پایه‌ای، قابلیت تبدیل ایجاد کنیم چه؟ C# به ما اجازه می‌دهد تا با تعریف عملگرهای تبدیل نوع سفارشی (User-Defined Conversion Operators)، این کار را انجام دهیم. این ویژگی، که شباهت زیادی به سربارگذاری عملگرها دارد، به ما کمک می‌کند تا انواع داده‌ی خود را به طور یکپارچه‌تر و طبیعی‌تر در برنامه‌هایمان به کار ببریم.

تبدیل‌های ضمنی (Implicit)

یک تبدیل ضمنی، تبدیلی است که به صورت خودکار و بدون نیاز به سینتکس تبدیل نوع (cast) توسط کامپایلر انجام می‌شود. شما باید یک تبدیل را به صورت ضمنی تعریف کنید تنها و تنها اگر این تبدیل همیشه ایمن باشد، هیچ‌گاه خطایی پرتاب نکند و هیچ اطلاعاتی در حین آن از دست نرود. یک مثال خوب، تبدیل از یک نوع خاص‌تر به یک نوع عمومی‌تر است.

برای تعریف یک تبدیل ضمنی، از کلمات کلیدی public static implicit operator استفاده می‌کنیم. بیایید یک ساختار به نام Fraction (کسر) بسازیم و یک تبدیل ضمنی از آن به double تعریف کنیم.

Copy Icon Program.cs
public readonly struct Fraction
{
    public int Numerator { get; }
    public int Denominator { get; }

    public Fraction(int num, int den) { Numerator = num; Denominator = den; }

    // Implicit conversion from Fraction to double.
    // This conversion is safe and never throws an exception.
    public static implicit operator double(Fraction f)
    {
        return (double)f.Numerator / f.Denominator;
    }
}

اکنون می‌توانیم از این تبدیل به صورت زیر استفاده کنیم:

Copy Icon Program.cs
Fraction half = new(1, 2);

// Implicitly convert the Fraction object to a double. No cast needed.
double result = half;

Console.WriteLine(result); // Output: 0.5

همانطور که می‌بینید، ما توانستیم یک شیء Fraction را مستقیماً به یک متغیر double اختصاص دهیم. کامپایلر با دیدن این کد، عملگر تبدیل ضمنی‌ای را که ما تعریف کرده بودیم، پیدا و فراخوانی می‌کند.

تبدیل‌های صریح (Explicit)

یک تبدیل صریح، تبدیلی است که برنامه‌نویس باید با استفاده از سینتکس تبدیل نوع (TargetType)، به طور مشخص درخواست انجام آن را بدهد. شما باید از تبدیل صریح زمانی استفاده کنید که تبدیل ممکن است با شکست مواجه شود (و یک استثناء پرتاب کند) یا ممکن است در حین آن اطلاعاتی از دست برود (مانند تبدیل double به int که بخش اعشاری حذف می‌شود).

برای تعریف یک تبدیل صریح، از کلمات کلیدی public static explicit operator استفاده می‌کنیم. بیایید یک تبدیل صریح از double به Fraction به ساختار خود اضافه کنیم. این تبدیل می‌تواند با شکست مواجه شود زیرا نمایش دقیق برخی اعداد اعشاری به صورت کسر ممکن نیست.

Copy Icon Program.cs
// Inside the Fraction struct from before...

// Explicit conversion from double to Fraction.
// This conversion is potentially lossy and more complex.
public static explicit operator Fraction(double d)
{
    // This is a simplified conversion logic.
    // A real implementation would be much more complex.
    if (d > int.MaxValue || d < int.MinValue)
        throw new OverflowException();

    return new Fraction((int)(d * 100), 100);
}

برای استفاده از این عملگر، ما باید به طور صریح آن را درخواست کنیم:

Copy Icon Program.cs
double value = 0.75;

// Explicit cast is required.
Fraction threeQuarters = (Fraction)value;

Console.WriteLine($"{threeQuarters.Numerator}/{threeQuarters.Denominator}"); // Output: 75/100

قوانین و بهترین رویه‌ها

  • implicit فقط برای تبدیل‌های ایمن: قانون طلایی این است که از implicit فقط و فقط برای تبدیل‌هایی استفاده کنید که هرگز شکست نمی‌خورند و هیچ‌گاه منجر به از دست رفتن اطلاعات نمی‌شوند. تعریف یک تبدیل ضمنی ناامن می‌تواند منجر به باگ‌های پنهان و غیرمنتظره شود.
  • explicit برای تبدیل‌های پرخطر: برای هر تبدیلی که ممکن است استثناء پرتاب کند یا باعث از دست رفتن دقت شود، حتماً از explicit استفاده کنید تا برنامه‌نویس را مجبور به اعلام صریح نیت خود کنید.
  • از آن‌ها با احتیاط استفاده کنید: تبدیل‌های سفارشی می‌توانند کد را خواناتر کنند، اما استفاده‌ی نادرست یا بیش از حد از آن‌ها می‌تواند باعث سردرگمی شود. همیشه اطمینان حاصل کنید که تبدیل شما یک معنای طبیعی و قابل انتظار برای نوع‌تان دارد.
  • محدودیت تعریف: شما فقط می‌توانید عملگر تبدیل را در تعریف نوع مبدأ یا نوع مقصد پیاده‌سازی کنید. شما نمی‌توانید یک عملگر تبدیل برای دو نوعی که کنترل کد منبع آن‌ها را ندارید، تعریف کنید.