[C#] 013.為型別輸出格式化字串 (讓型別的意境更符合商業邏輯)

讀【157個完美化C#的建議】一書的理解筆記 - 013

重點:讓類別的輸出格式更具意義

流程說明
1. 類別複寫.Tostring()範例
2. 彈性的格式化器
3. 結合.Tostring() 與 格式化器
4. 結論

 

1. 類別複寫.Tostring()範例

我們先宣告以下,classPerson類別,這邊情境設定為

東方名字  :  姓 + 名

西方名字  : 名  + 姓 

/// <summary>
/// classPersonB 類別覆寫 .ToString() 方法
/// </summary>
public class classPerson : IFormattable
{
    /// <summary>
    /// 身分證
    /// </summary>
    public string IDCode { get; set; }

    /// <summary>
    /// 姓
    /// </summary>
    public string FirstName { get; set; }

    /// <summary>
    /// 名
    /// </summary>
    public string LastName { get; set; }

    /// <summary>
    /// 覆寫classPerson在內部呼叫ToString()的方法
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        //東方名字  姓 + 名
        return string.Format("{0} {1}", this.FirstName, this.LastName);
    }

    /// <summary>
    /// 覆寫classPerson於外部呼叫ToString()的方法
    /// </summary>
    /// <param name="format"></param>
    /// <param name="formatProvider"></param>
    /// <returns></returns>
    public string ToString(string format, IFormatProvider formatProvider)
    {
        switch (format)
        {
            case "Ch":
                //東方名字 姓 + 名 ※已於ClassB內部覆寫
                return this.ToString();
            case "Eg":
                //西方名字 名 + 姓
                return string.Format("{0} {1}", this.LastName, this.FirstName);
            default:
                //(預設)東方名字 姓 + 名 ※已於classPerson內部覆寫
                return this.ToString();
        }
    }
}

在主程式中,執行下列語法,完成基本覆寫ToString() 格式化 :

當代入Ch 時 顯示 東方名字  ,Eg時顯示 西方名字

classPerson objB = new classPerson() { IDCode = "H333456789", FirstName = "王", LastName = "小明" };

//Class資訊(類別資訊)
var resultA = objA.ToString();
//王 小明
var resultB = objB.ToString("Ch", null);
//小明 王
var resullC = objB.ToString("Eg", null);
//王 小明
var resullD = objB.ToString();

 

2. 彈性的格式化器

(彈性好,目的性較低)

假設今天有多個類似"功能"的類別,但每個類別基本上都複寫一次相同的程式碼是滿辛苦的 (※Copy Paste 為工程師最高境界)

因此我們可以利用客製化格式化器 ICustomFormatter  來達到這個效果,寫一次,讓所有類別可以一起使用

宣告以下:

/// <summary>
/// 實作"格式化器" - 自訂義套用的格式化字串方法
/// </summary>
public class classPersonFormatter : IFormatProvider, ICustomFormatter
{
    /// <summary>
    /// 說明:IFormatProvider 實作
    /// 目的:傳進來的ICustomFormatter 需求物件,回傳自己
    ///       為了能餵進下方的Format(string format, object arg, IFormatProvider formatProvider) 
    ///       的formatProvider 參數中
    /// </summary>
    /// <param name="formatType"></param>
    /// <returns></returns>
    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter))
        {
            return this;
        }
        else
        {
            return null;
        }
    }

    /// <summary>
    /// 說明:ICustomFormatter 實作
    /// 目的:可以傳進自己想要定義的格式化資料
    /// </summary>
    /// <param name="format"></param>
    /// <param name="arg"></param>
    /// <param name="formatProvider"></param>
    /// <returns></returns>
    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        //這邊因為是範例所以簡化,如果有多個類別 可以先用IS判斷再轉到對應的型別,餵進對應的格式化
        classPerson person = arg as classPerson;
        if (person == null)
        {
            return string.Empty;
        }

        switch (format)
        {
            case "Ch":
                return string.Format("{0} {1}", person.FirstName, person.LastName);
            case "Eg":
                return string.Format("{0} {1}", person.LastName, person.FirstName);
            case "ChM":
                return string.Format("{0} {1} : {2}", person.FirstName, person.LastName, person.IDCode);
            default:
                return string.Format("{0} {1}", person.LastName, person.FirstName);
        }

    }
}

