1. 程式人生 > 其它 >dotnet 讀 WPF 原始碼筆記 XAML 建立物件的方法

dotnet 讀 WPF 原始碼筆記 XAML 建立物件的方法

技術標籤:WPF# WPF原始碼WPF原始碼WPFdotnetc#dotnet core

在 WPF 中,在 XAML 裡面定義的物件的建立,實際上不是完全通過反射來進行建立的,在WPF框架裡面,有進行了一系列的優化

在 WPF 中,將會通過 XamlTypeInvoker 的 CreateInstance 方法來進行物件的建立,而預設的 XamlTypeInvoker 的 CreateInstance 定義如下

        public virtual object CreateInstance(object[] arguments)
        {
            ThrowIfUnknown
(); if (!_xamlType.UnderlyingType.IsValueType && (arguments == null || arguments.Length == 0)) { object result = DefaultCtorXamlActivator.CreateInstance(this); if (result != null) { return result; }
} return CreateInstanceWithActivator(_xamlType.UnderlyingType, arguments); } private object CreateInstanceWithActivator(Type type, object[] arguments) { return SafeReflectionInvoker.CreateInstance(type, arguments); }

也就是說將呼叫 SafeReflectionInvoker.CreateInstance 進行物件的建立,這裡的建立方式就是通過反射,如下面程式碼

    static class SafeReflectionInvoker
    {
        internal static object CreateInstance(Type type, object[] arguments)
        {
            return Activator.CreateInstance(type, arguments);
        }
    }

.NET/C# 反射的的效能資料,以及高效能開發建議(反射獲取 Attribute 和反射呼叫方法) - walterlvC# 直接建立多個類和使用反射建立類的效能 可以瞭解,使用反射建立和物件建立效能相差大概有 30 倍

如果 WPF 真的全部使用反射進行建立,那麼整體效能將會很低

從 XamlTypeInvoker 的 CreateInstance 方法的定義可以看到,這是一個可以被重寫的方法,也就是說上面的程式碼只是預設的實現而已。在 WPF 中的一個重寫方法是 WpfKnownTypeInvoker 類,這裡面的定義如下

    class WpfKnownTypeInvoker : XamlTypeInvoker
    {
        WpfKnownType _type;

        public override object CreateInstance(object[] arguments)
        {
            if ((arguments == null || arguments.Length == 0) && _type.DefaultConstructor != null)
            {
                return _type.DefaultConstructor.Invoke();
            }
            else
            {
                return base.CreateInstance(arguments);
            }
        }
    }

也就是將會嘗試呼叫 WpfKnownType 的 DefaultConstructor 方法,這裡的定義如下

    class WpfKnownType : WpfXamlType, ICustomAttributeProvider
    {
        public Func<object> DefaultConstructor
        {
            get { return _defaultConstructor; }
            set
            {
                CheckFrozen();
                _defaultConstructor = value;
            }
        }
    }

也就是說其實這裡面是委託建立,效能將會比反射的執行效率大概高几十倍的速度

這裡的委託是在 WpfSharedBamlSchemaContext 類裡面定義的,這裡面的內容大概如下

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        private WpfKnownType Create_BamlType_TextBlock(bool isBamlType, bool useV3Rules)
        {
            var bamlType = new WpfKnownType(this, // SchemaContext
                                              638, "TextBlock",
                                              typeof(System.Windows.Controls.TextBlock),
                                              isBamlType, useV3Rules);
            bamlType.DefaultConstructor = delegate() { return new System.Windows.Controls.TextBlock(); };
            bamlType.ContentPropertyName = "Inlines";
            bamlType.RuntimeNamePropertyName = "Name";
            bamlType.XmlLangPropertyName = "Language";
            bamlType.UidPropertyName = "Uid";
            bamlType.IsUsableDuringInit = true;
            bamlType.Freeze();
            return bamlType;
        }

可以看到對於 WPF 框架裡面瞭解的物件,都將會建立委託的方式提升效能

這個類超過了一萬行,可以看到這裡用了很大的邏輯來提升 XAML 物件建立的效能

