主頁 > .NET開發 > 讀 MAUI 源代碼 理解可系結物件和可系結屬性的存盤機制

讀 MAUI 源代碼 理解可系結物件和可系結屬性的存盤機制

2022-09-14 06:00:40 .NET開發

和 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 = https://www.cnblogs.com/lindexi/p/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 = https://www.cnblogs.com/lindexi/p/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]),

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/507078.html

標籤:.NET Core

上一篇:AndroidStudio在專案視窗樹中不顯示檔案夾

下一篇:【Shashlik.EventBus】.NET 事件總線,分布式事務最終一致性

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more