2019年2月20日 星期三

.NET Framework 提供的非同步方法使用綜觀 : APM, EAP, Task, async / awa


.NET Framework 提供的非同步方法使用綜觀 : APM, EAP, Task, async / await

在這篇文章中,將會來快速瀏覽 Microsoft .NET Framework 開發框架內提供的各種不同非同步開發方法的使用方式 (例如:APM 非同步程式設計模型、EAP 事件架構非同步模式、TAP 以工作為基礎的非同步模式、async / await 的使用方式、把 AMP 程式轉換成為工作、把 EAP 程式轉換成為工作),在這裡將會要透過網路來讀取這個 URL https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample 內的結果。

APM 非同步程式設計模型 Asynchronous Programming Model Pattern

當 .NET Framework 1.0 推出的時候,是沒有提供任何具體的非同步設計模式方法,而在 .NET Framework 1.1 的時候,推出了 APM 非同步程式設計模型 Asynchronous Programming Model Pattern。
在 APM 下所設計的非同步應用方法中,都會有 Beginxxx / Endxxx 這樣一對的方法,其中前面 Begin 開頭的方法,表示要啟動這個非同步呼叫,而後面的 End 開頭的方法,則表示要取得非同步方法的執行結果;不過,若當呼叫 End 開頭方法的時候,此時,非同步方法尚未執行完成,這個時候,呼叫 End 開頭方法的執行緒,將會進入到封鎖 Block 階段,不再繼續執行任何程式碼,一直等到非同步作業完成之後,才會繼續往下執行其他程式碼。
在這個例子中,使用 WebRequest.Create 來建立一個 WebRequest 物件,這個 WebRequest 類別有支援 APM & 存取一個 URL 資源的功能;因此,要啟動這個讀取遠端 Web 資源的時候,將會呼叫 request.BeginGetResponse 方法 (這裡的兩個參數,都傳進 null 空值,表示不需要使用 回呼 callback 機制來取得非同步最後執行結果),而當要取得 URL 資源內容的時候,就可以呼叫 EndGetResponse 方法。
這個範例執行結果如下所示:
Console
封鎖現在執行緒,取得結果
來自遠端 ASP.NET Core Web API 服務的資料
Press any key for continuing...
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        APM();
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static void APM()
    {
        WebRequest request = WebRequest.Create("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample");
        // 第一個參數也可以使用 null ,表示不使用 callback
        IAsyncResult ar = request.BeginGetResponse(null, null);
        Console.WriteLine($"封鎖現在執行緒,取得結果");
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar);
        Encoding enc = System.Text.Encoding.UTF8;
        StreamReader loResponseStream = new
          StreamReader(response.GetResponseStream(), enc);
        string Response = loResponseStream.ReadToEnd();
        Console.WriteLine($"{Response}");
    }
}
若在 APM 非同步設計模式下,想要透過 回呼 callback 來取得非同步方法執行結果,可以參考底下的範例程式碼,當要啟動非同步方法的時候,可以呼叫 BeginGetResponse 這個方法,不過,要傳入一個 callback 委派方法物件,這個委派方法將會當這個非同步方法執行完成之後,就會執行這個委派方法;而在 BeginGetResponse 方法內的第二個引數,將是要呼叫這個 回呼 callback 方法時候,要傳入進去的參數物件值。
這個範例執行結果如下所示:
Console
Press any key for continuing...
使用 回呼 callback 取得結果
來自遠端 ASP.NET Core Web API 服務的資料
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        APMCallback();
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static void APMCallback()
    {
        WebRequest request = WebRequest.Create("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample");
        // 第一個參數也可以使用 null ,表示不使用 callback
        IAsyncResult ar = request.BeginGetResponse(WebRequestCallback, (HttpWebRequest)request);
    }
    private static void WebRequestCallback(IAsyncResult ar)
    {
        Console.WriteLine($"使用 回呼 callback 取得結果");
        HttpWebRequest myHttpWebRequest = (HttpWebRequest)ar.AsyncState;
        HttpWebResponse response = (HttpWebResponse)myHttpWebRequest.EndGetResponse(ar);
        Encoding enc = System.Text.Encoding.UTF8;
        StreamReader loResponseStream = new
          StreamReader(response.GetResponseStream(), enc);
        string Response = loResponseStream.ReadToEnd();
        Console.WriteLine($"{Response}");
    }
}

