讀 MAUI 源代碼 理解可綁定對象和可綁定屬性的存儲機制

来源:https://www.cnblogs.com/lindexi/archive/2022/09/13/16687997.html
-Advertisement-
Play Games

閱讀須知:本文為入門介紹、指引文章,所示代碼皆為最簡易(或僅為實現功能)的演示示例版本,不一定切實符合個人(企業)實際開發需求。 一、DbContext生存期 DbContext 的生存期從創建實例時開始,併在釋放實例時結束。 DbContext 實例旨在用於單個工作單元。這意味著 DbContex ...


和 UWP 與 WPF 不同的是在 MAUI 裡面,使用可綁定對象 BindableObject 替換了依賴對象的概念,我閱讀了 MAUI 的源代碼發現其實只是命名變更了,裡面的機制和設計思想都是差不多的。在 MAUI 裡面提供 BindableObject 用來支持可綁定屬性機制和附加屬性機制,本文將告訴大家在 MAUI 裡面是如何在可綁定對象裡面提供可綁定屬性和附加屬性的存儲的機制

在 WPF 裡面,依賴屬性的提出的一部分原因是為了省記憶體。在 MAUI 裡面,我猜測省記憶體是可綁定對象提出的一個原因。由於一個界面控制項,例如按鈕等,有著非常龐大數量的屬性,假設每個控制項裡面的所有屬性都是需要獨立的對象不能共用,那麼在複雜界面上,將會因為大量的控制項的大量屬性占用大量的記憶體。可綁定對象裡面可以實現在屬性沒有被賦值時,將可以使用預設值,而對於大部分控制項來說,很多不常用的屬性都是使用預設值即可。可綁定對象需要解決的是讓可綁定屬性可以代替普通的 CLR 屬性,對可綁定屬性進行賦值時,可以值和可綁定對象關聯,從而可以讀取出來。既然名字叫可綁定對象,那自然也要實現綁定的支持,綁定的支持的核心就是通知,需要支持在屬性值變更的時候進行通知。接下來將通過閱讀源代碼瞭解在 MAUI 里是如何實現

打開 MAUI 的 BindableObject 的源代碼,可以看到在 BindableObject 里有 _properties 欄位,定義如下

    public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
    {
        readonly Dictionary<BindableProperty, BindablePropertyContext> _properties = new Dictionary<BindableProperty, BindablePropertyContext>(4);
    }

沒錯,這就是在 MAUI 裡面的可綁定對象的存儲核心實現。在 MAUI 的可綁定對象裡面通過 _properties 字典存放可綁定屬性的值內容,字典的 Key 是 BindableProperty 可綁定屬性,字典的 Value 是 BindablePropertyContext 可綁定屬性上下文,初始化字典預設占用 4 個空間,預設初始化空間是為了優化而已,沒有什麼特別用途。通過此字典定義可以瞭解到存儲的核心實現就是將可綁定屬性和對應的值存入到對象的字典里,例如給某個可綁定對象的某個叫 Xxx 的可綁定屬性進行賦值,那將會對 _properties 字典更新 Xxx 屬性的值內容

在 MAUI 的實現是,在可綁定對象裡面,使用 SetValueCore 方法進行屬性更新賦值,我刪掉了不關鍵的邏輯的代碼如下

        internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
        {
            // 獲取或創建可綁定屬性上下文信息
            BindablePropertyContext context = GetOrCreateContext(property);

            SetValueActual(property, context, value, currentlyApplying, attributes, silent);
        }

        BindablePropertyContext GetOrCreateContext(BindableProperty property) => GetContext(property) ?? CreateAndAddContext(property);

        internal BindablePropertyContext GetContext(BindableProperty property) => _properties.TryGetValue(property, out var result) ? result : null;

        BindablePropertyContext CreateAndAddContext(BindableProperty property)
        {
            var context = new BindablePropertyContext { ... };
            _properties.Add(property, context);
            return context;
        }


        void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false)
        {
            // 觸發對象變更前事件

            context.Value = value;

            // 觸發對象已變更事件
        }

