diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs
index a3a2e0c2b0..39ef4374aa 100644
--- a/src/Avalonia.Base/Data/BindingNotification.cs
+++ b/src/Avalonia.Base/Data/BindingNotification.cs
@@ -178,7 +178,7 @@ namespace Avalonia.Data
/// to . If is a
/// then the value will first be extracted.
///
- public static object? UpdateValue(object o, object value)
+ public static object? UpdateValue(object? o, object value)
{
if (o is BindingNotification n)
{
diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs
index c98650fa8e..ba0ce7bb9e 100644
--- a/src/Avalonia.Base/Data/Core/BindingExpression.cs
+++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs
@@ -160,7 +160,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
var source = _nodes[0].Source;
for (var i = 0; i < _nodes.Count; ++i)
- _nodes[i].SetSource(null, null);
+ _nodes[i].SetSource(AvaloniaProperty.UnsetValue, null);
_nodes[0].SetSource(source, null);
}
@@ -253,10 +253,6 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
_nodes[nodeIndex + 1].SetSource(value, dataValidationError);
WriteTargetValueToSource();
}
- else if (value is null)
- {
- OnNodeError(nodeIndex, "Value is null.");
- }
else
{
_nodes[nodeIndex + 1].SetSource(value, dataValidationError);
@@ -273,11 +269,11 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
/// The error message.
internal void OnNodeError(int nodeIndex, string error)
{
- // Set the source of all nodes after the one that errored to null. This needs to be done
- // for each node individually because setting the source to null will not result in
+ // Set the source of all nodes after the one that errored to unset. This needs to be done
+ // for each node individually because setting the source to unset will not result in
// OnNodeValueChanged or OnNodeError being called.
for (var i = nodeIndex + 1; i < _nodes.Count; ++i)
- _nodes[i].SetSource(null, null);
+ _nodes[i].SetSource(AvaloniaProperty.UnsetValue, null);
if (_mode == BindingMode.OneWayToSource)
return;
@@ -394,7 +390,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
protected override void StopCore()
{
foreach (var node in _nodes)
- node.Reset();
+ node.SetSource(AvaloniaProperty.UnsetValue, null);
if (_mode is BindingMode.TwoWay or BindingMode.OneWayToSource &&
TryGetTarget(out var target))
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/ArrayIndexerNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/ArrayIndexerNode.cs
index a347a1ab72..514cf235ff 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/ArrayIndexerNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/ArrayIndexerNode.cs
@@ -44,8 +44,11 @@ internal sealed class ArrayIndexerNode : ExpressionNode, ISettableNode
return false;
}
- protected override void OnSourceChanged(object source, Exception? dataValidationError)
+ protected override void OnSourceChanged(object? source, Exception? dataValidationError)
{
+ if (!ValidateNonNullSource(source))
+ return;
+
if (source is Array array)
SetValue(array.GetValue(_indexes));
else
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/AvaloniaPropertyAccessorNode.cs
index 09f4c9be26..266fbb884a 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/AvaloniaPropertyAccessorNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/AvaloniaPropertyAccessorNode.cs
@@ -38,6 +38,9 @@ internal sealed class AvaloniaPropertyAccessorNode :
protected override void OnSourceChanged(object? source, Exception? dataValidationError)
{
+ if (!ValidateNonNullSource(source))
+ return;
+
if (source is AvaloniaObject newObject)
{
WeakEvents.AvaloniaPropertyChanged.Subscribe(newObject, this);
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/CollectionNodeBase.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/CollectionNodeBase.cs
index db8a8e8080..1c9c7d0294 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/CollectionNodeBase.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/CollectionNodeBase.cs
@@ -22,8 +22,11 @@ internal abstract class CollectionNodeBase : ExpressionNode,
UpdateValueOrSetError(sender);
}
- protected override void OnSourceChanged(object source, Exception? dataValidationError)
+ protected override void OnSourceChanged(object? source, Exception? dataValidationError)
{
+ if (!ValidateNonNullSource(source))
+ return;
+
Subscribe(source);
UpdateValue(source);
}
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/DataContextNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/DataContextNode.cs
index 14e21d4192..2aa14f12a2 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/DataContextNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/DataContextNode.cs
@@ -4,8 +4,11 @@ namespace Avalonia.Data.Core.ExpressionNodes;
internal sealed class DataContextNode : DataContextNodeBase
{
- protected override void OnSourceChanged(object source, Exception? dataValidationError)
+ protected override void OnSourceChanged(object? source, Exception? dataValidationError)
{
+ if (!ValidateNonNullSource(source))
+ return;
+
if (source is IDataContextProvider && source is AvaloniaObject ao)
{
ao.PropertyChanged += OnPropertyChanged;
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs
index 8b53190f86..ee52308756 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
@@ -60,16 +61,6 @@ internal abstract class ExpressionNode
BuildString(builder);
}
- ///
- /// Resets the node to its uninitialized state when the is unsubscribed.
- ///
- public void Reset()
- {
- SetSource(null, null);
- _source = null;
- _value = AvaloniaProperty.UnsetValue;
- }
-
///
/// Sets the owner binding.
///
@@ -101,28 +92,26 @@ internal abstract class ExpressionNode
///
public void SetSource(object? source, Exception? dataValidationError)
{
- var oldSource = Source;
-
- if (source == AvaloniaProperty.UnsetValue)
- source = null;
+ if (_source?.TryGetTarget(out var oldSource) != true)
+ oldSource = AvaloniaProperty.UnsetValue;
if (source == oldSource)
return;
- if (oldSource is not null)
+ if (oldSource is not null && oldSource != AvaloniaProperty.UnsetValue)
Unsubscribe(oldSource);
- _source = new(source);
-
- if (source is null)
+ if (source == AvaloniaProperty.UnsetValue)
{
- // If the source is null then the value is null. We explicitly do not want to call
+ // If the source is unset then the value is unset. We explicitly do not want to call
// OnSourceChanged as we don't want to raise errors for subsequent nodes in the
// binding change.
+ _source = null;
_value = AvaloniaProperty.UnsetValue;
}
else
{
+ _source = new(source);
try { OnSourceChanged(source, dataValidationError); }
catch (Exception e) { SetError(e); }
}
@@ -242,6 +231,26 @@ internal abstract class ExpressionNode
}
}
+ ///
+ /// Called from to validate that the source
+ /// is non-null and raise a node error if it is not.
+ ///
+ /// The expression node source.
+ ///
+ /// True if the source is non-null; otherwise, false.
+ ///
+ protected bool ValidateNonNullSource([NotNullWhen(true)] object? source)
+ {
+ if (source is null)
+ {
+ Owner?.OnNodeError(Index - 1, "Value is null.");
+ _value = null;
+ return false;
+ }
+
+ return true;
+ }
+
///
/// When implemented in a derived class, subscribes to the new source, and updates the current
/// .
@@ -250,7 +259,7 @@ internal abstract class ExpressionNode
///
/// Any data validation error reported by the previous expression node.
///
- protected abstract void OnSourceChanged(object source, Exception? dataValidationError);
+ protected abstract void OnSourceChanged(object? source, Exception? dataValidationError);
///
/// When implemented in a derived class, unsubscribes from the previous source.
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/FuncTransformNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/FuncTransformNode.cs
index 7ad0b7ee97..1eb15d2469 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/FuncTransformNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/FuncTransformNode.cs
@@ -21,8 +21,11 @@ internal sealed class FuncTransformNode : ExpressionNode
// We don't have enough information to add anything here.
}
- protected override void OnSourceChanged(object source, Exception? dataValidationError)
+ protected override void OnSourceChanged(object? source, Exception? dataValidationError)
{
+ if (!ValidateNonNullSource(source))
+ return;
+
SetValue(_transform(source));
}
}
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/LogicalAncestorElementNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/LogicalAncestorElementNode.cs
index ccf6c76f90..b15f285867 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/LogicalAncestorElementNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/LogicalAncestorElementNode.cs
@@ -56,8 +56,11 @@ internal sealed class LogicalAncestorElementNode : SourceNode
return target is ILogical logical && logical.IsAttachedToLogicalTree;
}
- protected override void OnSourceChanged(object source, Exception? dataValidationError)
+ protected override void OnSourceChanged(object? source, Exception? dataValidationError)
{
+ if (!ValidateNonNullSource(source))
+ return;
+
if (source is ILogical logical)
{
var locator = ControlLocator.Track(logical, _ancestorLevel, _ancestorType);
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/LogicalNotNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/LogicalNotNode.cs
index bb65ac16dd..8b1102b463 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/LogicalNotNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/LogicalNotNode.cs
@@ -28,14 +28,12 @@ internal sealed class LogicalNotNode : ExpressionNode, ISettableNode
return false;
}
- protected override void OnSourceChanged(object source, Exception? dataValidationError)
+ protected override void OnSourceChanged(object? source, Exception? dataValidationError)
{
var v = BindingNotification.ExtractValue(source);
if (TryConvert(v, out var value))
- {
SetValue(BindingNotification.UpdateValue(source, !value), dataValidationError);
- }
else
SetError($"Unable to convert '{source}' to bool.");
}
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/MethodCommandNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/MethodCommandNode.cs
index 76ea564320..e0641f1461 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/MethodCommandNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/MethodCommandNode.cs
@@ -39,8 +39,11 @@ internal sealed class MethodCommandNode : ExpressionNode, IWeakEventSubscriber
(source);
if (_plugin.Start(reference, PropertyName) is { } accessor)
diff --git a/src/Avalonia.Base/Data/Core/ExpressionNodes/Reflection/DynamicPluginPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNodes/Reflection/DynamicPluginPropertyAccessorNode.cs
index d1ecc13208..533520d35c 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionNodes/Reflection/DynamicPluginPropertyAccessorNode.cs
+++ b/src/Avalonia.Base/Data/Core/ExpressionNodes/Reflection/DynamicPluginPropertyAccessorNode.cs
@@ -42,8 +42,11 @@ internal sealed class DynamicPluginPropertyAccessorNode : ExpressionNode, IPrope
return _accessor?.SetValue(value, BindingPriority.LocalValue) ?? false;
}
- protected override void OnSourceChanged(object source, Exception? dataValidationError)
+ protected override void OnSourceChanged(object? source, Exception? dataValidationError)
{
+ if (!ValidateNonNullSource(source))
+ return;
+
var reference = new WeakReference