EAP 事件架構非同步模式 Event-based Asynchronous Pattern

在 .NET Framework 2.0 的時候,改良了非同步程式設計模式,推出了 EAP 事件架構非同步模式 Event-based Asynchronous Pattern
在前面 APM 非同步設計模式中,有看到可以在 APM 中使用 回呼 callback 方法來取得非同步方法的執行結果;而在 EAP 非同步設計模式之中,將其簡化了 APM 的使用方式,不再需要使用 Beginxxx & Endxxx 這樣的方法,而是直接使用 callback 方法來取得非同步結果。
同樣的,當要取得網路上 URL 資源的時候,就可以使用 EAP 非同步設計模式來做到,這裡使用的是有支援 EAP 的 WebClient 類別。首先建立一個 WebClient 物件,接者綁定當非同步呼叫完成之後,要執行的 callback 委派方法,這裡使用的是: client.DownloadStringCompleted += Client_DownloadStringCompleted;,最後,就是使用 client.DownloadStringAsync(new Uri("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample")); 來啟動這個非同步方法。
當已經從遠端網站取得結果之後,Client_DownloadStringCompleted 委派方法就會被執行,而這個方法會有兩個參數,其參數用法與格式就如同 .NET Framework 框架中事件使用的方法相同,透過 DownloadStringCompletedEventArgs 參數,便可以取得這次非同步呼叫的處理結果。在這裡特別將呼叫 EAP 非同步方法前的執行緒 ID 與在 callback 方法內的執行緒 ID 顯示出來,可以看到,當執行 callback 委派方法的時候,使用的另外一個執行緒來執行,因此,取得非同步執行結果的時候,是不會造成呼叫非同步方法的執行緒處於被封鎖的狀態。
這個範例執行結果如下所示:
Console
EAP 執行前的的執行緒為 1
Press any key for continuing...
EAP 執行完成後的的 Callback 執行緒為 6
來自遠端 ASP.NET Core Web API 服務的資料
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        EAP();
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static void EAP()
    {
        WebClient client = new WebClient();
        Console.WriteLine($"EAP 執行前的的執行緒為 {Thread.CurrentThread.ManagedThreadId}");
        client.Encoding = Encoding.UTF8;
        client.DownloadStringCompleted += Client_DownloadStringCompleted;
        client.DownloadStringAsync(new Uri("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample"));
    }
    private static void Client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        Console.WriteLine($"EAP 執行完成後的的 Callback 執行緒為 {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine($"{e.Result}");
    }
}

TAP 以工作為基礎的非同步模式 Task-based Asynchronous Pattern

