Xamarin 的 XamlCompilation Attribute

  • 498
  • 0
  • 2016-12-19

這是 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 吧。