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 staticimplicit 或 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)| 特性 | 結構體 | 類別 |
|---|---|---|
| 轉換運算子 | 支援 | 支援 |
| 隱含轉換至介面 | 不支援 (需顯式) | 不支援 |
| 預設值 | 全零位元 (無參建構函式隱含) | 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、運算子多載,並遵循「單步轉換、避免驚喜、提供工廠方法補充」等準則,即能設計出直覺、安全、高效的值型別轉換系統。