在 .NET Framework 4.0 的時候,設計出比起 APM / EAP 更加好用的 TAP 以工作為基礎的非同步模式 Task-based Asynchronous Pattern
TAP 是一個使用工作 Task 物件來設計非同步應用的設計模式,這是透過 TPL 工作平行程式庫 Task Parallel Library 所提供各項服務來實作出來的。同樣的,在這裡也需要設計出可以讀取相同 URL 的回傳結果,不過,在這裡使用的是工作來做到非同步執行結果。
在 .NET Framework 4.0 之後,建議要存取 Http 服務的時候,可以使用 HttpClient 這個類別物件,這是用來取代 .NET Framework 1.1 所推出的 WebClient 類別服務,當要呼叫遠端 Web API 服務的時候,可以使用 client.GetStringAsync("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample?a=123"); 敘述,啟動一個非同步呼叫,這個方法將會回傳一個 工作 Task 物件,這裡將會回傳一個泛型工作型別物件 Task<string>
當要取得結果的時候,可以透過 task.Result 屬性來取得這個非同步方法的執行結果,同樣的,當非同步方法尚未執行結束的時候,若要透過 task.Result 取得非同步執行結果的時候,將會造成呼叫非同步方法端的執行緒進入封鎖 Block 狀態,只要非同步方法執行完畢之後,該執行緒就會繼續執行下去;對於沒有回傳結果的 Task 物件,可以使用 task.Wait() 來等候這個非同步工作結束。另外,也可以透過 Task.ContinueWit 方法,建立一個 callback 方法,來取得非同步工作執行結果。
這個範例執行結果如下所示:
Console
封鎖現在執行緒,等候非同步工作結果...
來自遠端 ASP.NET Core Web API 服務的資料
Press any key for continuing...
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        TaskObject();
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static void TaskObject()
    {
        using (HttpClient client = new HttpClient())
        {
            Task<string> task = client.
                GetStringAsync("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample?a=123");
            Console.WriteLine("封鎖現在執行緒,等候非同步工作結果...");
            task.Wait();
            string result = task.Result;
            Console.WriteLine($"{result}");
        }
    }
}

async / await 關鍵字

在 .NET Framework 4.5 / C# 5.0,加入了兩個關鍵字, async / await ,讓程式設計師可以使用同步程式碼設計邏輯,設計出非同步的應用程式碼
TPL 工作平行程式庫 Task Parallel Library 已經提供了相當方便與好用的非同步開發環境,為了要能夠讓 .NET C# 開發者可以使用同步程式設計邏輯,設計出具有非同步應用的程式碼,因此,在 C# 5.0 的時候,推出了 修飾詞 async 與 關鍵字 await,透過 async 修飾詞設計出非同步的方法,與使用 await 關鍵字來等候非同步工作的完成,請注意,這裡是使用 等候 (await),而不是使用 等待 (wait),前者並不會造成呼叫非同步方法的時候,造成當前執行緒進入封鎖 Block 狀態,而後者將會造成當前執行緒進入封鎖狀態,一直到非同步工作完成為止。
這個範例同樣使用了 HttpClient 這個類別,不過,在呼叫非同步方法的時候,會使用 client.GetStringAsync("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample"); 來等候遠端 Web API 回傳最後結果內容,而因為在這個方法內有使用了 await 關鍵字,所以,這個方法 private static async Task AwaitTask() 需要加入 async 這個修飾詞,這樣,編譯器就會幫忙產生非同步方法會用到的相關程式碼,如此,讓我們設計的非同步應用程式碼,可以使用循序的同步方式來設計程式碼,並且不會造成執行緒封鎖,也節省了許多程式碼的撰寫。
這個範例執行結果如下所示:
Console
等候工作完成...
等候工作完成前的,現在執行緒為 1
等候工作完成後的,現在執行緒為 7
來自遠端 ASP.NET Core Web API 服務的資料
Press any key for continuing...
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        await AwaitTask();
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static async Task AwaitTask()
    {
        HttpClient client = new HttpClient();
        Task<string> task = client.GetStringAsync("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample");
        Console.WriteLine("等候工作完成...");
        Console.WriteLine($"等候工作完成前的,現在執行緒為 {Thread.CurrentThread.ManagedThreadId}");
        string result = await task;
        Console.WriteLine($"等候工作完成後的,現在執行緒為 {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine($"{result}");
    }
}

將 APM 設計成為 Task

