2017年10月15日 星期日

C# 非同步程式設計的 async void 與 async Task 的差異

若你正在觀看此篇文章,那麼你將會對於 為什麼需要使用非同步程式設計,真的可以提升整體應用程式的執行效能嗎? 問題更有興趣的。

今天,想來談談絕大部分的 C# 開發者都會產生這樣的不正確的 C# 程式碼寫法與用法,那就是當我們採用 工作式非同步模式 (TAP, Task-based Asynchronous Pattern) 進行非同步的程式開發的時候,並且想要寫個非同步運作的分法,在很多時候,您會想說,底下的方法,就是一個非同步運作的程式碼方法。

了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Thread Class] 的使用方式
了解更多關於 [Task Class] 的使用方式


在這裡,我們在方法 M1 前面使用了 async 修飾詞,不過,這個方法並沒有任何物件值需要回傳,因此,在底下的程式碼中,您看到了這個方法使用了 async void
在工作式非同步模式的方法中,回傳值的類型僅有 Task / Task<T>,前者代表沒有任何實際物件要回傳,而後者表示要回傳型別為 T 的物件;不過,那我們為什麼又可以使用 void 這個關鍵字呢?通常,我們會在綁定事件的方法上,需要使用 async void 這樣的標示,這是因為這些事件的函式簽章就是僅支援 void 這樣的回傳值,另外,我們在這個綁定的事件方法中,又需要撰寫工作式非同步模式的功能,因此,又需要在這個事件方法前面,加入 async 這個修飾詞。
所以,除了是在綁定事件的方法內,其他任何情況下,對於您開發的工作式非同步模式方法,就不需要使用async void這樣的回傳值標示。
上面的說明,很重要,很重要,很重要
public async void M1()
{
    var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
    Console.WriteLine($"M1 開始時間 : {foo}");
    await Task.Delay(3000);
    foo = DateTime.UtcNow.ToString("mm:ss.ffff");
    Console.WriteLine($"M1 結束時間 : {foo}");
}
現在,讓我們來看看這個方法實際使用的情況,首先,我們定義一個類別 A,它的定義如下程式碼,其中, M1 是我們使用 async void 的方式建立的方法,而 M2,則是正常的工作式非同步模式方法的程式碼寫法。
class A
{
    public async void M1()
    {
        var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M1 開始時間 : {foo}");
        await Task.Delay(3000);
        foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M1 結束時間 : {foo}");
    }

    public async Task M2()
    {
        var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M2 開始時間 : {foo}");
        await Task.Delay(3000);
        foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M2 結束時間 : {foo}");
    }
}
M1這個方法,會先顯示這個方法的開始執行時間,接著,便會暫停3秒鐘,接著便會顯示結束執行這個方法的時間。
在測試程式碼中,我們先建立一個類別A的物件 objA,接著呼叫 objA.M1() 方法,最後,等候使用按下任一按鍵。
class Program
{
    static async Task Main(string[] args)
    {
        A objA = new A();

        objA.M1();
        Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
        Console.ReadKey();
    }
}
上面的程式碼執行結果如底下所示,你可以看到,當呼叫 M1 方法之後,接著, M1 方法似乎就以另外一個執行緒的方式進行執行,所以, Main 這個方法內的程式碼,沒有等到 M1 執行完畢,就直接執行 objA.M1() 之後的所有敘述,而當三秒鐘之後,就會顯示 M1 方法已經結束的時間。
這是因為我們使用了 async void 的方式來宣告這個方法,因此,我們沒有任何方法,可以等候 M1 方法執行完成之後,才要繼續執行 objA.M1() 之後的所有敘述。
M1 開始時間 : 09:23.1819
Press any key to Exist...

