2011年12月16日 星期五

型別轉換

何謂型別轉換

資料在進行型別轉換時,可分成明確轉換或隱含轉換二種。

  • 明確轉換(explicit conversion):明確指定轉換的型別。
  • 隱含轉換(implicit conversion):由系統自動進行不同型別資料的轉換。
//明確轉型 
long a = System.Convert.ToInt64(10000);

//明確轉型
byte c = (byte) a;   //long 轉 byte 可能會造成資料丟失 

//隱含轉型
a = c;              //byte 轉 long

隱含轉換的過程,可能造成下面二種情況:

  • 擴展轉換(widening conversion):隱含轉換時,目的型別精準度大於原始型別。
  • 縮小轉換(narrowing conversion):隱含轉換時,目的型別精準度小於原始型別。

VB預設二種情況都是允許的,C#則禁止縮小轉換。 VB若要關閉隱含轉換,可在程式碼檔案最前面加上 Option Strict On。 或者在[專案]->[屬性]->[編譯],將整個專案的 Option Strict 設為 On。

int i = 10;
double d = 5.5;

d = i;          // 擴展轉換
//i = d;        // 縮小轉換: C#不允許縮小轉換,所以會產生 double 不能隱含轉換為 int 的錯誤.
i = (int)5.5;   // explicit conversion , may lost some information

何謂 Boxing 與 Unboxing

  • Boxing : Value Type to Reference Type, implicit convertion.
  • Unboxing : Reference Type to Value Type, explicit convertion.
int i = 123;
object obj = i;     //boxing會自動隱含轉換
object obj = 123;
int i = (int)obj;   //unboxing 動作必須明確宣告轉換型別,無法自動隱含轉換
int j = obj;        //這一行會錯誤。

下面這個例子,第 4 和第 8 行會發生轉換錯誤。原因?

int no1 = 10;
    object obj1 = no1;
    int no2 = (int)obj1;
    short no3 = (short)obj1;

    short no4 = 10;
    object obj2 = no4;
    int no5 = (int)obj2;
    short no6 = (short)obj2;
  • 行2:obj1 boxing 一個 int 的資料。
  • 行3:no2 將 obj1 中的資料 unboxing 回 int.
  • 行4:obj1 中的資料為一個 int 型別資料,無法 unboxing 成 short 型別。
  • 行8:obj2 中的資料為一個 short 型別資料,無法 unboxing 成 int 型別。

如何實作自訂型別的轉換 (Conversion)

若要設計自訂型別的轉型,通常必須根據想要轉換的型別使用不同的技巧。例如以下幾種方式:

  • 覆寫 ToString:提供將其他型別轉換為字串的功能。
  • 覆寫 Parse:提供將字串轉換為數字型別的功能。
  • 定義轉換運算子:可執行數值之間的轉換功能。
  • 加入 System.IConvertible 介面:在自訂型別中,實作各種型別的轉換方法。

ToString & Parse

  • ToString:這個方法是覆寫自 Object 的 ToString 方法,可用來將數值型態的執行個體,轉換成對等的字串。
  • Parse:每個數值型別都具有靜態的 Parse 方法,可用來將字串轉換成本身的數值型別。
int iNum = 10;
double dNum = 5.5;
string str;

str = iNum.ToString();       //將int轉換為字串。
dNum = double.Parse(str);    //將字串轉換為double。
iNum = int.Parse(str);       //將字串轉換為int。

無參數的 ToString 方法是覆寫自 Object.ToString 。 通常.NET 內建的型別中,也會自訂其它多載的 ToString 方法,以應付不同需求狀況。例如底下為 DateTime 型別4個多載方法的樣式:

public override string ToString();
public string ToString(IFormatProvider provider);
public string ToString(string format);
public string ToString(string format, IFormatProvider provider);
DateTime theDate = new DateTime(2012, 7, 20);
Console.WriteLine(theDate.ToString());          // 2012/7/20 上午 12:00:00
Console.WriteLine(theDate.ToString("d"));       // 2012/7/20

