2013年8月1日 星期四

轉型方法的選擇

先前在撰寫某個 Windows RT 的專案時,因為要用到 GridView + VariableSizedWrapGrid 這種元件,寫了一篇 http://ascii-iicsa.blogspot.tw/2012/07/gridview-variablesizedwrapgrid.html

當時並沒有想太多,因為該專案不算複雜,我只定義了一種叫 ItemBase 的類別來讓專案中所有的 GridView 做 Binding,直到後來我碰到一個狀況。

在最近我發現之前的寫法有個很大的問題,我每定義一種結構,就要為了那種結構生一個專門處理它的衍生自 GridView 的類別,原因在於 PrepareContainerForItemOverride 這個 Method 裡面我必須把 Item Object 做轉型才能拿到 Span 資訊,所以後來我定義了一個介面名為 ISpanItem 長下面這個樣子

public interface ISpanItem
{
    int ItemRowSpan {  get; }
    int ItemColumnSpan {  get; }
}

接著我只要讓所有的結構實作 ISpanItem 就可以利用同一個 GridView 做出 VariableSizedWrapGrid 效果,一開始我是這麼寫的

class NoSpanItemSizeException : Exception
{
    public NoSpanItemSizeException() : base("must implement ISpanItem this interface") { }
}
 
public interface ISpanItem
{
    int ItemRowSpan {  get; }
    int ItemColumnSpan {  get; }
}
 
public class VSWGridView : GridView
{
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
 
        ISpanItem _spanItem = null;
        try
        {
            _spanItem = (ISpanItem)item;
        }
        catch (Exception e) // 這個地方若只接 InvalidCastException 會更好
        {
            throw new NoSpanItemSizeException();
        }
 
        VariableSizedWrapGrid.SetRowSpan(element as UIElement, _spanItem.ItemRowSpan);
        VariableSizedWrapGrid.SetColumnSpan(element as UIElement, _spanItem.ItemColumnSpan);
    }
}

其實這樣的寫法沒什麼問題,只是我後來在想,真的有必要用一個 try catch 來避免非 ISpanItem 的介面嗎?

使用 (型別)物件 這種強制轉型的語法也一直是我的習慣,它的確只能 catch Exception 這種方式來知道轉型失敗,後來我查了一下,看來還有更好的寫法,所以我把這段程式碼改成這個樣子

public class VSWGridView : GridView
{
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
 
        int rowSpan = 1;
        int colSpan = 1;
 
        if (item is ISpanItem)
        {
            ISpanItem si = item as ISpanItem;
            rowSpan = si.ItemRowSpan;
            colSpan = si.ItemColumnSpan;
        }
 
        VariableSizedWrapGrid.SetRowSpan(element as UIElement, rowSpan);
        VariableSizedWrapGrid.SetColumnSpan(element as UIElement, colSpan);
    }
}

利用 is 來判斷該物件是否屬於該介面或該類型,並用 as 來做轉型,利用 as 來做轉型有個好處,它不會發生 Exception,所以對效能是有幫助的,若無法轉型的情況 si 就會是 null,所以我們可以判斷 si 在轉型後是否為 null 或像上面的範例一樣一開始就用 is 來判斷即可,此處我也把 throw Exception 遺棄了,讓 VSWGridView 可以通吃所有的結構,只是在沒有實作 ISpanItem 介面的狀況下就讓 VariableSize 失效如此而已。

沒有留言:

張貼留言