[C#] 型別轉換關鍵字 implicit/explicit 心得筆記

紀錄型別轉換關鍵字 implicit(隱含轉換/隱式轉換)與 explicit(明確轉換/顯式轉換)使用方式跟注意事項

C#中當宣告一個變數類型之後,需要將此變數指派給另一個類型的值就要用到類型轉換

抽空記錄一下常常無形中使用到卻很少人特別去注意的功能-implicit/explicit 型別轉換!

這邊簡單實作兩個例子來看看 implicit 與 explicit 有什麼差別

// 這是 implicit 
int nNumber = 0;
double fNumber;

fNumber = nNumber;
// 這是 explicit
int nNumber;
double fNumber = 100.1;

nNumber = (int)fNumber;

當把上述例子輸出就會發現都是100,但第二個例子明顯有資料缺失了!

資料缺失不是 implicit/explicit 型別轉換的效果,而是因為高精度型別轉低精度型別造成!

那 implicit/explicit 型別轉換使用的情境場景如下:

  1. 隱含轉換/隱式轉換(implicit)
    • 使用上不需要特殊語法便可以轉型
    • 應用場景是型別轉換不會造成資料失真(或精度損失);比如從整數 int 轉為浮點數 float
  2. 明確轉換/顯示轉換(explicit)
    • 使用上需要用強制型轉運算式: (T)E → 表示從型別 E 轉成型別 T
    • 應用場景是型別轉換會有資料誤差或會因此造成錯誤產生,需額外做處理的情況

implicit 關鍵字可以讓程式碼看上去簡潔優雅,但怎麼使用很重要!!

尤其會造成資訊缺失的型別轉換需要用 explicit 關鍵字才能在編譯時期警告調用者評估這樣的缺失是否造成無預期的情況!

當會引發異常情況或造成資訊缺失時,就請使用 explicit

總的來說~精度低轉高用 implicit,精度高轉低用 explicit!資訊多轉資訊少用 implicit,資訊少轉資訊多用 explicit!

implicit/explicit 型別轉換方法的宣告須具備以下因素

  1. 存取修飾詞須是 public
  2. 宣告為靜態方法 static
  3. 使用關鍵字 implicit 或 explicit
  4. 使用運算子宣告關鍵字 operator
  5. 定義轉換的型別(以下寫為 Destination)
  6. 傳入欲轉換的型別(以下寫為 Source)

必須使用 public 存取修飾詞很好理解是需要給類別外使用

宣告為靜態方法就是為了不用實例化類別就可以使用此方法

使用過運算子宣告關鍵字 operator 就可以很好理解使用上兩項修飾詞關鍵字的原因

而第4項剛好印證這兩個型別轉換關鍵字實作上是當作運算子的類型

具體宣告定義型別轉換方法的語句格式如下

  • 隱含轉換/隱式轉換/implicit
public static implicit operator Destination(Source src)
 {
     return new Destination();
 }
  • 明確轉換/顯示轉換explicit
public static explicit operator Destination(Source src)
 {
     return new Destination();
 }

這裡用一個例子實際演練,這三個類別的數字越大模擬表示精度越高的情況

先宣告三個自訂類別,這三個類別結構各自有一個字串屬性跟兩個型別轉換的方法

型別轉換方法中會將類別名稱加入字串表示這是 implicit 轉換還是 explicit 轉換

public class Lv1
 {
     private string _Name;
     public string Name
     {
         get { return _Name; }
         set
         {
             _Name = value;
         }
     }
 
     public Lv1(string LvName)
     {
         this.Name = LvName;
     }
 
     public static implicit operator Lv2(Lv1 lv1)
     {
         return new Lv2("(隱式轉換至Lv2)" + lv1.Name);
     }
     public static implicit operator Lv3(Lv1 lv1)
     {
         return new Lv3("(隱式轉換至Lv3)" + lv1.Name);
     }
 }
 public class Lv2
 {
     private string _Name;
     public string Name
     {
         get { return _Name; }
         set
         {
             _Name = value;
         }
     }
 
     public Lv2(string LvName)
     {
         this.Name = LvName;
     }
 
     public static explicit operator Lv1(Lv2 lv2)
     {
         return new Lv1("(顯式轉換至Lv1)" + lv2.Name);
     }
     public static implicit operator Lv3(Lv2 lv2)
     {
         return new Lv3("(隱式轉換至Lv3)" + lv2.Name);
     }
 }
 public class Lv3
 {
     private string _Name;
     public string Name
     {
         get { return _Name; }
         set
         {
             _Name = value;
         }
     }
 
     public Lv3(string LvName)
     {
         this.Name = LvName;
     }
 
     public static explicit operator Lv1(Lv3 lv3)
     {
         return new Lv1("(顯式轉換至Lv1)" + lv3.Name);
     }
     public static explicit operator Lv2(Lv3 lv3)
     {
         return new Lv2("(顯式轉換至Lv2)" + lv3.Name);
     }
 }

txtObj1/txtObj2/txtObj3 是 TextBox 輸入控制元件用來給類別的 Name 屬性賦值

txtContent 是 TextBlock 顯示文字控制元件用來顯示資訊

這裡改寫一下我發在其他論壇上實作部分,這樣比較好顯示轉換前的類型與轉換後的類型

Lv1 L1 = new Lv1(txtObj1.Text);
Lv2 L2 = new Lv2(txtObj2.Text);
Lv3 L3 = new Lv3(txtObj3.Text);

txtContent.Text = "初始狀態:\r\n" + 
                  "第一層:" + L1.Name + "\r\n" +
                  "第二層:" + L2.Name + "\r\n" +
                  "第三層:" + L3.Name + "\r\n" ;

Lv1 tmpL1;
Lv2 tmpL2;
Lv3 tmpL3;

txtContent.Text = txtContent.Text + "\r\n";
tmpL2 = L1;
txtContent.Text = txtContent.Text + "轉換前類別: " + L1.GetType().Name + "\r\n";
txtContent.Text = txtContent.Text + tmpL2.Name + "\r\n";
txtContent.Text = txtContent.Text + "轉換後類別: " + tmpL2.GetType().Name + "\r\n";
tmpL3 = L1;
txtContent.Text = txtContent.Text + "轉換前類別: " + L1.GetType().Name + "\r\n";
txtContent.Text = txtContent.Text + tmpL3.Name + "\r\n";
txtContent.Text = txtContent.Text + "轉換後類別: " + tmpL3.GetType().Name + "\r\n";

txtContent.Text = txtContent.Text + "\r\n";
tmpL1 = (Lv1)L2;
txtContent.Text = txtContent.Text + "轉換前類別: " + L2.GetType().Name + "\r\n";
txtContent.Text = txtContent.Text + tmpL1.Name + "\r\n";
txtContent.Text = txtContent.Text + "轉換後類別: " + tmpL1.GetType().Name + "\r\n";
tmpL3 = L2;
txtContent.Text = txtContent.Text + "轉換前類別: " + L2.GetType().Name + "\r\n";
txtContent.Text = txtContent.Text + tmpL3.Name + "\r\n";
txtContent.Text = txtContent.Text + "轉換後類別: " + tmpL3.GetType().Name + "\r\n";

txtContent.Text = txtContent.Text + "\r\n";
tmpL1 = (Lv1)L3;
txtContent.Text = txtContent.Text + "轉換前類別: " + L3.GetType().Name + "\r\n";
txtContent.Text = txtContent.Text + tmpL1.Name + "\r\n";
txtContent.Text = txtContent.Text + "轉換後類別: " + tmpL1.GetType().Name + "\r\n";
tmpL2 = (Lv2)L3;
txtContent.Text = txtContent.Text + "轉換前類別: " + L3.GetType().Name + "\r\n";
txtContent.Text = txtContent.Text + tmpL2.Name + "\r\n";
txtContent.Text = txtContent.Text + "轉換後類別: " + tmpL2.GetType().Name + "\r\n";
執行結果

 

 

 

 

 

 

 

 

 

 

 

 

 

 

如果改寫 Lv1 與 Lv2 的型別轉換方法位置如下,也是可以編譯過且達成相同效果

public class Lv1
{
    private string _Name;
    public string Name
    {
        get { return _Name; }
        set
        {
            _Name = value;
        }
    }

    public Lv1(string LvName)
    {
        this.Name = LvName;
    }

    public static implicit operator Lv3(Lv1 lv1)
    {
        return new Lv3("(隱式轉換至Lv3)" + lv1.Name);
    }
}
public class Lv2
{
    private string _Name;
    public string Name
    {
        get { return _Name; }
        set
        {
            _Name = value;
        }
    }

    public Lv2(string LvName)
    {
        this.Name = LvName;
    }

    public static explicit operator Lv1(Lv2 lv2)
    {
        return new Lv1("(顯式轉換至Lv1)" + lv2.Name);
    }
    public static implicit operator Lv2(Lv1 lv1)
    {
        return new Lv2("(隱式轉換至Lv2)" + lv1.Name);
    }
    public static implicit operator Lv3(Lv2 lv2)
    {
        return new Lv3("(隱式轉換至Lv3)" + lv2.Name);
    }
}

當兩個類別互相轉換的方法可以寫在來源類別(Source)內或目標類別(Destination)內,但不能同時存在兩個類別內!

實務上建議把轉換方法寫在來源類別內維護起來也比較直覺!