由於使用 Task 工作物件來開發非同步應用程式碼相當的好用與方便,然而,對於之前使用 APM 寫的類別庫或者程式碼,也想要使用 工作 方式來設計與使用非同步應用,此時,可以使用 Task.Factory.FromAsync 這個工廠方法提供的功能,把 APM 類別,輕輕鬆鬆地打包成為 工作 應用的非同步程式碼。在這裡需要傳入 Beginxxx 委派方法、Endxxx 委派方法與要傳送過去的參數,在這個例子中,並沒有需要用到第三個參數,因此,可以傳送 null 過去即可。
Task.Factory.FromAsync 會回傳一個 Task<WebResponse> 物件,有了 Task 物件,便可以選擇使用 Task.Result 取得非同步運算的結果,或者使用 await 關鍵字來等候非同步運算的結果;在這個範例中,將是使用 await 關鍵字來等候 Task<WebResponse> 的結果。
這個範例執行結果如下所示:
Console
Press any key for continuing...
來自遠端 ASP.NET Core Web API 服務的資料
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        APMtoTask();
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static async Task APMtoTask()
    {
        WebRequest request = WebRequest.Create("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample");
        Task<WebResponse> task =
            Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse, request.EndGetResponse, null);
        var response = await task;
        Encoding enc = System.Text.Encoding.UTF8;
        StreamReader loResponseStream = new
          StreamReader(response.GetResponseStream(), enc);
        string Response = await loResponseStream.ReadToEndAsync();
        Console.WriteLine($"{Response}");
    }
}

將 EAP 設計成為 Task

對於 EAP 相關的程式碼,也是可以重新包裝成為 Task 物件,在這裡,可以透過 TaskCompletionSource 類別來完成
這個範例執行結果如下所示:
Console
來自遠端 ASP.NET Core Web API 服務的資料
Press any key for continuing...
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        await EAPtoTask();
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static Task EAPtoTask()
    {
        TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
        WebClient client = new WebClient();
        client.Encoding = Encoding.UTF8;
        client.DownloadStringCompleted += (s, e) =>
        {
            Console.WriteLine($"{e.Result}");
            tcs.SetResult(null);
        };
        client.DownloadStringAsync(new Uri("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample"));
        return tcs.Task;
    }
}


2019年2月4日 星期一

C# async 非同步方法需要額外花費多少時間成本之評估


C# async 非同步方法需要額外花費多少時間成本之評估

我們知道,當我們撰寫一個 async 方法的時候,就算我們只有撰寫一行 Console.Write 方法,編譯器也會幫我們產生出許多的 IL ( intermediate language ) 程式碼,也就是說,雖然我們僅有撰寫一行程式碼,面對一個非同步的方法,編譯器幫助我們產生了當要設計出一個非同步方法所需要用到的相關程式碼,這些程式碼主要是由一個狀態機 state machine 所組成,而且,編譯器會依據您正在設計的 async 方法,自動建立出其他的類別代碼;也就是說,當我們呼叫這個非同步方法的時候,將會透過編譯器所產生的程式碼來運作。

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

