選擇性引數(Optional Argument)是我愛用的C# 4.0新特性之一。

以傳入arg1, arg2引數的方法為例,若要讓arg2變成選擇性引數,過去得用多載(Overloading)實現,需要宣告成
    void someMethod(string arg1) { someMethod(arg1, "arg2_default_value"); }

    void someMethod(string arg1, string arg2) { ... }
兩個方法;而在C# 4.0只需宣告成
    void someMethod(string arg1, string arg2 = "arg2_default_value")
就搞定,當有多個選擇性引數時,可省下一大堆額外的方法宣告,簡潔許多。

不過,當引數為DateTime型別時,則會遇到一個困擾。例如: 我想將sayHello的time引數改為選擇性,未提供時直接用DateTime.Now。

using System;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            sayHello("Jeffrey", DateTime.Now);
            Console.Read();
        }
        static void sayHello(string name, DateTime time)
        {
            Console.WriteLine(
                "Hello {0}, the system time is {1:HH:mm:ss}.",
                name, time);
        }
    }
}

依直覺,我們會寫成DateTime time = DateTime.Now

using System;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            sayHello("Jeffrey");
            Console.Read();
        }
        static void sayHello(string name, DateTime time = DateTime.Now)
        {
            Console.WriteLine(
                "Hello {0}, the system time is {1:HH:mm:ss}.",
                name, time);
        }
    }
}

但以上寫法無法編譯,會彈出"Default parameter value for 'time' must be a compile-time constant"錯誤,原因是選擇性引數預設值必須為編譯時就已確定的常數(Constant)。即便改用DateTime time = DateTime.MinValue當預設值,方法內部再用if (time == DateTime.MinValue) time = DateTime.Now;動態指定,依然行不通! 原因是DateTime.MinValue雖然是一個固定不動的值,但由於它是DateTime的一個唯讀欄位,並不符合編譯時期常數的要求。

針對這個問題,過去看到比較多的做法是將time由DateTime改為DateTime?,在一開始時設為null,在方法內部再透過if (time == null) time = DateTime.Now;,這方法可行,但有點美中不足: 當time的型別變成了DateTime?,後續需要用它做為其他方法的DateTime引數時,必須改用time.Value,直接給time會產生型別不符錯誤(因DateTime與DateTime?是兩種不同的型別)。

今天爬文學到一個關鍵字default(T),可用來處理泛型有時要給null(參考型別, Reference Type),有時要給0(實值型別, Value Type)的情境。而它恰巧也可應用在本案例中,宣告成DateTime time = default(DateTime)可以通過編譯,而其值等於DateTime.MinValue,故可用if (time == DateTime.MinValue) time = DateTime.Now;在方法內部進行後續處理,同時也維持time是DateTime型別,個人覺得是更好的解決方案。

程式範例如下:

using System;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime t = DateTime.Now.AddHours(1);
            sayHello1("Jeffrey");
            sayHello1("Jeffrey", t);
            sayHello2("Jeffrey");
            sayHello2("Jeffrey", t);
            Console.Read();
        }
        //解法1: 將參數改為Nullable<DateTime>
        static void sayHello1(string name, DateTime? time = null)
        {
            if (time == null) time = DateTime.Now;
            Console.WriteLine(
                "Hello {0}, the system time is {1:HH:mm:ss}.",
                name, time);
            //缺點: 後續應用在限定DateTime型別的場合,得改寫成time.Value
            DateTime time2 = time.Value.AddHours(2);
            (new System.Globalization.TaiwanCalendar())
                .GetDayOfMonth(time.Value);
        }
        //解法2: 利用default(DateTime) (其值相當於DateTime.MinValue)
        static void sayHello2(string name, DateTime time = default(DateTime))
        {
            if (time == DateTime.MinValue) time = DateTime.Now;
            Console.WriteLine(
                "Hello {0}, the system time is {1:HH:mm:ss}.",
                name, time);
            //使用default(DateTime)時,time為DateTime型別,應用較方便
            DateTime time2 = time.AddHours(2);
            (new System.Globalization.TaiwanCalendar())
                .GetDayOfMonth(time);
        }
 
    }
}

Comments

# by Allen Kuo

我剛才測試時,發現使用vs.net 2010, 新專案選.NET Framework 2.0, 寫sayHello2()其實也可以正常運作 static DateTime gettomorrow(DateTime dt = default(DateTime)) 難道這語法其實在2.0時就有了

# by Jeffrey

to Allen Kuo, 真的耶! 找到一篇文章http://blogs.us.sogeti.com/dmaher/2011/02/optional-and-named-parameters-for-net-20/ 其解釋為 The reason it works in .Net Framework 2.0, is because the CLR always supported optional parameters because of VB .Net. It is just that c# took a while to support that feature.

# by Leo

To Allen, 代位回答:default(T) 的確是 2.0 就有的語法。黑大上面留的 MSDN 連結,就是 VS2005 版本的說明。

# by Jeffrey

to Leo, 我猜Allen指的是Optional Argument,當初它被當成C# 4.0的亮點之一(http://weblogs.asp.net/scottgu/archive/2010/04/02/optional-parameters-and-named-arguments-in-c-4-and-a-cool-scenario-w-asp-net-mvc-2.aspx) 倒沒想到透過VS2010編譯,在.NET 2.0上也能支援。

# by Allen Kuo

沒錯,我的意思就是如此,最近也遇到二個怪問題 http://www.allenkuo.com/userfiles/article/2012q1/2012-3-3_12-11-49.png http://www.allenkuo.com/GenericArticle/view1283.aspx 跟我平時想的不同

# by Billy

回 Allen 的圖片: http://www.allenkuo.com/userfiles/article/2012q1/2012-3-3_12-11-49.png 因為 datatable 為reference type,傳進 doB 時加 row 改變的是同一個 object。但在傳進 doC 後 assign 新的 datatable,此時 doC 內的 dt 已只向新的 memory location,對外面的 datatable 並沒有影響。 可以參考黑大的 Self Test - Value Type vs Reference Type: http://blog.darkthread.net/post-2007-10-19-self-test-value-type-vs-reference-type.aspx http://msdn.microsoft.com/en-us/library/0f66670z(v=vs.71).aspx

# by 假面人生

謝謝你的網站,讓我學到好多知識

# by 偉恩嘎B

謝謝解惑!! 受益良多~~

Post a comment