可以看到賦值的第一步就是調用 GetOrCreateContext 方法,嘗試去拿到上下文信息,如果拿不到就創建。這裡的用到的 BindablePropertyContext 上下文信息是存儲可綁定屬性的關鍵,在 BindablePropertyContext 裡面存放了很多欄位,定義如下

    public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
    {
        internal class BindablePropertyContext
        {
            public BindableContextAttributes Attributes;
            public BindingBase Binding;
            public Queue<SetValueArgs> DelayedSetters;
            public BindableProperty Property;
            public object Value;

            public bool StyleValueSet;
            public object StyleValue;
        }
    }

可以看到 BindablePropertyContext 是一個內部類型,也不對外開放。在 BindablePropertyContext 裡面重要的就是 Value 欄位,表示存儲的實際值內容。其次為了更好的支持綁定,也添加了 Binding 欄位

在獲取到 BindablePropertyContext 上下文之後,即可進行賦值,賦值是調用 SetValueActual 方法進行賦值,賦值前後分別觸發事件用來通知。觸發通知事件最重要的功能是讓綁定可以有刷新的時機。如此即可完成賦值過程

通知事件是分別觸發可綁定的對象的通知事件和對應的可綁定屬性的通知事件,如下麵代碼

        void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false)
        {
            // 觸發對象變更前事件
            property.PropertyChanging?.Invoke(this, original, value);
            OnPropertyChanging(property.PropertyName);

            context.Value = value;

            // 觸發對象已變更事件
            OnPropertyChanged(property.PropertyName);
            property.PropertyChanged?.Invoke(this, original, value);
        }

通過以上代碼可以看到,可綁定對象給可綁定屬性賦值的時候,就是先獲取或創建可綁定屬性上下文,將賦值的參數值給到 可綁定屬性上下文 的 Value 欄位。如此完成賦值過程

由於賦值的參數值被放入到 可綁定屬性上下文 的 Value 欄位,而 可綁定屬性上下文 又放入到 _properties 字典里,相當於間接將 賦值的參數值 放入到 _properties 字典里。自然在獲取值過程里,也需要從字典裡面讀取。在 MAUI 裡面讀取可綁定屬性是通過 GetValue 方法實現,代碼如下

        public object GetValue(BindableProperty property)
        {
            if (property == null)
                throw new ArgumentNullException(nameof(property));

            var context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property);

            return context == null ? property.DefaultValue : context.Value;
        }

以上代碼的判斷 BindableProperty 的 DefaultValueCreator 屬性邏輯是 MAUI 特有的邏輯,和 WPF 與 UWP 不相同,咱下文再聊。回到獲取屬性的方法上,是通過先獲取對象的可綁定上下文信息,如果能獲取到可綁定上下文,證明此可綁定對象的這個可綁定屬性曾經被賦值過,需要用賦值更新的內容。如果拿到的可綁定屬性上下文是空,那就使用可綁定屬性定義的預設值即可

在 MAUI 裡面,通過 BindableProperty 的 DefaultValueCreator 屬性簡化了可綁定屬性的定義,和讓可綁定屬性更加強大。使用 MAUI 的可綁定屬性和可綁定對象對比 WPF 的依賴屬性和依賴對象的實現,可以看到 MAUI 的實現實在簡潔很多。在 MAUI 里的 BindableProperty 的 DefaultValueCreator 屬性是一個委托,定義如下

    public sealed class BindableProperty
    {
        public delegate object CreateDefaultValueDelegate(BindableObject bindable);

        internal CreateDefaultValueDelegate DefaultValueCreator { get; }
    }

可以看到 BindableProperty 的 DefaultValueCreator 屬性的委托是支持給傳入的可綁定對象進行處理,對可綁定對象返回特定的預設值。這裡值得說明的是,通過委托是可以特例給可綁定對象不同的預設值的,但不代表著一定是不同的可綁定對象都一定需要不同的預設值對象。這裡只是一個委托,讓委托返回相同的對象是完全可以的。這個委托更多的是使用在判斷可綁定對象類型,根據可綁定類型對象或者狀態,返回不同的預設值。或者是返回一個需要運行時動態計算值,而不是一個可以寫固定在代碼裡面的參數

