2015/7/21

[VS] VS2015 新功能簡介

對 Windows 開發者而言, 今年 (2015) 七月真是熱鬧的月分。首先, Visual Studio 2015 在 7/20 正式發行, 而 Windows 10 也緊跟著在 7/29 正式發行。這次的 Visual Studio 有以下幾個相關的版本:

  1. Visual Studio Community
  2. Visual Studio Profession
  3. Visual Studio Enterprise
  4. Visual Studio Express 2015 editions
  5. Visual Studio Test Professional with MSDN
  6. Visual Studio Team Foundation Server 2015

關於這幾個版本的詳情和訂價, 可以參考 VisualStudio 網站。更詳細的功能比較, 則可以參考此頁

以上這幾個版本中有一個值得稍加留意的地方。Visual Studio 既提供了 Community 版本, 卻也同時提供了 Express 版本。VisualStudio 的介紹網頁上對 Express 版本加上了一個註解: "Non-enterprise customers are encouraged to check out Visual Studio Community 2015, which is also free and provides a more comprehensive solution."。看得出來, 顯然 Community 版本是提供給非企業使用者使用的, 功能也較完整。換句話說, Express 版本應該是比 Community 版本更陽春, 但是可以安心地在企業中使用的版本。相對而言, Community 版本則是專供「個人開發人員、 開放原始碼專案、學術研究單位、教育機構及小型專業團隊」使用 (請注意, 不包括「企業」在內)。但是至於「小型專業團隊」和「企業」要如何區分, 我也不太清楚。

在這個新版本中, 有許多新功能被加入。以下, 我將陸續介紹幾個我認為很棒或者有趣的功能或技巧。至於那些可能已為大家熟知的重點 (例如 .Net Framework 4.6 的支援、Roslyn、多種作業系統的支援等等), 我在這裡就略過不提了。

IDE 的改進

首先要介紹的是, 當我們在 ASP.NET 專案中 (包括 MVC) 編輯各種 HTML 網頁時, VS2015 能夠辨識來自 Bootstrap 所提供的樣式, 並且在 HTML 標籤的 class 列表中明白標示。如下圖, 隨便找個 HTML 標籤, 再加入 class 屬性, VS2015 會列出當前可用的所有樣式名稱。各位可以看到, 所有已經在 Bootstrap 中定義的樣式名稱, 都會在前面放上一個大大的 "B" 圖像, 代表它們是由 Bootstrap 所提供的。

CSS Class Source

當然, 這個功能只能提供一點工作上的便利性而已, 它可不保證這個樣式一定是由 Bootstrap 所定義的。如果你在該網頁上又自訂了一個同樣叫做 "bottom" 的樣式, 那麼在如上圖的樣式列表中, 你仍然會看到完全相同的列表內容 (意思是, 還是只有一個 bottom 項目, 而且同樣標上了 "B" 圖像), 但是依照 CSS 樣式套用的優先原則, 如果你選用了 "bottom" 樣式, 該 HTML 標籤實際上會套用定義在網頁裡的那個 "bottom" 樣式, 而不是 Bootstrap 裡定義的 "bottom" 樣式。像這種小陷阱, 讀者請自行留意。

其次, 自從 VS2015 開始, 它在 IDE 介面中提供了一個叫做 "Quick Action" (快速控制) 的新功能。這個功能事實上並不侷限於 HTML 類型的檔案, 在程式碼檔案中也有。如果你找不到它, 按下「Ctrl + 句號」就能夠把它叫出來。VS2015 的使用者一定要知道這個 "Quick Action" 新功能, 因為它已經把原來的「重構」(Refactor) 功能取代了。以前, 我們可以將一段程式碼標示起來, 再按滑鼠右鍵, 把「重構」功能叫出來, 然後就可以擷取方法; 現在, 我們必須改用 "Quick Action" (快速控制) 這個功能, 才能擷取方法。

這個新的 "Quick Action" 功能可以視情境而提供對應的功能。以下圖為例, 如果你把游標移到一個 img 標籤上, 你將看到最左邊的 "Quick Actions" 燈泡亮起來。在燈泡上按一下, 可以看到它在此處能夠提供的兩個功能:

Base64 Encoding