M1 結束時間 : 09:26.2171
現在,讓我們修改測試程式碼,我們在呼叫 M1 方法前後,與在 M1 方法內,當要呼叫 await Task.Delay(3000); 的前後,都顯示出當時執行緒的 ID (這裡顯示的是受管理的執行緒ID) Thread.CurrentThread.ManagedThreadId
class A
{
    public async void M1()
    {
        var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M1 開始時間 : {foo}");
        Console.WriteLine($"M1 {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(3000);
        Console.WriteLine($"M1 {Thread.CurrentThread.ManagedThreadId}");
        foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M1 結束時間 : {foo}");
    }

    public async Task M2()
    {
        var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M2 開始時間 : {foo}");
        await Task.Delay(3000);
        foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M2 結束時間 : {foo}");
    }
}
class Program
{
    static async Task Main(string[] args)
    {
        A objA = new A();

        Console.WriteLine($"Main {Thread.CurrentThread.ManagedThreadId}");
        objA.M1();
        Console.WriteLine($"Main {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
        Console.ReadKey();
    }
}
此時,執行結果將會如下所示,您會看到,當 M1 的方法內,其 await Task.Delay(3000); 敘述執行完畢之後,此時的執行緒 ID 變成了 4。
Main 1
M1 開始時間 : 20:55.2604
M1 1
Main 1
Press any key to Exist...

M1 4
M1 結束時間 : 20:58.2897
最後,讓我們加入 M2 的方法呼叫,對於要呼叫 M2 的非同步方法,我們使用了 await 關鍵字 await objA.M2();
class A
{
    public async void M1()
    {
        var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M1 開始時間 : {foo}");
        Console.WriteLine($"M1 {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(3000);
        Console.WriteLine($"M1 {Thread.CurrentThread.ManagedThreadId}");
        foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M1 結束時間 : {foo}");
    }

    public async Task M2()
    {
        var foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M2 開始時間 : {foo}");
        await Task.Delay(1000);
        foo = DateTime.UtcNow.ToString("mm:ss.ffff");
        Console.WriteLine($"M2 結束時間 : {foo}");
    }
}
class Program
{
    static async Task Main(string[] args)
    {
        A objA = new A();

        Console.WriteLine($"Main {Thread.CurrentThread.ManagedThreadId}");
        objA.M1();
        await objA.M2();
        Console.WriteLine($"Main {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
        Console.ReadKey();
    }
}
底下將為這個測試程式碼的執行輸出結果,我們可以看到, objA.M1() 方法一執行到 await Task.Delay(3000);敘述之後,就立即返回到 Main 方法內,接著執行 await objA.M2();,不過,此時,整個程式將會等候到 objA.M2() 方法執行完後,才會繼續進行下去,而在這個時候, M1 的方法,也持續在等候 Task.Delay 的甦醒時間
ain 1
M1 開始時間 : 24:53.7485
M1 1
M2 開始時間 : 24:53.7645
M2 結束時間 : 24:54.7670
Main 4
Press any key to Exist...

M1 5
M1 結束時間 : 24:56.7678



對於已經具備擁有 .NET / C# 開發技能的開發者,可以使用 Xamarin.Forms Toolkit 開發工具,便可以立即開發出可以在 Android / iOS 平台上執行的 App;對於要學習如何使用 Xamarin.Forms & XAML 技能,現在已經推出兩本電子書來幫助大家學這這個開發技術。
這兩本電子書內包含了豐富的逐步開發教學內容與相關觀念、各種練習範例,歡迎各位購買。
Xamarin.Forms 電子書
想要購買 Xamarin.Forms 快速上手 電子書,請點選 這裡

想要購買 XAML in Xamarin.Forms 基礎篇 電子書,請點選 這裡




2017年10月2日 星期一

如何在 Xamarin.Forms 中執行結束應用程式的功能

大家經常都在詢問這樣的問題:如何在 Xamarin.Forms 中執行結束應用程式的功能
我個人認為,這個問題,不是技術上的問題,而是問問題的人,需要對於您開發的應用程式所在的平台上,了解這個平台提供的應用程式生命週期;一旦你對於 iOS & Android 平台的應用程式生命週期有了解之後,這個問題也就解決了;喔,你們沒有聽錯,可是,我們有看到任何的技術方法呀,可否告訴我究竟要呼叫甚麼 API 才能夠結束應用程式的執行嗎?
這個答案是很明確的,在行動裝置作業系統中,iOS / Android ,想要結束您開發的應用程式執行,在各自的應用程式生命週期中,只有兩個方法,一個就是當作業系統資源不足夠的時候,作業系統有權將任何應用程式立即結束執行;另外一個就是使用者可以透過手勢或者作業系統提供的操作介面,手動的結束特定應用程式的執行。
從應用程式的生命週期,每個應用程式是沒有透過 App 結束執行這件事情,只有在前景執行或者移到背景去等候再度回到前景來執行。所以,請不要逢人就詢問 如何在 Xamarin.Forms 中執行結束應用程式的功能,這不是 Xamarin.Forms 也不是 Xamarin 可以做到的事情,原則上,只要作業系統有提供這樣的 API,你就可以在 Xamarin.Forms 呼叫他來使用。
在這篇文章中 How do I programmatically quit my iOS application?,你會看到蘋果官方的回覆:There is no API provided for gracefully terminating an iOS application.。對於不死心的人,還是想要這麼做,那麼,請自行觀看蘋果相關的上架說明文件(不要沒看文件就到處用您的智慧來想像該怎麼做),強制這麼做的話,是無法上架App到公開的 App Store 市集上。
好的,若您還是堅持要這麼做,我們來看看在 Xamarin.Form 的開發環境中,該如何做到這樣的需求,在這裡,我們設計一個按鈕,當使用者點選這個按鈕之後,就會 ViewModel 內,執行一個注入原生平台的物件,執行結束應用程式的功能。

建立一個結束應用程式的介面

首先,我們在 Xamarin.Forms 專案內,建立一個介面,這個介面裡面只有定義一個方法 Exit(),當使用者呼叫這個方法之後,就會結束這個應用程式的執行。
    public interface IAppExit
    {
        void Exit();
    }

測試頁面 View / ViewModel

我們的測試 App 很簡單,只有一個按鈕
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFExit.Views.MainPage"
             Title="MainPage">
    <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
        <Label Text="{Binding Title}" />
        <Button
            Text="結束應用程式"
            Command="{Binding CloseAppCommand}"/>
    </StackLayout>
</ContentPage>
當按下了 結束應用程式 按鈕之後,就會執行 ViewModel 內的 CloseAppCommand DelegateCommand 物件方法。
在底下的測試範例 ViewModel 中,我們透過相依性服務注入技術,將 IAppExit 實作物件注入到 ViewModel 內,並且執行 _AppExit.Exit(); 方法之後,就會結束應用程式的執行。
    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private readonly INavigationService _navigationService;