例如對於 FontSize 的可綁定屬性的定義里,就採用讓不同的控制項返回不同的預設字體大小,定義如下

        public static readonly BindableProperty FontSizeProperty =
            BindableProperty.Create("FontSize", typeof(double), typeof(IFontElement), 0d,
                                    propertyChanged: OnFontSizeChanged,
                                    defaultValueCreator: FontSizeDefaultValueCreator);

        static object FontSizeDefaultValueCreator(BindableObject bindable)
            => ((IFontElement)bindable).FontSizeDefaultValueCreator();

也就是說對於不同的可綁定對象,獲取到的預設的字體大小是根據對應的可綁定對象的 FontSizeDefaultValueCreator 方法實現決定,不同的可綁定對象可以有不同的實現,從而實現了讓預設值關聯上具體的可綁定對象類型。這個創新的設計,可以省掉在 WPF 裡面的大量預設依賴屬性值重寫的邏輯代碼,省掉了這部分代碼,也可以大量減少的機制,從而減少更多的代碼

例如 Span 和 Editor 控制項對字體大小預設值有不同的實現

    public class Span : GestureElement, IFontElement
    {
        double IFontElement.FontSizeDefaultValueCreator() =>
            double.NaN;
    }

    public partial class Button : View, IFontElement
    {
        double IFontElement.FontSizeDefaultValueCreator() =>
            this.GetDefaultFontSize();
    }

同樣,對於某些可綁定屬性來說,需要給每個可綁定對象的對象不同的預設值對象,例如 Grid 裡面的 RowDefinitions 屬性。大家都知道,在 Grid 裡面的 RowDefinitions 是一個集合,如果集合也是一個共用的預設值,那自然會存在預設值污染。如果預設值是一個空值,那麼將會讓 Grid 邏輯裡面存在大量的判斷空邏輯,或者需要其他額外的初始化邏輯。在 MAUI 裡面,通過 DefaultValueCreator 委托,實現了每個 Grid 對象使用獨立的預設值對象,代碼如下

    public class Grid : Layout, IGridLayout
    {
        public static readonly BindableProperty RowDefinitionsProperty = BindableProperty.Create("RowDefinitions",
            typeof(RowDefinitionCollection), typeof(Grid), null, validateValue: (bindable, value) => value != null,
            propertyChanged: UpdateSizeChangedHandlers, defaultValueCreator: bindable =>
            {
                // 每個 Grid 對象使用獨立的,新創建的預設值對象
                var rowDef = new RowDefinitionCollection();
                rowDef.ItemSizeChanged += ((Grid)bindable).DefinitionsChanged;
                return rowDef;
            });
    }

在 MAUI 裡面除了可綁定屬性之外,還有一個特殊的屬性類型,附加屬性。附加屬性可以定義在任意的類型裡面,通過附加屬性,給某個現有的類型附加上屬性。功能上和 WPF 或 UWP 的附加屬性功能是相同的。可綁定屬性和附加屬性都是相同的 BindableProperty 類型,只是在創建的時候,調用的靜態創建方法不同而已。對於可綁定屬性來說,調用的是 BindableProperty.Create 方法創建。對於附加屬性來說,調用 BindableProperty.CreateAttached 創建。在 MAUI 裡面,通過閱讀代碼,我認為分開兩個方法更多的是為了相容 WPF 或 UWP 的寫法,沒有非常本質的差別,參數也差不多,如下麵代碼

        internal static BindableProperty Create(string propertyName, Type returnType, [DynamicallyAccessedMembers(DeclaringTypeMembers)] Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue,
                                                BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging,
                                                CreateDefaultValueDelegate defaultValueCreator = null)
        {
            return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging,
                defaultValueCreator: defaultValueCreator);
        }

        internal static BindableProperty CreateAttached(string propertyName, Type returnType, [DynamicallyAccessedMembers(DeclaringTypeMembers)] Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue,
                                                        BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging,
                                                        bool isReadOnly, CreateDefaultValueDelegate defaultValueCreator = null)
        {
            return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging, isReadOnly,
                defaultValueCreator);
        }

