首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 使用者定義型別轉換:隱含/顯式轉換運算子、結構體轉換與設計準則

     編輯:關於C語言

型別轉換:隱含 vs 顯式

C# 允許為自訂型別 (class/struct) 定義轉換運算子,分兩種:

  • 隱含轉換:implicit operator,安全、無資料遺失、自動發生
  • 顯式轉換:explicit operator,可能資料遺失/例外、需強制轉型
// 隱含:int → long (安全擴大)
long l = 42; 

// 顯式:long → int (可能截斷)
int i = (int)long.MaxValue; // -1

// 自訂型別同理
var roman = new RomanNumeral(10); // 隱含 int → RomanNumeral
int value = (int)roman;           // 顯式 RomanNumeral → int

轉換運算子語法規則

  • 必須是 public static
  • 必須標記 implicit 或 explicit (二選一)
  • 參數或回傳型別必須包含宣告型別 (來源或目標)
  • 不可轉換為 object、介面型別、基底類別 (由繼承處理)
  • 不可定義相同來源/目標的隱含與顯式雙重轉換

實戰範例一:羅馬數字結構體

using System;

public readonly struct RomanNumeral : IEquatable
{
    private readonly int _value;

    public RomanNumeral(int value)
    {
        if (value <= 0 || value > 3999)
            throw new ArgumentOutOfRangeException(nameof(value), "羅馬數字範圍 1-3999");
        _value = value;
    }

    // === 隱含轉換:int → RomanNumeral (安全、無遺失) ===
    public static implicit operator RomanNumeral(int value) => new RomanNumeral(value);

    // === 顯式轉換:RomanNumeral → int (需強制轉型) ===
    public static explicit operator int(RomanNumeral roman) => roman._value;

    // === 隱含轉換:RomanNumeral → string (顯示用,安全) ===
    public static implicit operator string(RomanNumeral roman) => roman.ToRomanString();

    // === 核心邏輯 ===
    private string ToRomanString()
    {
        // 簡化實作
        return _value switch
        {
            1 => "I", 4 => "IV", 5 => "V", 9 => "IX", 10 => "X",
            40 => "XL", 50 => "L", 90 => "XC", 100 => "C",
            400 => "CD", 500 => "D", 900 => "CM", 1000 => "M",
            _ => $"[{_value}]" // 簡化:非標準值直接顯示數字
        };
    }

    public override string ToString() => ToRomanString();
    public bool Equals(RomanNumeral other) => _value == other._value;
    public override bool Equals(object obj) => obj is RomanNumeral r && Equals(r);
    public override int GetHashCode() => _value.GetHashCode();
    public static bool operator ==(RomanNumeral a, RomanNumeral b) => a.Equals(b);
    public static bool operator !=(RomanNumeral a, RomanNumeral b) => !a.Equals(b);
}

// 使用示範
public class Demo
{
    public static void Main()
    {
        // 隱含轉換:int → RomanNumeral
        RomanNumeral numeral = 10;          // 自動呼叫 implicit operator RomanNumeral(int)
        RomanNumeral numeral2 = 20;

        // 顯式轉換:RomanNumeral → int (必須加括號)
        int value = (int)numeral;           // 呼叫 explicit operator int(RomanNumeral)
        Console.WriteLine($"int 值: {value}"); // 10

        // 隱含轉換:RomanNumeral → string
        Console.WriteLine($"字串: {numeral}"); // 自動呼叫 implicit operator string → "X"
        Console.WriteLine(numeral2);        // "XX"

        // 顯式轉換鏈:RomanNumeral → int → short
        short s = (short)(int)numeral;      // 先顯式轉 int,再隱含/顯式轉 short
        Console.WriteLine($"short: {s}");   // 10
    }
}

實戰範例二:結構體之間的相互轉換

public readonly struct BinaryNumeral : IEquatable
{
    private readonly int _value;

    public BinaryNumeral(int value)
    {
        if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
        _value = value;
    }

    // int → BinaryNumeral (隱含)
    public static implicit operator BinaryNumeral(int value) => new BinaryNumeral(value);

    // BinaryNumeral → int (顯式)
    public static explicit operator int(BinaryNumeral bin) => bin._value;

    // BinaryNumeral → string (隱含)
    public static implicit operator string(BinaryNumeral bin) => Convert.ToString(bin._value, 2);

    // === 關鍵:BinaryNumeral ↔ RomanNumeral 相互轉換 ===
    
    // RomanNumeral → BinaryNumeral (顯式:語意不同進制,需明確意圖)
    public static explicit operator BinaryNumeral(RomanNumeral roman) 
        => new BinaryNumeral((int)roman); // 先轉 int,再建構 BinaryNumeral

    // BinaryNumeral → RomanNumeral (顯式)
    public static explicit operator RomanNumeral(BinaryNumeral bin) 
        => new RomanNumeral((int)bin);

    public override string ToString() => Convert.ToString(_value, 2);
    public bool Equals(BinaryNumeral other) => _value == other._value;
    public override bool Equals(object obj) => obj is BinaryNumeral b && Equals(b);
    public override int GetHashCode() => _value.GetHashCode();
}