//使用委內瑞拉的 CultureInfo 來顯示日期
CultureInfo esVE = new CultureInfo("es-VE");
Console.WriteLine(theDate.ToString("d", esVE)); // 20/07/2012

//使用克羅埃西亞的 DateTimeFormatInfo 來顯示日期
DateTimeFormatInfo fmt = new CultureInfo("hr-HR").DateTimeFormat;
Console.WriteLine(theDate.ToString("d", fmt));  // 20.7.2012
Console.WriteLine(theDate.ToString(fmt.ShortDatePattern));  // 20.7.2012
int num = 4567;
Console.WriteLine(num.ToString());          // 4567
Console.WriteLine(num.ToString("d"));       // 4567
Console.WriteLine(num.ToString("d6"));      // 004567
Console.WriteLine(num.ToString("C"));       // NT$4,567.00
Console.WriteLine(num.ToString("N"));       // 4,567.00

//使用大陸的 CultureInfo
CultureInfo zhCN = new CultureInfo("zh-CN");
Console.WriteLine(num.ToString("C", zhCN)); // ¥4,567.00

//使用克羅埃西亞的 NumberFormatInfo
NumberFormatInfo fmt = new CultureInfo("hr-HR").NumberFormat;
Console.WriteLine(num.ToString("C", fmt));  // 4.567,00 kn

轉換運算子 (Conversion operator)