其中第一個就是要進行狀態機的初始化設定工作,在狀態機內將會開始呼叫 MoveNext 方法,開始執行這個非同步方法;在這個狀態機內將會知道當呼叫 await 關鍵字的時候,並且當所呼叫的非同步方法執行完成之後,該繼續從哪裡來執行點,而狀態機內也會有我們所設計的非同步方法的敘述,不過,會依據您所設計的程式碼,拆解成為不同的區塊,但是,您不用擔心,狀態機將會把這些程式碼串接起來,並且也會處理當有例外異常發生的時候,把這些例外異常捕捉起來,記錄在工作物件內。
現在,我們要來看看當我們使用了 async 修飾詞之後,會有甚麼影響?這包含了在 async 方法內沒有使用 await 關鍵字與有使用 await 關鍵字的執行效能。
首先,我們來撰寫個同步方法的呼叫花費時間測試,測試程式碼如下:
在我的電腦上,螢幕輸出結果為 呼叫 1000 次 同步方法 的平均花費時間 : 0.0001087 ms ,好的,我們接下來繼續做下一個測試。
C Sharp / C#
class Program
{
    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 1000; i++)
        {
            string foo = 同步方法();
        }
        sw.Stop();
        double cost = sw.Elapsed.TotalMilliseconds / 1000.0;
        Console.WriteLine($"呼叫 1000 次 同步方法 的平均花費時間 : {cost} ms");
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    static string 同步方法()
    {
        return "同步方法";
    }
}
我們來撰寫個空的非同步方法的呼叫花費時間測試,測試程式碼如下,在這裡的 空的非同步方法() 並沒有做任何事情,如同前面的同步方法,唯一的差異就是我們使用了 async 修飾詞在這個方法上,並也在這個 空的非同步方法() 內,也沒有使用任何的 await 關鍵字。
在我的電腦上,螢幕輸出結果為 呼叫 1000 次 同步方法 的平均花費時間 : 0.0014271 ms ,經過與前面的同步方法來比較,在同步方法前面加上 async 關鍵字之後,每次使用 await 還呼叫這個空的非同步方法,將會多花費 0.0013184ms 時間,也就是成長的 13.13倍,好的,我們接下來繼續做下一個測試。
C Sharp / C#
static async Task Main(string[] args)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 1000; i++)
    {
        string foo = await 空的非同步方法();
    }
    sw.Stop();
    double cost = sw.Elapsed.TotalMilliseconds / 1000.0;
    Console.WriteLine($"呼叫 1000 次 同步方法 的平均花費時間 : {cost} ms");
    Console.WriteLine("Press any key for continuing...");
    Console.ReadKey();
}
static async Task<string> 空的非同步方法()
{
    return "同步方法";
}
現在,在空的非同步方法內,使用 await Task.Yield() 進行非同步方法的呼叫,讓我們來看看這樣的做法究竟會花費多少時間,測試程式碼如下:
其中,我們使用 await 等候的 Task.Yield 方法,其功能為: 您可以使用await Task.Yield();中非同步的方法,以強制以非同步方式完成的方法,我們是用來模擬一個等候非同步工作,但是又沒有要做甚麼事情的情境。
在我的電腦上,螢幕輸出結果為 呼叫 1000 次 同步方法 的平均花費時間 : 0.361438 ms ,很明顯的,這樣的做法比起空的非同步方法來說,多花費了 0.3600109 ms 時間,也就是成長了 253.27 倍之多。
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 1000; i++)
        {
            string foo = await 立即返回非同步方法();
        }
        sw.Stop();
        double cost = sw.Elapsed.TotalMilliseconds / 1000.0;
        Console.WriteLine($"呼叫 1000 次 同步方法 的平均花費時間 : {cost} ms");
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    static async Task<string> 立即返回非同步方法()
    {
        await Task.Yield();
        return "同步方法";
    }
}
透過這樣的測試過程,同步方法的執行效能,比起非同步方法的執行效能,可以說來的快得多,可是,像要設計一個非同步方法,會相當的麻煩與不好維護程式碼,但是透過了 TPL & async & await 的幫助,可以讓我們使用同步程式碼設計邏輯,設計出具有非同步方法應用的成果,不過,我們所要付出的將會是一點點執行效能的犧牲。


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







2019年2月3日 星期日

打包 Xamarin.Forms 使用的 C# 程式碼片段成為 Visual Studio 2017 的擴充功能

打包 Xamarin.Forms 使用的 C# 程式碼片段成為 Visual Studio 2017 的擴充功能

在過去幾年中,由於從事於 Xamarin.Forms 專案開發與教學的工作,因此,累積了許多開發上使用的程式碼片段,這些程式碼片段大約有27個;而每次進行教學課程的時候,都會要求 Xamarin.Forms 上課學員可以把這些程式碼片段安裝起來,以便可以加快練習專案的開發。然而,幾乎都經常會有許多學員對於使用 Visual Studio 2017 程式碼片段管理員,把這些程式碼片段檔案安裝到 Visual Studio 開發環境上遇到問題,雖然,我都有提供相關作法文件與操作較學影片,不過,還是會有學員設定與使用上發生了問題。
有鑑於此,想說何不自己開發出一個 Visual Studio 擴充功能,如此,可以讓上課學員可以安裝這個擴充功能之後,便可以把這些好用的程式碼開發片段安裝到他們的 Visual Studio 2017 開發環境上;在這裡,我已經做好這個擴充功能,並且上架到 Visual Studio Marketplace 上,有興趣的人可以自行安裝 Xamarin.Forms Snippet for Prism