上面的這個「以 base64 為影像編碼」 功能雖然不是新加入的功能, 但仍然值得一提, 因為它可以幫你把這張圖片進行 base64 編碼, 變成如下的樣子:

Base64 Encoded

這種事情, 我們以前必須寫程式才能辦到, 沒想到現在變得輕而易舉。不過, 你應該看得出來, 這個功能只適用於靜態網頁, 如果網頁中的圖片是動態的, 那麼你還是必須寫程式才能辦到。

題外話: base64 圖形編碼程式

離題一下。如果你想知道如何寫程式去做 base64 圖形編碼的話, 你可以拿我寫好的程式去用:

public static string ImageEmbedded(string uri, string alt = null, string style = null)
{
    return string.Format("<img src='data:image/png;base64,{0}'{1}{2}>",
        ImageToBase64(uri),
        string.IsNullOrEmpty(alt) ? string.Empty : string.Format(" alt='{0}'", alt),
        string.IsNullOrEmpty(style) ? string.Empty : string.Format(" style='{0}'", style));
}

public static string ImageToBase64(string uri)
{
    Image image = Image.FromFile(uri); // 如果圖片來自 LAN, 使用這一行
    // Image image = LoadPicture(uri); // 如果圖片來自網路, 改用這一行

    if (null == image) // 防呆
        return string.Empty;

    byte[] imageBytes = ImageToBuffer(image);
    string base64String = Convert.ToBase64String(imageBytes);
    return base64String;
}

private static byte[] ImageToBuffer(Image image)
{
    byte[] buffer = null;
    using (Bitmap oBitmap = new Bitmap(image))
        using (MemoryStream MS = new MemoryStream())
        {
            oBitmap.Save(MS, ImageFormat.Jpeg);
            MS.Position = 0;
            buffer = new byte[MS.Length];
            MS.Read(buffer, 0, Convert.ToInt32(MS.Length));
            MS.Flush();
        }
    return buffer;
}

public static Bitmap LoadPicture(string url)
{
    FileWebRequest wreq;
    Stream mystream = null;
    Bitmap bmp = null;
    try
    {
        wreq = (System.Net.FileWebRequest)System.Net.FileWebRequest.Create(url);
        using (FileWebResponse wresp = (System.Net.FileWebResponse)wreq.GetResponse())
            if ((mystream = wresp.GetResponseStream()) != null)
                bmp = new Bitmap(mystream);
    }
    catch(Exception ex)
    {
        throw new Exception("Util.LoadPicture() error: " + ex.Message, ex);
    }
    finally
    {
        if (mystream != null)
            mystream.Close();
    }
    return bmp;
}

使用方式如下:

HtmlGenericControl span = new HtmlGenericControl("span");
span.InnerHtml = ImageEmbedded(imagePath, "圖片說明", "max-width: 100%;");

其中 imagePath 內容就是圖片的路徑。

對 AngularJS 的支援

VS2015 現在也開始支援 AngularJS 的 Intellisense, 如下圖所示。

這對前端開發者提供了小小的方便。其實除了 AngularJS, VS2015 提供了 JavaScript/TypeScript 和許多前端程式庫的其它支援。有興趣的朋友, 可以參考 Scottgu 的文章

強大的偵錯及診斷工具

如下圖所示, 當你在 VS2015 進行偵錯時, 一個整合的診斷工具 (Diagnostic Tools) 畫面就會出現在工作區的右邊。在這個畫面中, 使用者可以選擇顯示監看記憶體和 CPU 的負載情況, 作為參考。

此外, 請注意上圖左邊偏下方處。當我們設定中斷點並按下 F10 逐步執行時, 我們可以看到個別指令行的執行時間估算。這個功能叫做 PerfTips。

還有, 請注意在 VS2015 中, 效能精靈 (Performance Wizard) 的入口點改變了。這個功能以前是放在「分析」頁籤之下, 現在如果找不到的話, 必須從「偵錯」、「啟動診斷工具但不偵錯」畫面中進入。勾選「效能精靈」(如果它可供勾選的話), 然後就可以啟動它。結果如下圖所示:

Performance Wizard

對這個主題有興趣的朋友, 可以參考 MSDN 部落格中「Visual Studio 2015 中的效能及診斷工具」一文。

C# 新功能