轉換運算子就是在自訂類別中直接宣告型別的轉換,該自訂型別可與其他型別或.NET型別互相轉換。
轉換運算子有二種屬性, explicitimplicit。詳情請參考:使用轉換運算子 (C# 程式設計手冊)

隱含轉換 vs 明確轉換

  • 宣告為隱含的轉換運算子(implicit),會在必要時自動發生,也就是客戶端不需要動作就會自行轉換。
    此種轉換使用在資料不會失去精準度時使用,例如 int => double。
  • 宣告為明確的轉換運算子(explicit),需要呼叫轉型,也就是客戶端得使用強制轉型才會進行轉換動作。
    此種轉換使用在容易失去精準度時使用,例如 double => int。

使用轉換運算子時,要注意以下幾點︰

  • 所有轉換必須宣告為 static
  • 轉換運算子的宣告方法就和運算子一樣,並且以轉換成的型別來命名。
  • 轉換運算子的宣告,轉換的結果或傳入的參數,其中一個必須是轉換型別。

使用 operator 關鍵字來建立使用者定義轉換的語法如下:

//目標型別或來源型別,其中一個必須是轉換型別
public static implicit operator 目標型別 ( 來源型別名稱 )
public static explicit operator 目標型別 ( 來源型別名稱 )
public static implicit operator float (myType _type)        // 定義隱含轉換運算子,允許 myType 型別隱含轉換成 float 型別
public static explicit operator int (myType _type)          // 定義明確轉換運算子, myType 型別必須明確轉換成 int 型別
public static explicit operator myType (int _value)    
public static implicit operator myType (float _value)  

下面這個例子中的CTemp、FTemp是二個自訂型別,程式碼示範如何利用轉換運算子做型別轉換。若我們需求程式可以達到:
1. 二者都可以和 float 型別做隱含轉換。
2. FTemp 可隱含轉換成 CTemp ; CTemp 須明確轉換成 FTemp。

public abstract class Temperature
{
    public float Temp { get; set; }
    public Temperature(float temp)
    {
        this.Temp = temp;
    }
    public override string ToString()
    {
        return Temp.ToString("0.00"); 
    }
}
public class CTemp : Temperature
{
    //將建構子設為 private,代表無法用 new 關鍵字new出 CTemp 型別
    private CTemp(float temp) : base(temp) { }

    public static implicit operator float(CTemp c)      //隱含轉換 (將CTemp轉成float)
    {
        return c.Temp;
    }

    public static implicit operator CTemp(float temp)   //隱含轉換 (將float轉成CTemp)
    {
        return new CTemp(temp);
    }

    public static implicit operator CTemp(FTemp f)      //隱含轉換 (將FTemp轉成CTemp)
    {
        return (((f.Temp - 32) * 5) / 9);
    }

    public static explicit operator FTemp(CTemp c)      //明確轉換 (將CTemp轉成FTemp)
    {
        return (((c.Temp * 9) / 5) + 32);
    }
}

public class FTemp : Temperature
{
    private FTemp(float temp) : base(temp) { }

    public static implicit operator FTemp(float temp)
    {
        return new FTemp(temp);
    }
    public static implicit operator float(FTemp f)
    {
        return f.Temp;
    }
}

CTemp tempC1, tempC2, tempC3;
FTemp tempF = 95;

tempC1 = 28.563f;            // float 允許隱含轉換成 CTemp
tempC2 = 25;                 // int 允許隱含轉換成 CTemp
tempC3 = tempF;              // FTemp 允許隱含轉換成 CTemp

Console.WriteLine(tempC1.ToString());   // 28.56
Console.WriteLine(tempC2.ToString());   // 25.00
Console.WriteLine(tempC3.ToString());   // 35.00

//tempF = tempC3;             // CTemp 不能隱含轉換成 FTemp
tempF = (FTemp)tempC3;        // 必須明確轉換成 FTemp


Console.WriteLine(tempF.ToString());   // 95.00

實作 System.IConvertible 介面

System.IConvertible 介面是用來定義自訂型別轉換成具有等值的 CLR 型別 (如 Boolean、Byte、Int16 ...) 。
若要實作 System.IConvertible 介面,可以將 IConvertible 介面加入到型別定義中,然後實作該介面。

TypeA a = 68;
string s = a.ToString();
byte be = Convert.ToByte(a);
bool bl = Convert.ToBoolean(a);
int i = Convert.ToInt16(a);

class TypeA : IConvertible
{
    protected int Value;

    public static implicit operator TypeA(int arg)
    {
        TypeA res = new TypeA();
        res.Value = arg;
        return res;
    }

    public override string ToString()
    {
        return this.Value.ToString();
    }

    public bool ToBoolean(IFormatProvider provider)
    {
        if (Value > 0)
            return true;
        else
            return false;
    }

    public byte ToByte(IFormatProvider provider)
    {
        if (Value < 255)
            return (byte)Value;
        else
            return 0;
    }
    public short ToInt16(IFormatProvider provider)
    {
        if (Value < 32767)
            return (short)Value;
        else
            return 0;
    }
}

實作 System.IFormattable 介面

IFormattable 介面是用來自訂型別的字串顯示樣式,它會根據「格式字串」和「格式提供者」這二個參數,將物件轉換成其字串表示。

格式字串:通常用來定義外觀。例如,.NET Framework 定義了下列幾種格式字串:

格式提供者:通常用來定義字串使用的符號。例如,.NET Framework 會定義三種格式提供者:

下面例子,我們簡單設計一個用來表示攝氏溫度的型別,並繼承 IFormattable 介面。 繼承 IFormattable 介面就必須實作 ToString 方法,我們在這個方法中,依不同的參數值顯示不同的溫度格式。

public class MyTemperature : IFormattable
{
    private float m_Temp;
    public MyTemperature(float temperature)
    {
        this.m_Temp = temperature;
    }

    public string ToString(string format, IFormatProvider formatProvider)
    {
        if (String.IsNullOrEmpty(format)) format = "C";

        switch (format)
        {
            case "F":
                return (((m_Temp * 9) / 5) + 32) + " °F";
            case "C":
                return m_Temp.ToString() + " °C";
            default:
                throw new FormatException("Invalid format string");
        }

    }
}

private void button11_Click(object sender, EventArgs e)
{
    MyTemperature temp = new MyTemperature(32.6f);
    Console.WriteLine(temp.ToString());             //PracticeMCTS.Chapter01.Lesson4+MyTemperature
    Console.WriteLine(temp.ToString("C", null));    //32.6 °C
    Console.WriteLine(temp.ToString("F", null));    //90.68 °F
}

沒有留言:

張貼留言