這是 2016-12-17 我在 Xamarin Day 議題內容中的最後一個段落,內容是關於 Xamarin 中的 XamlCompilationAttribute。
在研究 Xamarin Forms 的 XAML 設計過程,發現了一個很有趣的 Attribute -- XamlCompilation。
根據 Xamarin 文件 中 XAML Compilation 這一節的敘述,說明他有以下幾個好處。
- It performs compile-time checking of XAML, notifying the user of any errors.
- It removes some of the load and instantiation time for XAML elements.
- It helps to reduce the file size of the final assembly by no longer including .xaml files.
簡單用中文解釋就是:
- 在編譯時期會檢查 XAML 語法,並且在發生錯誤時會通知使用者。
- 會降低載入與實例化 XAML 元素的時間
- 因為不需要將原始的 XAML code 包進來,因此可以減少編譯後的檔案大小
對於編譯時期檢查語法這件事,我們可以很簡單地測試一下:
在預設沒有啟用 XamlCompilationAttribute 的狀態下,以下的問題 ( 就是把 Style 寫成 StyleXYZ 那一行 ) 在編譯時期是不會被發現的。原因是傳統的 Xamarin Forms 是在執行時期才載入 XAML ,因此在編譯時期是不會檢查語法正確性與否;於是這個問題會在執行時期才跳出 Exception,頗令人困擾。
<?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:local="clr-namespace:XApp8"
x:Class="XApp8.MainPage">
<ContentPage.Resources >
<ResourceDictionary >
<Style TargetType="Label" x:Key="MyStyle">
<Setter Property="FontSize" Value="24"/>
</Style>
<Style TargetType="Label" x:Key="OtherStyle">
<Setter Property="FontSize" Value="48"/>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout >
<Label Text="123" StyleXYZ="{StaticResource MyStyle}" x:Name="L01"/>
<Label Text="456" Style="{StaticResource MyStyle}" x:Name="L02"/>
<Label Text="123" Style="{DynamicResource MyStyle}" x:Name="L03"/>
<Label Text="123" Style="{DynamicResource MyStyle}" x:Name="L04"/>
<Button Text="Click" Clicked="Button_Clicked" />
</StackLayout>
</ContentPage>
那我們要如何加入 XamlCompilationAttribute 呢?這是一個應用在組件 (assembly) 的 Attribute,所以直接加在 App.cs 中就會讓整個專案的 XAML 都會經過預先編譯。如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
// 將 attribute 加在此處
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace XApp8
{
public partial class App : Application
接著,重新建置一次,此時就會發現編譯失敗,如下圖:
至於另外兩個特性,在此就不多做說明了。
但故事還是要繼續,因為我後來發現了一個原廠文件上並沒有說明的詭異功能,請聽我細細道來。話說有個朋友前兩天在寫 Xamarin 的時候,因為要用到 async ,同時就不小心把某個事件的委派函式回傳值定義成 Task;基本上,按照慣例,事件委派函式的簽章必須符合其事件宣告時的形式,以 Xamarin.Forms 中的 Button.Clicked Event 為例,他的事件宣告原型是以 EventHandler 委派宣告的,如下:
public event EventHandler Clicked;
而 EventHandler 委派的原型宣告如下:
public delegate void EventHandler(object sender, EventArgs e);
上面的宣告很明顯的可以看出如果要寫 Button.Clicked 事件的委派函式,回傳值必須是 void ,也就是沒有回傳值的意思。
所以當我們把要委派給 Button.Clicked 事件委派函式宣告成以下的方式就不符合其事件的宣告簽章:
async private Task Button_Clicked(object sender, EventArgs e)
{
var button = (Button)sender;
await button.RelRotateTo(45);
}
在預設沒有使用 XamlCompilationAttribute 的情形下,編譯這樣的程式碼並不會出錯,這是由於預設並不會編譯 XAML code,所以編譯器將這個方法當成一般的方式處理,不會考慮到符合 EventHandler 委派的簽章問題。所以只有在執行時期,這個 Exception 才會跑出來。
既然如此,那我們加入 XamlCompilationAttribute 不就可以在編譯時期檢查這個簽章不符的問題了吧?然後我就真的這麼做了,結果他居然編譯過了,而且還可以正確執行不會發生 Exception,這真是太神奇了,傑克!這個 Attribute 居然還偷改 code。
總之,如果沒有甚麼需要向下相容舊版本這種問題的話,就儘量加入這個 Atrribute 吧。