在 C# 6.0 中, 雖然看不到什麼革命性的變化, 但除了我已另文介紹過的 String Interpolation (字串插補) 功能 (見「C# 6.0 中新字串格式化功能簡介」) 之外, 還是有幾個小小的新功能出現。

nameof() 方法

如果你不是程式老手的話, 你大概會以為這個功能是雞肋。但事實上不是。

先來說說這是什麼東西。

假設你的程式裡有個變數叫做 A, 有個方法叫做 B, 那麼 nameof(A) 會得到 "A", nameof(B) 會得到 "B"。就這麼簡單。你可能會覺得, A 的名字是 A, 而 B 的名字是 B, 這不是在撰寫程式時就知道了嗎? 為什麼還要特別設計一個方法來取得它的名字?

如果你真的這麼想的話, 那麼我可以反問你一個問題: 在你的程式裡, 任何變數的型別, 不是在撰寫時就知道了嗎? 為什麼 C# 還要提供一個 typeof() 方法來取得它的型別呢? 如果你對後面這個問題有答案的話, 那麼它大概也同樣適用於 nameof()。

我舉一個簡單的情境來說明 nameof() 可以使用在什麼情況之下。如果你和我一樣, 在應用程式裡加入了一個共用的 logger, 那麼你就可以在所有 catch 區段中把攔截到的 exception 給 log 起來, 而不是將它 fire 出去。在某些情況下, 我習慣在 log 時標注出問題的方法名稱, 例如:

log("Exception triggered in method A(): " + ex.Message);

然後, 我可以在事後檢查 log, 看看到底哪裡曾經出過什麼問題。

麻煩的是, 如果我曾經把方法 A() 重新命名 (Ctrl-R, R) 過, 可能有 70% 以上的機率, 我會忘記把 "Exception triggered in method A(): " 這串文字裡的 "A()" 一併改掉。但是如果我使用 nameof(A) 來取代這個寫死的文字, 就不用再擔心這種問題。

自動屬性的預設值

自動屬性是從 .Net 3.0 之後才出現的。從 C# 6.0 以後, 我們終於可以使用最簡單的方法為自動屬性賦予初始值:

public DateTime DateCreated { get; set; } = DateTime.Now;
public DateTime DateRunning { get; } = DateTime.Now;
public int BaseSalary { get; set; } = 32000;
public string DefaultName { get; set; } = "Unnamed";

Null 條件運算子

C# 從 2.0 之後, 加入了一個 ?? 運算子 (Null 聯合運算子), 從 6.0 之後, 則又加入了一個稱為 "Null-conditional operators" 的運算子, 都可以用來判斷一個物件的值是否為 null。如果不是, 則傳回它的值, 如果是, 則傳回 null 以避免觸發 exception。差異在於, 新的條件運算子會傳回物件本身, 以串接物件的屬性:

Company CustomerCompany = Customer?.Company;

我們也可以將它繼續串起來呼叫:

string CustomerCompany = Customer?.Company?.Name;

當然, 這個功能僅適用於參考型別。像 int 這種值型別的物件就不適用了 (唯獨 string 例外)。如果你真的有必要把這個功能套用在值型別物件上, 那麼你必須先將它改成 Nullable 型別 (例如 int?) 才行。

值得留意的是, ?? 運算子與 ?. 運算子也可以串在一起使用, 但是如下的這段程式碼可能會讓人百思不解:

//Customer cust = new Customer() { Company = new Company() { Name = "MVP" } };
Customer cust = null;
Company CustomerCompany = cust?.Company;
int CustomerCompanyNameLength = cust?.Company?.Name.Length ?? -1;

上述程式在執行之後, CustomerCompanyNameLength 的值是 -1。如果把第一行程式取消註解, 把第二行加上註解, 則會得到 3。但是都不會觸發 exception。看得出來, 最後一行裡的 ?? 是作用在 cust?.Company?.Name, 而不是 cust?.Company?.Name.Length。如果讀者未來有機會寫到這種程式, 請記得有這種情況存在。

靜態類別子句