如此可以看到可綁定屬性和附加屬性從參數上是似乎相同的。由於附加屬性也是一個可綁定屬性類型,同理可以瞭解到附加屬性的存儲也和可綁定對象的可綁定屬性的存儲是相同的。如此也能解答一個問題,在 MAUI 的附加屬性,附加到對象上,附加屬性的參數值是如何跟隨對象的生命周期的問題。由於附加屬性也是一個可綁定屬性,同樣將參數值存在可綁定對象的 _properties 字典裡面,在對象會 GC 回收時,自然 _properties 欄位也被回收,那放在字典裡面的參數值也自然被減去引用,當參數值的沒有被引用時,也就自然被回收

在 MAUI 裡面,可綁定對象基類型的意義就是提供了可綁定屬性的機制,存儲可綁定屬性的方式就是通過 _properties 字典存放。通過字典存放的內容是被賦值更改的屬性,沒有賦值更改的屬性是沒有被放入到字典裡面,獲取在字典裡面沒有存放的屬性時,將會通過對應的可綁定屬性獲取到預設值。預設值的獲取有兩個方式,一個是可綁定屬性的固定的預設值屬性,另一個是通過可綁定屬性的預設值創建委托創建預設值。在 MAUI 里的可綁定屬性的預設值創建委托是一個創新,可以寫出讓不同的可綁定對象使用不同的預設值的功能,也可以寫出根據不同的可綁定對象類型返回不同的預設值,通過委托的方式靈活實現複雜的功能

更多的 MAUI 相關博客,還請參閱我的 博客導航

博客園博客只做備份,博客發佈就不再更新,如果想看最新博客,請到 https://blog.lindexi.com/

知識共用許可協議
本作品採用知識共用署名-非商業性使用-相同方式共用 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發佈,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我[聯繫](mailto:[email protected])。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文詳細闡述了knn演算法,從開始介紹什麼事knn,到講解knn演算法的原理再到最後以實際例子來運用knn演算法的步驟,實際例子的代碼講解也十分詳細 ...
  • 為了在jupyter中使用pyTorch的虛擬環境,來記錄一下怎麼操作一、conda命令的使用因為使用的是jupyter,所有就使用Anaconda Prompt來創建虛擬環境(也可使用virtualenv,不過沒試過) conda create -n 環境名 # 創建的環境在預設路徑下,C盤位置不 ...
  • 摘要:本文主要講述如何進行圖像量化處理和採樣處理及局部馬賽克特效。 本文分享自華為雲社區《[Python圖像處理] 二十.圖像量化處理和採樣處理及局部馬賽克特效》,作者: eastmount。 本文主要講述如何進行圖像量化處理和採樣處理及局部馬賽克特效。 一.圖像量化處理 圖像通常是自然界景物的客觀 ...
  • 1.推導式套路 除了最簡單的列表推導式和生成器表達式,其實還有字典推導式、集合推導式等等。 下麵是一個以列表推導式為例的推導式詳細格式,同樣適用於其他推導式。 variable = [out_exp_res for out_exp in input_list if out_exp == 2] out ...
  • 當面試官問你,“什麼是令牌桶限流演算法”! 你知道要怎麼回答,才能獲得面試官的青睞嗎? 大家好,我是Mic,一個工作了14年的Java程式員。 關於這個問題,面試官想考察哪些緯度?我們又該怎麼回答呢? 問題解析 限流策略,是在高併發流量下保護系統穩定性的一種策略。 所以這個問題,主要是互聯網公司會去考 ...
  • 一、argparse簡介 argparse 是 python 自帶的命令行參數解析包,可以用來方便的服務命令行參數,使用之前需要先導入包 import argparse 二、簡單案例 簡單使用,創建一個名為test.py的文件 # 導入 argparse 模塊 import argparse # 創 ...
  • 在實際業務中,當後臺數據發生變化,客戶端能夠實時的收到通知,而不是由用戶主動的進行頁面刷新才能查看,這將是一個非常人性化的設計 ...
  • 【Shashlik.EventBus】.NET 事件匯流排,分散式事務最終一致性 簡介 github https://github.com/dotnet-shashlik/shashlik.eventbus 各位爺高興了給個star唄。 分散式事務、CAP定理、事件匯流排,在當前微服務、分散式、集群大行 ...