// 使用
public class CrossConvertDemo
{
    public static void Main()
    {
        RomanNumeral roman = 10;        // "X"
        BinaryNumeral binary = (BinaryNumeral)(int)roman; // 必須先轉 int,再轉 BinaryNumeral
        
        Console.WriteLine($"Roman: {roman}");     // X
        Console.WriteLine($"Binary: {binary}");   // 1010
        Console.WriteLine($"Binary int: {(int)binary}"); // 10

        // 反向
        BinaryNumeral bin2 = 15;        // 1111
        RomanNumeral rom2 = (RomanNumeral)bin2;     // XV
        Console.WriteLine($"Roman from binary: {rom2}"); // XV
    }
}

設計準則與最佳實務

何時使用隱含轉換?

  • 來源型別值完整保留於目標型別 (如 int → long, Derived → Base)
  • 無資料遺失、無精度損失、無例外風險
  • 語意上「是一種」關係 (如 RomanNumeral 本質是 int)

何時使用顯式轉換?

  • 可能資料遺失 (如 long → int, double → int)
  • 可能拋出例外 (如解析字串、範圍檢查)
  • 語意上概念不同 (如 RomanNumeral ↔ BinaryNumeral、Celsius ↔ Fahrenheit)
  • 呼叫者必須明確意圖 (加上括號轉型)

結構體 vs 類別的轉換差異

特性結構體類別
轉換運算子支援支援
隱含轉換至介面不支援 (需顯式)不支援
預設值全零位元 (無參建構函式隱含)null
null 檢查不需要 (非 nullable 時)必要
適用場景值語意、輕量、不可變、原生型別擴充參考語意、複雜行為、多型、身分識別

常見陷阱

// ❌ 錯誤:定義相同來源/目標的隱含+顯式
public static implicit operator MyType(int x) => new MyType(x);
public static explicit operator MyType(int x) => new MyType(x); // CS0556

// ❌ 錯誤:轉換至 object 或介面
public static implicit operator object(MyType t) => t; // CS0552

// ❌ 錯誤:使用者定義轉換鏈超過一步 (編譯器不自動串聯)
RomanNumeral r = 10;
BinaryNumeral b = (BinaryNumeral)r; // 錯誤:無直接轉換
// 必須手動拆解:
BinaryNumeral b = (BinaryNumeral)(int)r; // 正確

// ✅ 正確:提供便利靜態工廠方法補充
public static BinaryNumeral FromRoman(RomanNumeral r) => (BinaryNumeral)(int)r;

進階:搭配運算子多載

public readonly struct Temperature
{
    private readonly double _celsius;

    public Temperature(double celsius) => _celsius = celsius;

    // 隱含:Celsius 數值 → Temperature
    public static implicit operator Temperature(double celsius) => new Temperature(celsius);

    // 顯式:Temperature → Celsius 數值
    public static explicit operator double(Temperature t) => t._celsius;

    // 華氏度轉換 (顯式:不同單位)
    public static explicit operator Temperature(Fahrenheit f) 
        => new Temperature((f.Value - 32) * 5 / 9);

    // 運算子多載
    public static Temperature operator +(Temperature a, Temperature b) 
        => new Temperature(a._celsius + b._celsius);
    
    public static Temperature operator -(Temperature a, Temperature b) 
        => new Temperature(a._celsius - b._celsius);

    public override string ToString() => $"{_celsius:F1}°C";
}

public readonly struct Fahrenheit
{
    public double Value { get; }
    public Fahrenheit(double value) => Value = value;
    public static implicit operator Fahrenheit(double f) => new Fahrenheit(f);
    public override string ToString() => $"{Value:F1}°F";
}

// 使用
var t1 = (Temperature)25.0;        // 25°C
var t2 = (Temperature)30.0;        // 30°C
var sum = t1 + t2;                 // 55°C
var f = (Fahrenheit)98.6;          // 98.6°F
var t3 = (Temperature)f;           // 37°C (體溫)

總結

使用者定義轉換讓自訂型別擁有內建型別般的自然轉換體驗。核心原則:隱含 = 安全/自動,顯式 = 危險/明確。善用 readonly struct、implicit/explicit operator、運算子多載,並遵循「單步轉換、避免驚喜、提供工廠方法補充」等準則,即能設計出直覺、安全、高效的值型別轉換系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved