和UWP与WPF的不同之处在于,在MAUI中,依赖对象的概念被可绑定对象BindableObject所取代。我看了MAUI的源码发现,其实只是换了个名字,里面的机制和设计思路都是大同小异的。MAUI中提供了BindableObject,支持可绑定属性机制和附加属性机制。本文将告诉您如何在MAUI中提供可绑定属性和可绑定对象中附加属性的存储机制。在WPF中,提出依赖属性的部分原因是为了节省内存。在MAUI中,我猜节省内存是提出可绑定对象的原因之一。由于一个界面控件,比如一个按钮,有非常多的属性,假设每个控件中的所有属性都需要独立的对象,不能共享,那么在一个复杂的界面上,大量控件的大量属性会占用大量的财产。的记忆。在可绑定对象中,可以实现当属性没有赋值时,可以使用默认值。对于大多数控件,许多不常见的属性可以使用默认值。可绑定对象需要解决的是让可绑定属性代替普通的CLR属性。当给可绑定属性赋值时,该值可以与可绑定对象相关联,这样它就可以被读出。由于名称是可绑定对象,因此很自然地实现了绑定支持。绑定支持的核心是通知,需要支持属性值变化时的通知。接下来,我们将通过阅读源码来了解如何在MAUI中实现。在MAUI中打开BindableObject的源码,可以看到BindableObject中有一个_properties字段,其定义如下:BindablePropertyContext>(;}没错,这就是MAUI中可绑定对象的存储核心实现。在MAUI的可绑定对象中,可绑定属性的取值内容是通过_properties字典存储的。字典的Key是BindableProperty可绑定属性,字典的Value是BindablePropertyContext可绑定属性上下文。初始化字典默认占用4个空间,默认的初始化空间只是为了优化,没有特殊用途。通过这个字典定义,我们可以了解到存储的核心实现是将可绑定的属性和对应的值存储在对象字典中,比如给一个可绑定对象的名为Xxx的可绑定属性赋值,这样就会将Xxx属性的值内容更新到_properties字典中。MAUI中的实现是使用SetValueCore方法更新和分配可绑定对象中的属性。我删除了非关键逻辑代码如下:SetValueActual(属性、上下文、值、currentlyApplying、属性、无声);}BindablePropertyContextGetOrCreateContext(BindablePropertyproperty)=>GetContext(property)??CreateAndAddContext(属性);内部BindablePropertyContextGetContext(BindableProperty属性)=>_properties.TryGetValue(属性,输出变量结果)?结果:空;BindablePropertyContextCreateAndAddContext(BindablePropertyproperty){varcontext=newBindablePropertyContext{...};_properties.Add(属性,上下文);返回上下文;}无效SetValueActual(BindablePropertyproperty,BindablePropertyContextcontext,objectvalue,boolcurrentlyApplying,SetValueFlagsattributes,boolsilent=false){//在对象改变之前触发事件context.Value=value;//触发对象改变事件}可以看到赋值的第一个值第一步是调用GetOrCreateContext方法尝试获取上下文信息。如果你不能得到它,就创造它。这里使用的BindablePropertyContext上下文信息是存储可绑定属性的关键。BindablePropertyContext中存储了很多字段,定义如下:publicabstractclassBindableObject:INotifyPropertyChanged,IDynamicResourceHandler{公共BindingBase绑定;公共队列DelayedSetters;公共BindableProperty属性;公共对象值;公共布尔样式值集;公共对象样式值;}}可以看到BindablePropertyContext是一个内部类型,不向公众开放。BindablePropertyContext中最重要的是Value字段,它代表实际存储的值。其次,为了更好的支持绑定,还增加了Binding字段。获取到BindablePropertyContext上下文后,就可以赋值了。赋值是调用SetValueActual方法赋值。事件在分配通知之前和之后触发。触发通知事件最重要的作用就是给绑定一个刷新的机会。这样就完成了赋值过程。通知事件触发可绑定对象的通知事件和对应的可绑定属性的通知事件,比如下面的代码:{//在对象改变之前触发事件property.PropertyChanging?.Invoke(this,original,value);OnPropertyChanging(property.PropertyName);context.Value=值;//触发对象改变事件OnPropertyChanged(property.PropertyName);property.PropertyChanged?.Invoke(this,original,value);通过上面的代码,我们可以看到,当一个可绑定对象给一个可绑定属性赋值时,首先会获取或创建一个可绑定属性上下文,并将赋值参数的值赋给可绑定属性上下文的Value字段.这样就完成了赋值过程。由于赋值参数值被放入可绑定属性上下文的Value字段,而可绑定属性上下文被放入_properties字典,相当于间接将赋值参数值放入_properties字典。自然在获取值的过程中,还需要从字典中进行读取。MAUI中读取可绑定属性是通过GetValue方法实现的,代码如下:varcontext=property.DefaultValueCreator!=空?GetOrCreateContext(属性):GetContext(属性);返回上下文==null?property.DefaultValue:context.Value;上面代码中判断BindableProperty的DefaultValueCreator属性的逻辑是MAUI特有的,不同于WPF和UWP。以后再说吧。回到获取属性的方法,就是先获取对象的可绑定上下文信息。如果能够获取到bindablecontext,则证明该bindable对象的bindable属性已经被赋值,需要通过赋值更新Content。如果获得的可绑定属性上下文为空,则使用可绑定属性定义的默认值。在MAUI中,BindableProperty的DefaultValueCreator属性简化了可绑定属性的定义,使可绑定属性更加强大。使用MAUI的可绑定属性和可绑定对象来对比WPF的依赖属性和依赖对象的实现,可以看出MAUI的实现要简单的多。MAUI中BindableProperty的DefaultValueCreator属性是一个delegate,定义如下:内部CreateDefaultValueDelegateDefaultValueCreator{get;处理传入的可绑定对象,并为可绑定对象返回特定的默认值。这里值得说明的是,通过委托,可以给可绑定对象赋予不同的默认值,但并不意味着不同的可绑定对象就必须要求不同的默认值对象。这里只是一个委托,让委托返回相同的对象是完全没问题的。这个委托更多的是用来判断可绑定对象的类型,根据可绑定类型对象或状态返回不同的默认值。或者返回一个需要在运行时动态计算的值,而不是一个可以在代码中编写和固定的参数。例如在定义FontSize这个bindable属性时,不同的控件返回不同的默认字体大小,定义如下:publicstaticreadonlyBindablePropertyFontSizeProperty=BindableProperty.Create("FontSize",typeof(double),typeof(IFontElement),0d,propertyChanged:OnFontSizeChanged,defaultValueCreator:FontSizeDefaultValueCreator);静态对象FontSizeDefaultValueCreator(BindableObjectbindable)=>((IFontElement)bindable).FontSizeDefaultValueCreator();也就是说,对于不同的可绑定对象,获取到的默认字体大小是根据对应可绑定对象的FontSizeDefaultValueCreator方法的实现来确定的。不同的可绑定对象可以有不同的实现,因此默认值可以与特定的可绑定对象类型相关联。这种创新的设计可以省去WPF中默认依赖属性值重写的大量逻辑代码,省去这部分代码,也可以大大减少机制,从而减少更多的代码。例如,Span和Editor控件具有不同的字体大小默认值实现。公共类Span:GestureElement,IFontElement{doubleIFontElement.FontSizeDefaultValueCreator()=>double.NaN;}publicpartialclassButton:View,IFontElement{doubleIFontElement.FontSizeDefaultValueCreator()=>this.GetDefaultFontSize();对于可绑定属性,需要给每个可绑定对象一个不同的默认值对象,比如Grid中的RowDefinitions属性。大家都知道Grid中的RowDefinitions是一个集合。如果集合也是共享默认值,自然会出现默认值污染。如果默认值为null值,Grid逻辑中会有很多判断null的逻辑,或者需要其他额外的初始化逻辑。在MAUI中,每个Grid对象通过DefaultValueCreator委托使用一个独立的默认值对象。代码如下:publicclassGrid:Layout,IGridLayout{publicstaticreadonlyBindablePropertyRowDefinitionsProperty=BindableProperty.Create("RowDefinitions",typeof(RowDefinitionCollection),typeof(Grid),null,validateValue:(bindable,value)=>value!=null,propertyChanged:UpdateSizeChangedHandlers,defaultValueCreator:bindable=>{//每个Grid对象使用一个单独的、新创建的默认值对象varrowDef=newRowDefinitionCollection();rowDef.ItemSizeChanged+=((Grid)bindable).DefinitionsChanged;返回rowDef;});}除了可绑定属性外,MAUI中还有一种特殊的属性类型,即附加属性。可以在任何类型中定义附加属性,并通过附加属性向现有类型添加属性。在功能上,它与WPF或UWP的附加属性功能相同。可绑定属性和附加属性都是同一个BindableProperty类型,只是创建时调用的静态创建方法不同。对于可绑定属性,调用BindableProperty.Create方法创建。对于附加属性,调用BindableProperty.CreateAttached创建。在MAUI中,通过阅读代码,我认为两种方法的分离更多的是为了兼容WPF或者UWP。没有很本质的区别,参数也差不多,如下代码:,BindingPropertyChangingDelegatepropertyChanging,CoerceValueDelegatecoerceValue,BindablePropertyBindingChangingbindingChanging,CreateDefaultValueDelegatedefaultValueCreator=null){返回新的BindableProperty(propertyName,returnType,declaringType,defaultValue,defaultBindingMode,validateValue,propertyChanged,propertyChanging,coerceValue,bindingChanging,defaultValueCreator:defaultValueCreator);}内部静态BindablePropertyCreateAttached(stringpropertyName,TypereturnType,[DynamicallyAccessedMembers(DeclaringTypeMembers)]TypedeclaringType,objectdefaultValue,BindingModedefaultBindingMode,ValidateValueDelegatevalidateValue,BindingPropertyChangedDelegatepropertyChanged,BindingPropertyChangingDelegatepropertyChanging,CoerceValueDelegatecoerceValue,BindablePropertyBindingChangingbindingChanging,boolisReadOnly,CreateDefaultValueDelegatedefaultValueCreator=null){returnnewBindableProperty(propertyName,returnType,declaringType,defaultValue,defaultBindingMode,validateValue,propertyChanged,propertyChanging,coerceValue,bindingChanging,isReadOnly,defaultValueCreator);所以从参数上可以看出bindable属性和additional属性好像是一样的,因为additional属性也是一个Bindable属性类型,类似的,我们可以理解为附加属性的存储和可绑定对象的可绑定属性。这也可以回答一个问题,MAUI的附加属性是附加在对象上的,附加属性的参数值是如何跟随对象的生命周期的。由于附加属性也是可绑定属性,因此参数值也存储在可绑定对象的_properties字典中。对象被GC回收的时候,自然_properties字段也被回收了,放在字典中的参数值自然也减去引用,当参数值没有被引用时,自然回收。在MAUI中,可绑定对象的基类型的意思是提供一种绑定属性的机制,而存储可绑定属性的方式是通过_properties字典。字典中存储的内容是被赋值改变的属性,没有被赋值改变的属性不放入字典中。当获取字典中没有存储的属性时,会通过对应的可绑定属性获取默认值。默认值的获取有两种方式,一种是可绑定属性的固定默认值属性,另一种是通过可绑定属性的默认值创建委托来创建默认值。MAUI中可绑定属性的默认值创建委托是一项创新。可以编写让不同的可绑定对象使用不同默认值的函数,也可以根据不同的可绑定对象类型编写返回不同默认值的函数。默认值,通过委托灵活实现复杂的功能。