一周排行
    -Advertisement-
    Play Games
  • JWT(JSON Web Token)是一種用於在網路應用之間傳遞信息的開放標準(RFC 7519)。它使用 JSON 對象在安全可靠的方式下傳遞信息,通常用於身份驗證和信息交換。 在Web API中,JWT通常用於對用戶進行身份驗證和授權。當用戶登錄成功後,伺服器會生成一個Token並返回給客戶端 ...
  • 老周在幾個世紀前曾寫過樹莓派相關的 iOT 水文,之所以沒寫 Nano Framework 相關的內容,是因為那時候這貨還不成熟,可玩性不高。不過,這貨現在已經相對完善,老周都把它用在項目上了——第一個是自製的智能插座,這個某寶上50多塊可以買到,搜“esp32 插座”就能找到。一種是 86 型盒子 ...
  • 引言 上一篇我們創建了一個Sample.Api項目和Sample.Repository,並且帶大家熟悉了一下Moq的概念,這一章我們來實戰一下在xUnit項目使用依賴註入。 Xunit.DependencyInjection Xunit.DependencyInjection 是一個用於 xUnit ...
  • 在 Avalonia 中,樣式是定義控制項外觀的一種方式,而控制項主題則是一組樣式和資源,用於定義應用程式的整體外觀和感覺。本文將深入探討這些概念,並提供示例代碼以幫助您更好地理解它們。 樣式是什麼? 樣式是一組屬性,用於定義控制項的外觀。它們可以包括背景色、邊框、字體樣式等。在 Avalonia 中,樣 ...
  • 在處理大型Excel工作簿時,有時候我們需要在工作表中凍結窗格,這樣可以在滾動查看數據的同時保持某些行或列固定不動。凍結窗格可以幫助我們更容易地導航和理解複雜的數據集。相反,當你不需要凍結窗格時,你可能需要解凍它們以獲得完整的視野。 下麵將介紹如何使用免費.NET庫通過C#實現凍結Excel視窗以鎖 ...
  • .NET 部署 IIS 的簡單步驟一: 下載 dotnet-hosting-x.y.z-win.exe ,下載地址:.NET Downloads (Linux, macOS, and Windows) (microsoft.com) .NET 部署 IIS 的簡單步驟二: 選擇對應的版本,點擊進入詳 ...
  • 拓展閱讀 資料庫設計工具-08-概覽 資料庫設計工具-08-powerdesigner 資料庫設計工具-09-mysql workbench 資料庫設計工具-10-dbdesign 資料庫設計工具-11-dbeaver 資料庫設計工具-12-pgmodeler 資料庫設計工具-13-erdplus ...
  • 初識STL STL,(Standard Template Library),即"標準模板庫",由惠普實驗室開發,STL中提供了非常多對信息學奧賽很有用的東西。 vector vetor是STL中的一個容器,可以看作一個不定長的數組,其基本形式為: vector<數據類型> 名字; 如: vector ...
  • 前言 最近自己做了個 Falsk 小項目,在部署上伺服器的時候,發現雖然不乏相關教程,但大多都是將自己項目代碼複製出來,不講核心邏輯,不太簡潔,於是將自己部署的經驗寫成內容分享出來。 uWSGI 簡介 uWSGI: 一種實現了多種協議(包括 uwsgi、http)並能提供伺服器搭建功能的 Pytho ...
  • 1 文本Embedding 將整個文本轉化為實數向量的技術。 Embedding優點是可將離散的詞語或句子轉化為連續的向量,就可用數學方法來處理詞語或句子,捕捉到文本的語義信息,文本和文本的關係信息。 ◉ 優質的Embedding通常會讓語義相似的文本在空間中彼此接近 ◉ 優質的Embedding相 ...