那如果是 WPF 不認識的類呢?如我自己定義的型別,那麼將會進入 XamlTypeInvoker 的 CreateInstance 方法的 DefaultCtorXamlActivator 類,在這個類裡面的邏輯如下

        private static class DefaultCtorXamlActivator
        {
            public static object CreateInstance(XamlTypeInvoker type)
            {
                if (!EnsureConstructorDelegate(type))
                {
                    return null;
                }
                object inst = CallCtorDelegate(type);
                return inst;
            }

            // 忽略程式碼
        }

在 EnsureConstructorDelegate 方法裡面將會判斷如果物件是公開的,那麼嘗試獲取預設建構函式,將預設建構函式做成委託。此時的效能將會是型別第一次進入的時候的速度比較慢,但是後續進入的時候就能使用委託建立,此時效能將會比較好。通過反射建立委託提升效能的方法,詳細請看 .NET Core/Framework 建立委託以大幅度提高反射呼叫的效能 - walterlv

這裡的 EnsureConstructorDelegate 方法相對複雜,我刪減了一些程式碼,讓邏輯相對清晰。詳細的程式碼還請到 WPF 官方倉庫獲取

            private static bool EnsureConstructorDelegate(XamlTypeInvoker type)
            {
            	// 如果型別初始化過建構函式建立,那麼返回,這是快取的方法
                if (type._constructorDelegate != null)
                {
                    return true;
                }

                // 如果不是公開的方法,那麼將無法使用反射建立委託的科技
                if (!type.IsPublic)
                {
                    return false;
                }

                // 反射獲取物件的建構函式
                Type underlyingType = type._xamlType.UnderlyingType.UnderlyingSystemType;
                // Look up public ctors only, for equivalence with Activator.CreateInstance
                ConstructorInfo tConstInfo = underlyingType.GetConstructor(Type.EmptyTypes);
                IntPtr constPtr = tConstInfo.MethodHandle.GetFunctionPointer();
               
                // 反射建立委託,這樣下次訪問就不需要使用反射,可以提升效能
                // This requires Reflection Permission
                Action<object> ctorDelegate = ctorDelegate =
                    (Action<object>)s_actionCtor.Invoke(new object[] { null, constPtr });
                type._constructorDelegate = ctorDelegate;
                return true;
            }

也就是說只有第一次的型別進入才會呼叫反射建立委託用來提升效能,之後的進入將會使用第一次創建出來的委託來建立物件,這樣能提升效能

從上面程式碼可以看到,如果物件不是公開的,那麼將因為 .NET 的限制,不能使用反射建立委託的方法來提升效能。因此一個性能提升的建議是在 XAML 裡面使用的類儘量都是公開的,這樣能提升一些效能

在獲取到了建構函式的對應的委託之後,就能呼叫 CallCtorDelegate 方法來建立物件,此時的建立物件速度會比反射快很多

但是如果物件的類不是公開的,那麼將需要用到 CreateInstanceWithActivator 使用反射建立物件,此時的效能相對來說比較差

因此在 WPF 的 XAML 建立物件,只有在嘗試了判斷這是 WPF 已知的物件失敗之後,同時物件對應的類不是公開的不能使用反射建立委託的科技,才會使用反射建立物件。大多數的時候,使用 XAML 都不會有很多效能損失

而對於自己定義的非公開的類,我給 WPF 官方提一個建議,就是提供讓開發端自己注入建立器的方式,用來提升效能,請看 API Request: Allow developers to inject a XAML factory for creating objects · Issue #4022 · dotnet/wpf

當前的 WPF 在 https://github.com/dotnet/wpf 完全開源,使用友好的 MIT 協議,意味著允許任何人任何組織和企業任意處置,包括使用,複製,修改,合併,發表,分發,再授權,或者銷售。在倉庫裡面包含了完全的構建邏輯,只需要本地的網路足夠好(因為需要下載一堆構建工具),即可進行本地構建

知識共享許可協議
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名林德熙(包含連結:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我聯絡。