Browse Source

Don't use weak references for values.

If they're boxed values, they can get collected.
pull/13970/head
Steven Kirk 3 years ago
parent
commit
9025e1d4c8
  1. 2
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  2. 39
      src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs
  3. 31
      src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs

2
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -330,7 +330,7 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
}
// Don't set the value if it's unchanged.
if (LeafNode.IsValueAlive && TypeUtilities.IdentityEquals(LeafNode.Value, value, type))
if (TypeUtilities.IdentityEquals(LeafNode.Value, value, type))
return true;
try

39
src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs

@ -12,7 +12,7 @@ namespace Avalonia.Data.Core.ExpressionNodes;
internal abstract class ExpressionNode
{
private WeakReference<object?>? _source;
private WeakReference<object?>? _value;
private object? _value = AvaloniaProperty.UnsetValue;
/// <summary>
/// Gets the index of the node in the binding path.
@ -40,29 +40,7 @@ internal abstract class ExpressionNode
/// <summary>
/// Gets the current value of the node.
/// </summary>
public object? Value
{
get
{
if (_value is null)
return AvaloniaProperty.UnsetValue;
_value.TryGetTarget(out var value);
return value;
}
}
/// <summary>
/// Gets a value indicating whether the node's <see cref="Value"/> is alive, i.e.
/// initialized and not a GC'd object.
/// </summary>
public bool IsValueAlive
{
get
{
return _value == UntypedBindingExpressionBase._nullReference ||
_value?.TryGetTarget(out _) == true;
}
}
public object? Value => _value;
/// <summary>
/// Appends a string representation of the expression node to a string builder.
@ -88,7 +66,8 @@ internal abstract class ExpressionNode
public void Reset()
{
SetSource(null, null);
_source = _value = null;
_source = null;
_value = AvaloniaProperty.UnsetValue;
}
/// <summary>
@ -140,7 +119,7 @@ internal abstract class ExpressionNode
// If the source is null then the value is null. We explicitly do not want to call
// OnSourceChanged as we don't want to raise errors for subsequent nodes in the
// binding change.
_value = BindingExpression._nullReference;
_value = AvaloniaProperty.UnsetValue;
}
else
{
@ -172,7 +151,7 @@ internal abstract class ExpressionNode
/// <param name="message">The error message.</param>
protected void SetError(string message)
{
_value = new(AvaloniaProperty.UnsetValue);
_value = AvaloniaProperty.UnsetValue;
Owner?.OnNodeError(Index, message);
}
@ -245,15 +224,13 @@ internal abstract class ExpressionNode
// - This is the initial value (_value is null)
// - There is a data validation error
// - There is no data validation error, but the owner has one
// - The old value has been GC'd - in this case we don't know if the new value is different
// - The new value is different to the old value
if (_value is null ||
dataValidationError is not null ||
(dataValidationError is null && Owner.ErrorType == BindingErrorType.DataValidationError) ||
_value.TryGetTarget(out var oldValue) == false ||
!Equals(oldValue, value))
!Equals(value, _value))
{
_value = value is null ? BindingExpression._nullReference : new(value);
_value = value;
Owner.OnNodeValueChanged(Index, value, dataValidationError);
}
}

31
src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs

@ -21,7 +21,6 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
IValueEntry
{
protected static readonly object UnchangedValue = new();
internal static readonly WeakReference<object?> _nullReference = new(null);
private readonly bool _isDataValidationEnabled;
private object? _defaultValue;
private BindingError? _error;
@ -30,7 +29,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
private bool _produceValue;
private IBindingExpressionSink? _sink;
private WeakReference<AvaloniaObject?>? _target;
private WeakReference<object?>? _value;
private object? _value = AvaloniaProperty.UnsetValue;
/// <summary>
/// Initializes a new instance of the <see cref="UntypedBindingExpressionBase"/> class.
@ -92,7 +91,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
get
{
Start(produceValue: false);
return _value is not null;
return true;
}
}
@ -127,14 +126,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
{
if (!IsRunning)
throw new InvalidOperationException("BindingExpression has not been started.");
if (_value is null)
return AvaloniaProperty.UnsetValue;
else if (_value == _nullReference)
return null;
else if (_value.TryGetTarget(out var value))
return value;
else
return AvaloniaProperty.UnsetValue;
return _value;
}
/// <summary>
@ -441,7 +433,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
var hasErrorChanged = error is not null || _error is not null;
if (hasValueChanged)
_value = value is null ? _nullReference : new(value);
_value = value;
_error = error;
if (!_produceValue || _sink is null)
@ -516,7 +508,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
{
StopCore();
_isRunning = false;
_value = null;
_value = AvaloniaProperty.UnsetValue;
}
/// <summary>
@ -566,7 +558,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
IAvaloniaSubject<object?>
{
private readonly UntypedBindingExpressionBase _expression;
private WeakReference<object?>? _value;
private object? _value = AvaloniaProperty.UnsetValue;
public ObservableSink(UntypedBindingExpressionBase expression) => _expression = expression;
@ -607,18 +599,13 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
protected override void Subscribed(IObserver<object> observer, bool first)
{
if (!first && _value is not null)
{
if (_value == _nullReference)
base.PublishNext(null);
else if (_value.TryGetTarget(out var value))
base.PublishNext(value);
}
if (!first && _value != AvaloniaProperty.UnsetValue)
base.PublishNext(_value);
}
private new void PublishNext(object? value)
{
_value = (value is null) ? _nullReference : new(value);
_value = value;
base.PublishNext(value);
}
}

Loading…
Cancel
Save