建立擴充功能專案

那麼,要如何把自己開發的程式碼片段打包成為 Visual Studio 程式碼片段呢?首先,請打開 Visual Studio 2017 ,從功能表中點選 [檔案] > [新增] > [專案],當 [新增專案] 對話窗顯示出來之後,請選擇 [已安裝] > [Visual C#] > [Extensibility] > [VSIX Project] 這個項目,最後,請在對話窗的下方,輸入這個專案的名稱,並且點選 [確定] 按鈕,以便完成建立這個專案。

建立程式碼片段資料夾與設定檔案屬性

請使用滑鼠右擊這個專案,建立底下階層的資料夾 [Snippets] > [CSharp] > [XamarinPrism] ,最後一個資料夾 XamarinPrism 的名稱,您可以依據您的喜好,選擇一個適合的名稱。
此時,您可以把這些程式碼片段,加入倒 [XamarinPrism] 資料夾內,不過,此時,請特別注意要進行底下的設定動作,否則,會有問題產生。
請將這個 Visual Studio 專案內的所有程式碼片段節點全選起來,並且在屬性視窗內,請先選擇這些程式碼片段檔案的 [建置動作] 屬性為 [內容],接著,請設定 [Include in VSIX] 這個屬性為 [True]。

建立與設定機碼內容

請在這個專案內,加入一個 XamarinPrismSnippet.pkgdef 文字檔案,這個檔案的名稱您可以自行設定,並不一定要叫做 XamarinPrismSnippet,接著,請在這個文字檔案內,填入底下內容。
在這裡,我們要設定當這個 Visual Studio 擴充功能在使用者電腦上安裝成功之後,需要在他的電腦上新增底下描述的機碼,在第二行的等號左邊的名稱,將會出現在程式碼片段管理員中的分類名稱,因此,您可以依據您的需要,自行調整與變更成為您需要的名稱。
[$RootKey$\Languages\CodeExpansions\CSharp\Paths]
"XamarinPrism"="$PackageFolder$\Snippets\CSharp\XamarinPrism"

修正 source.extension.vsixmanifest

現在,請在專案中,找到 source.extension.vsixmanifest 節點,使用滑鼠雙擊這個節點,便會看到一個視窗內容,請點選 [Assets] 標籤頁次;接著,請點選右方的 [New] 按鈕。
現在會看到一個 [Add New Asset] 對話窗,請在 [Type] 欄位中,選擇 [Microsoft.VisualStudio.VsPackage],接著在 [Source] 欄位中,選擇 [File on filesystem] 這個選項。
此時,會出現一個新的 [Path] 欄位,我們可以將剛剛建立好的 XamarinPrismSnippet.pkgdef 這個文字檔案名稱,輸入到 [Path] 欄位中 [XamarinPrismSnippet.pkgdef] ,完成後,請點選 [OK] 按鈕。
請在這個視窗的最右上方,看到有兩個欄位,分別是 [Author] 與 [Version] ,請適當修正這兩個欄位值。

進行測試

好的,現在我們可以開始進行這個擴充功能的除錯執行與測試了,現在,請點選工具列上的 Current Instance [Visual Studio Enterprise 2017] 這個項目 (我的電腦上安裝的 Visual Studio 2017 是屬於 Enterprise 版本),現在,將會出現一個獨立環境的 Visual Studio。
請在這個獨立測試環境的 Visual Studio 中,打開程式碼片段管理員對話窗,此時,您應該可以從 CSharp 程式碼片段群組內看到 XamarinPrism 這個選項,那就表示這個擴充功能專案已經正常設計好了。