可能有許多人跟我一樣, 習慣在專案裡撰寫一些內含多個靜態方法的程式庫, 當做專案裡的 Helpers。事實上, .Net Framework 裡也有很多類似的程式庫, 例如 System.Diagnostics.Debug 類別。如果你去看看 System.Diagnostics.Debug 這個 MSDN 網頁的話, 你會發現該類別所提供的所有方法都是靜態的。像這種類別, 在 C# 6.0 之前, 你對它任何方法的呼叫, 都需要加上一個 Debug 類別名, 例如 Debug.Write()、Debug.WriteLine()、Debug.WriteIf()、Debug.WriteLineIf()... 諸如此類。但是, 現在我們可以在加上 using Static 指示詞之後, 省掉所有 Debug 類別名。如下例所示:

using static System.Diagnostics.Debug;
...
WriteLine("This is a debug message!");

下圖為操作畫面:

一般的 using 所標注的是命名空間, 但是這裡的 using Static 所標注的則是類別名稱。讀者們請特別留意, 切勿混淆。

Expression Bodied Members

這個功能似乎尚無中文翻譯, 所以我暫且保留英文。基本上, 這個新增的功能也是一種簡化的語法, 其目的只是讓我們能夠少寫幾行程式而已。如果我們需要寫一個很簡單的方法的話, 我們現在可以使用 lambda 語法來再次予以簡化:

private static string name { get; set; } = "Johnny";
private static string gendre { get; set; } = "Male";

public static string Profile1 = $"Name: {name}, Gendre: {gendre}."; // This is a field
public static string Profile2 => $"Name: {name}, Gendre: {gendre}."; // This is a read-only property
public static string ShowProfile() => $"Name: {name}, Gendre: {gendre}.";

static void Main(string[] args)
{
    Console.WriteLine(Profile1);
    Console.WriteLine(Profile2);
    Console.WriteLine(ShowProfile());
    Console.ReadKey();
}

像使用於 ShowProfile() 方法的寫法。我們並沒有真的省下什麼, 充其量只是省掉兩行空白而已。至於上例中的 Profile2, 表面上看起來跟 Profile1 那種寫法沒什麼兩樣, 但事實上 Profile1 只是一個欄位 (field), 而 Profile2 卻是一個唯讀的屬性 (property), 所以還是略有不同。如果你試圖寫入 Profile2 的值, 那麼在編輯時就會出現錯誤, 如下圖:

索引初始設定式

索引初始設定式 (Index initializers) 主要是提供了 Collections.Generic 命名空間下的幾個字典類相關型別較簡便的初始值設定寫法, 如下例:

static void Main(string[] args)
{
    Dictionary<int, string> salesGroup1 = new Dictionary<int, string>() // 舊的寫法
    {
        {3, "Jon" },
        {4, "Maron" },
        {1, "Cutie" }
    };

    Dictionary<int, string> salesGroup2 = new Dictionary<int, string> // 新的寫法
    {
        [3] = "Jon",
        [4] = "Maron",
        [1] = "Cutie"
    };

    SortedList<int, string> salesGroup3 = new SortedList<int, string> // 新的寫法
    {
        [3] = "Jon",
        [4] = "Maron",
        [1] = "Cutie"
    };

    SortedDictionary<int, string> salesGroup4 = new SortedDictionary<int, string> // 新的寫法
    {
        [3] = "Jon",
        [4] = "Maron",
        [1] = "Cutie"
    };

    Dictionary<string, string> salesGroup5 = new Dictionary<string, string>() // 新的寫法
    {
        ["Third"] = "Jon",
        ["Forth"] = "Maron",
        ["First"] = "Cutie"
    };

    Console.WriteLine(salesGroup1[1]);
    Console.WriteLine(salesGroup2[1]);
    Console.WriteLine(salesGroup3[1]);
    Console.WriteLine(salesGroup4[1]);
    Console.WriteLine(salesGroup5["First"]);
    Console.ReadKey();
}

不過, 如你所見, 這個新功能真的只為我們省下一點點工夫而已。

在 catch 與 finally 區段中允許使用 await 指令

在很多情況下, 我們真的需要這麼這個功能。因此, 我個人認為這個新功能是所有新功能裡最實用的。

參考資料

  1. Released Today: Visual Studio 2015, ASP.NET 4.6, ASP.NET 5 & EF 7 Previews
  2. Visual Studio 2015 Final Release Event
  3. Visual Studio 2015 RTM
  4. Visual Studio 2015 中的效能及診斷工具

 

沒有留言:

張貼留言