我們建立 ClassPerson 放進基本資料, 並且建立格式化器 classPersonFormatter

//============= 2. 使用格式化器的方法
classPerson objC = new classPerson() { FirstName = "王", LastName = "大明", IDCode = "H333456790" };
classPersonFormatter cpFormatter = new classPersonFormatter();

接著就可以呼叫 格式化器 ==> cpFomatter(傳參 , 型別物件 , null ) 呼叫 對應的格式。

//王 大明
var result_objC_A = objC.ToString();
//王 大明
var result_objC_B = cpFormatter.Format("Ch", objC, null);
//大明 王
var resull_objC_C = cpFormatter.Format("Eg", objC, null);
//王 大明 : H333456790
var resull_objC_D = cpFormatter.Format("ChM", objC, null);

3. 結合.Tostring() 與 格式化器

(目的性明確,彈性變低)

回到第1.項 ,如果以類型本身為出發點,更好的做法是量身訂做,選擇我們今天需要為這個類別使用什麼格式化器,宣告如下:

/// <summary>
/// 結合.Tostring() 與 格式化器
/// </summary>ㄍ
public class classPersonCombination : IFormattable
{
    /// <summary>
    /// 身分證
    /// </summary>
    public string IDCode { get; set; }

    /// <summary>
    /// 姓
    /// </summary>
    public string FirstName { get; set; }

    /// <summary>
    /// 名
    /// </summary>
    public string LastName { get; set; }

    /// <summary>
    /// 覆寫classPersonCombination 在內部呼叫ToString()的方法
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        //東方名字  姓 + 名
        return string.Format("{0} {1}", this.FirstName, this.LastName);
    }

    /// <summary>
    /// 覆寫classPersonCombination 於外部呼叫ToString()的方法
    /// </summary>
    /// <param name="format"></param>
    /// <param name="formatProvider"></param>
    /// <returns></returns>
    public string ToString(string format, IFormatProvider formatProvider)
    {
        switch (format)
        {
            case "Ch":
                //東方名字 姓 + 名 ※已於classPersonCombination內部覆寫
                return this.ToString();
            case "Eg":
                //西方名字 名 + 姓
                return string.Format("{0} {1}", this.LastName, this.FirstName);
            case "ChM":
                //完整資訊
                return string.Format("{0} {1} : {2}", this.FirstName, this.LastName, IDCode);
            default:
                //善用 IFormatProvider 格式化器
                classPersonFormatter myCustomFormatter = formatProvider as classPersonFormatter;

                //如果沒有格式化器,則回傳我們覆寫的ToString()
                if (myCustomFormatter == null)
                    return this.ToString();
                else
                {
                    return myCustomFormatter.Format(format, this, null);
                }
        }
    }
}

在Default 的地方是本範例的關鍵,將格式化器放進去,讓這個類別可以吃指定的格式化器

default:
//善用 IFormatProvider 格式化器
classPersonFormatter myCustomFormatter = formatProvider as classPersonFormatter;

//如果沒有格式化器,則回傳我們覆寫的ToString()
if (myCustomFormatter == null)
    return this.ToString();
else
{
    return myCustomFormatter.Format(format, this, null);
}

回到主程式,第1.版本的Code ,可以簡化為以下:

//============  結合 1. 2.的方法
classPersonCombination objD =
    new classPersonCombination() { FirstName = "王", LastName = "超明", IDCode = "H333456791" };

classPersonFormatter cp2Formatter = new classPersonFormatter();
//王 超明
var result_objD_A = objD.ToString();
//王 超明
var result_objD_B = objD.ToString("Ch", cp2Formatter);
//超明 王
var resull_objD_C = objD.ToString("Eg", cp2Formatter);
//王 超明 : H333456791
var resull_objD_D = objD.ToString("ChM", cp2Formatter);

 

4. 結論

應該依照 1.使用者需求 2.開發規範 3.設計情境

來決定最適合的作法 ,甚至不覆寫類別的格式化(省時),才是最佳做法。

OS: 不過如果有前人寫了完善的格式化器,真的很感動 ,在愈大型的系統裡,威力愈強大。

github連結(Vs2015) : 點我下載