        public DelegateCommand CloseAppCommand { get; set; }
        IAppExit _AppExit;
        public MainPageViewModel(INavigationService navigationService, IAppExit appExit)
        {
            _navigationService = navigationService;
            _AppExit = appExit;
            CloseAppCommand = new DelegateCommand(() =>
            {
                _AppExit.Exit();
            });
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }

Android 平台實作 IAppExit

現在,我們需要在 Android 專案內,建立一個類別 AppExit,這個類別需要實作 IAppExit 介面的方法,我們在 Exit() 這個方法中,呼叫 Android.OS.Process.KillProcess(Android.OS.Process.MyPid());,這樣,當這個 Android 平台的 Xamarin.Forms 專案執行的時候,就會結束這個應用程式的執行。
[assembly: Xamarin.Forms.Dependency(typeof(AppExit))]
namespace XFExit.Droid.Servoces
{
    class AppExit : IAppExit
    {
        public void Exit()
        {
            Android.OS.Process.KillProcess(Android.OS.Process.MyPid());
        }
    }
}

iOS 平台實作 IAppExit

現在,我們需要在 iOS 專案內,建立一個類別 AppExit,這個類別需要實作 IAppExit 介面的方法,我們在 Exit() 這個方法中,呼叫 System.Diagnostics.Process.GetCurrentProcess().CloseMainWindow();,這樣,當這個 iOS 平台的 Xamarin.Forms 專案執行的時候,就會結束這個應用程式的執行。
當然,你也可以執行 .NET 平台的中止執行續的方法:Thread.CurrentThread.Abort(); 這樣,也會結束這個應用程式的執行。
[assembly: Xamarin.Forms.Dependency(typeof(AppExit))]
namespace XFExit.iOS.Services
{
    public class AppExit : IAppExit
    {
        public void Exit()
        {
            System.Diagnostics.Process.GetCurrentProcess().CloseMainWindow();
            //Thread.CurrentThread.Abort();
        }
    }
}

C# : 泛型 Generic 類別內動態產生指定泛型物件

我們分別宣告簡單的類別與結構型別,接著,我們宣告了一個客製化的泛型類別 MyGenericClass<T>,這個泛型類別只有一個方法 GetInstance,他會根據我們建立這個泛型類別所傳送過去的型別,幫我們傳回這個型別的預設物件。

了解更多關於 [泛型的使用方式
了解更多關於 [C# 程式設計手冊 



我們要建立一個預設的物件,可以使用 System.Activator.CreateInstance 方法,不過,當泛型參數型別為字串 string 的時候,就會有問題了,因為,這個字串型別,他沒有無參數的預設建構函式,因此,我們需要呼叫其他的建構函式,這樣才能夠幫我們產生一個字串物件出來;因此,對於字串型別,我們使用了 Activator.CreateInstance(type, "".ToCharArray() 表示式來建立字串型別的物件。
由於呼叫System.Activator.CreateInstance所得到的物件,我們還需要將其轉型成為我們指定的泛型參數型別,在這裡,我們可以使用 (T) 就可輕鬆達到轉型的需求。
public class MyClass
{
    public int ID { get; set; }
    public string Name { get; set; }
}

public struct MyStruct
{
    public int ID { get; set; }
    public string Name { get; set; }
}


public class MyGenericClass<T> 
{
    public T MyObject { get; set; }
    public T GetInstance()
    {
        T myObject;
        var type = typeof(T);
        // 類別型別,使用 Activator.CreateInstance 動態來產生物件
        if(type.Name != "String")
        {
            myObject = (T)Activator.CreateInstance(type);
        }
        else
        {
            myObject = (T)Activator.CreateInstance(type, "".ToCharArray());
        }

        return myObject;
    }
}
測試用的程式碼
在這裡,我們使用泛型型別 MyGenericClass,請這個型別的 GetInstance 方法,幫助我們產生我們所指定的型別物件。
//var tmp = new string();
var tmp = new int();

MyGenericClass<int> myGenericObject1 = new MyGenericClass<int>();
MyGenericClass<MyClass> myGenericObject2 = new MyGenericClass<MyClass>();
MyGenericClass<MyStruct> myGenericObject3 = new MyGenericClass<MyStruct>();
MyGenericClass<string> myGenericObject4 = new MyGenericClass<string>();
Console.WriteLine($"MyGenericClass<int>() Object is : {myGenericObject1.GetInstance()}");
Console.WriteLine($"MyGenericClass<MyClass>() Object is : {myGenericObject2.GetInstance()}");
Console.WriteLine($"MyGenericClass<MyStruct>() Object is : {myGenericObject3.GetInstance()}");
Console.WriteLine($"MyGenericClass<string>() Object is : {myGenericObject4.GetInstance()}");

Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();

執行結果
MyGenericClass<int>() Object is : 0
MyGenericClass<MyClass>() Object is : GenericDynObject.MyClass
MyGenericClass<MyStruct>() Object is : GenericDynObject.MyStruct
MyGenericClass<string>() Object is :
Press any key to Exist...



對於已經具備擁有 .NET / C# 開發技能的開發者,可以使用 Xamarin.Forms Toolkit 開發工具,便可以立即開發出可以在 Android / iOS 平台上執行的 App;對於要學習如何使用 Xamarin.Forms & XAML 技能,現在已經推出兩本電子書來幫助大家學這這個開發技術。
這兩本電子書內包含了豐富的逐步開發教學內容與相關觀念、各種練習範例,歡迎各位購買。
Xamarin.Forms 電子書
想要購買 Xamarin.Forms 快速上手 電子書,請點選 這裡

想要購買 XAML in Xamarin.Forms 基礎篇 電子書,請點選 這裡




C# : 使用迭代器 Iterators 產生自己的集合 Collection 清單

在這個練習中,我們來開發出一個類別,可以用於 foreach 陳述式中,也就是迭代器的能力,根據微軟的定義:迭代器 可用來逐步執行集合,例如清單和陣列。迭代器方法或 get 存取子會對集合執行自訂反覆運算
要客製化類別,使其具有迭代器能力,在 C# 中,我們僅需要實作 IEnumerable<T> 這個介面,在您的類別中,實作出 GetEnumerator 這個方法即可。
這裡,我們模擬一個情境,您需要透過網路抓取四個網頁內容,不過,你無法控制哪個網頁會先讀取完畢,因此,您將需要設您的類別,只要網頁內容讀取完成之後,就會回傳到用戶端;而在用戶端中,可以使用 foreach 陳述式來逐一讀取這些網頁內容。為了達到這樣的需求,我們是用 C# 工作的靜態方法 Task.WhenAny 來協助我們處理這樣需求。
public class DownloadWeb : IEnumerable<DownloadWeb.GetWebResult>
{
    public class GetWebResult
    {
        public int Length { get; set; }
        public string URL { get; set; }
        public int ms { get; set; }
    }

    public string[] Urls = new string[]
    {
        "https://tw.yahoo.com/",
        "http://www.msn.com/zh-tw/",
        "https://world.taobao.com/",
        "https://www.microsoft.com",
    }; 

    public IEnumerator<GetWebResult> GetEnumerator()
    {
        List<Task<GetWebResult>> tasks = new List<Task<GetWebResult>>();
        tasks.Add(GetWebContent(Urls[0]));
        tasks.Add(GetWebContent(Urls[1]));
        tasks.Add(GetWebContent(Urls[2]));
        tasks.Add(GetWebContent(Urls[3]));

        while (tasks.Count > 0)
        {
            var aTask = Task.WhenAny(tasks);
            tasks.Remove(aTask.Result);
            var result = aTask.Result;
            yield return result.Result;
        }
    }

    public async Task<GetWebResult> GetWebContent(string url)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        using (var client = new HttpClient())
        {
            var result = await client.GetStringAsync(url);
            sw.Stop();
            GetWebResult getWebResult = new GetWebResult()
            {
                Length = result.Length,
                URL = url,
                ms = sw.Elapsed.Milliseconds
            };
            return getWebResult;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}
在我們要進行測試的時候,首先,我們產生出一個 DownloadWeb 物件,使用 foreach 陳述式,逐一取得讀取到的網頁內容。你可以嘗試重複執行這個練習程式,將會發現到,每次進行 foreach 陳述式的時候,回傳結果順序都不進相同。
DownloadWeb downloadWeb = new DownloadWeb();

foreach (var item in downloadWeb)
{
    Console.WriteLine($"{item.URL} ({item.ms}) : {item.Length}");
}

Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
執行結果
http://www.msn.com/zh-tw/ (241) : 46965
https://tw.yahoo.com/ (327) : 201659
https://world.taobao.com/ (349) : 220947
https://www.microsoft.com (4) : 1020
Press any key to Exist...

2017年10月1日 星期日

C# : 傳遞參數 - ref / out 方法參數修飾詞 練習

這個練習相當的簡單,是要讓你可以孰悉 ref / out 這兩個方法參數修飾詞的用法與差異。
底下是微軟官方對於這兩個修飾詞的定義。
ref 關鍵字會導致引數由參考加以傳遞,而非透過值
out 關鍵字會導致引數由參考傳遞。 它類似於 ref 關鍵字,只是 ref 需要在傳遞之前,先初始化變數。
static void Main(string[] args)
{
    int normal = 20;
    int ref_int = 10;
    int out_int;
    Method_normal(normal);
    Method_ref(ref ref_int);
    Method_out(out out_int);

    Console.WriteLine($"normal的值為 {normal}");
    Console.WriteLine($"ref_int的值為 {ref_int}");
    Console.WriteLine($"out_int的值為 {out_int}");
    Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
    Console.ReadKey();
}

private static void Method_normal(int normal)
{
    normal = 999;
}

private static void Method_out(out int out_int)
{
    out_int = 999;
}

private static void Method_ref(ref int ref_int)
{
    ref_int = 999;
}

C# : 使用擴充方法 Extension Method,增加 DateTime 功能

在這個練習中,我們將會要來練習擴充方法 Extension Method 的使用方式,根據微軟官方的說明:擴充方法可讓您在現有類型中「加入」方法,而不需要建立新的衍生類型、重新編譯,或是修改原始類型

了解更多關於 [擴充方法
了解更多關於 [C# 程式設計手冊 


在這裡,我們將會要使用結構 DateTime 型別,進行擴充它原有的功能;我們在實際開發專案的時候,往往需要使用到很多的日期相關的方法,取得需要的特定日期,例如,想要取得下個星期日的日期是哪一天,可是,在原有的 DateTime 結構中,卻沒有提供這樣的功能,因此,很多時候,我們需要寫出一些支援方法,幫助我們進行這樣的日期計算;最後,透過呼叫這些支援方法,得到所需要的日期。
不過,透過擴充方法的使用,讓我們可以更容易地在 DateTime 物件上,直接使用這些擴充方法,得到我們的日期。
首先,我們需要宣告一個靜態類別,在這個靜態類別內,我們再進行更多的靜態方法的定義;在這些靜態方法中,他的第一個參數將會是需要加入 this 關鍵字與要套用的型別,例如:public static DateTime Next(this DateTime from, DayOfWeek dayOfWeek);若要使用這個擴充方法,我們僅需要 DateTime 物件之後,輸入 .,Visual Studio 的 Intellience 就會顯示出我們剛剛定義的擴充方法(此時,需要加入適當的命名空間宣告),例如: Now.Next(DayOfWeek.Sunday)
    public static class MyDateTimeExtension
    {
        public static DateTime Next(this DateTime from, DayOfWeek dayOfWeek)
        {
            int start = (int)from.DayOfWeek;
            int target = (int)dayOfWeek;
            if (target <= start)
                target += 7;
            return from.AddDays(target - start);
        }
    }
這是我們測試的程式碼
            var Now = DateTime.Now;
            Console.WriteLine($"下個星期日為 {Now.Next(DayOfWeek.Sunday)}");
            Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
            Console.ReadKey();

執行結果為
下個星期日為 2017/10/1 下午 06:05:22
Press any key for continuing...

了解更多關於 [擴充方法
了解更多關於 [C# 程式設計手冊