diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 7601b64ce9..a3d5803ab0 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -466,7 +466,7 @@ namespace Avalonia
/// The old property value.
/// The new property value.
/// The priority of the binding that produced the value.
- protected void RaisePropertyChanged(
+ protected internal void RaisePropertyChanged(
AvaloniaProperty property,
object oldValue,
object newValue,
@@ -508,45 +508,6 @@ namespace Avalonia
}
}
- ///
- /// A callback type for encapsulating complex logic for setting direct properties.
- ///
- /// The type of the property.
- /// The value to which to set the property.
- /// The backing field for the property.
- /// A wrapper for the property-changed notification.
- protected delegate void SetAndRaiseCallback(T value, ref T field, Action notifyWrapper);
-
- ///
- /// Sets the backing field for a direct avalonia property, raising the
- /// event if the value has changed.
- ///
- /// The type of the property.
- /// The property.
- /// The backing field.
- /// A callback called to actually set the value to the backing field.
- /// The value.
- ///
- /// True if the value changed, otherwise false.
- ///
- protected bool SetAndRaise(
- AvaloniaProperty property,
- ref T field,
- SetAndRaiseCallback setterCallback,
- T value)
- {
- Contract.Requires(setterCallback != null);
- return Values.Setter.SetAndNotify(
- property,
- ref field,
- (object update, ref T backing, Action notify) =>
- {
- setterCallback((T)update, ref backing, notify);
- return true;
- },
- value);
- }
-
///
/// Sets the backing field for a direct avalonia property, raising the
/// event if the value has changed.
@@ -561,32 +522,15 @@ namespace Avalonia
protected bool SetAndRaise(AvaloniaProperty property, ref T field, T value)
{
VerifyAccess();
- return SetAndRaise(
- property,
- ref field,
- (T val, ref T backing, Action notifyWrapper)
- => SetAndRaiseCore(property, ref backing, val, notifyWrapper),
- value);
- }
- ///
- /// Default assignment logic for SetAndRaise.
- ///
- /// The type of the property.
- /// The property.
- /// The backing field.
- /// The value.
- /// A wrapper for the property-changed notification.
- ///
- /// True if the value changed, otherwise false.
- ///
- private bool SetAndRaiseCore(AvaloniaProperty property, ref T field, T value, Action notifyWrapper)
- {
- var old = field;
- field = value;
+ if (EqualityComparer.Default.Equals(field, value))
+ {
+ return false;
+ }
+
+ DeferredSetter setter = Values.GetDirectDeferredSetter(property);
- notifyWrapper(() => RaisePropertyChanged(property, old, value, BindingPriority.LocalValue));
- return true;
+ return setter.SetAndNotify(this, property, ref field, value);
}
///
diff --git a/src/Avalonia.Base/IPriorityValueOwner.cs b/src/Avalonia.Base/IPriorityValueOwner.cs
index 540b1bf19b..1d6e5e59ad 100644
--- a/src/Avalonia.Base/IPriorityValueOwner.cs
+++ b/src/Avalonia.Base/IPriorityValueOwner.cs
@@ -29,6 +29,13 @@ namespace Avalonia
/// The notification.
void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
+ ///
+ /// Returns deferred setter for given non-direct property.
+ ///
+ /// Property.
+ /// Deferred setter for given property.
+ DeferredSetter GetNonDirectDeferredSetter(AvaloniaProperty property);
+
///
/// Logs a binding error.
///
@@ -40,7 +47,5 @@ namespace Avalonia
/// Ensures that the current thread is the UI thread.
///
void VerifyAccess();
-
- DeferredSetter Setter { get; }
}
}
diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs
index 89a893577f..4996420fe7 100644
--- a/src/Avalonia.Base/PriorityValue.cs
+++ b/src/Avalonia.Base/PriorityValue.cs
@@ -30,7 +30,9 @@ namespace Avalonia
private readonly SingleOrDictionary _levels = new SingleOrDictionary();
private readonly Func _validate;
+ private readonly SetAndNotifyCallback<(object, int)> _setAndNotifyCallback;
private (object value, int priority) _value;
+ private DeferredSetter _setter;
///
/// Initializes a new instance of the class.
@@ -50,6 +52,7 @@ namespace Avalonia
_valueType = valueType;
_value = (AvaloniaProperty.UnsetValue, int.MaxValue);
_validate = validate;
+ _setAndNotifyCallback = SetAndNotify;
}
///
@@ -242,22 +245,22 @@ namespace Avalonia
/// The priority level that the value came from.
private void UpdateValue(object value, int priority)
{
- Owner.Setter.SetAndNotify(Property,
- ref _value,
- UpdateCore,
- (value, priority));
- }
+ var newValue = (value, priority);
+
+ if (newValue == _value)
+ {
+ return;
+ }
- private bool UpdateCore(
- object update,
- ref (object value, int priority) backing,
- Action notify)
- => UpdateCore(((object, int))update, ref backing, notify);
+ if (_setter == null)
+ {
+ _setter = Owner.GetNonDirectDeferredSetter(Property);
+ }
+
+ _setter.SetAndNotifyCallback(Property, _setAndNotifyCallback, ref _value, newValue);
+ }
- private bool UpdateCore(
- (object value, int priority) update,
- ref (object value, int priority) backing,
- Action notify)
+ private void SetAndNotify(AvaloniaProperty property, ref (object value, int priority) backing, (object value, int priority) update)
{
var val = update.value;
var notification = val as BindingNotification;
@@ -286,7 +289,7 @@ namespace Avalonia
if (notification == null || notification.HasValue)
{
- notify(() => Owner?.Changed(Property, ValuePriority, old, Value));
+ Owner?.Changed(Property, ValuePriority, old, Value);
}
if (notification != null)
@@ -305,7 +308,6 @@ namespace Avalonia
val,
val?.GetType());
}
- return true;
}
}
}
diff --git a/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs
new file mode 100644
index 0000000000..ac128d83de
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs
@@ -0,0 +1,150 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+
+namespace Avalonia.Utilities
+{
+ ///
+ /// Stores values with as key.
+ ///
+ /// Stored value type.
+ internal sealed class AvaloniaPropertyValueStore
+ {
+ private Entry[] _entries;
+
+ public AvaloniaPropertyValueStore()
+ {
+ // The last item in the list is always int.MaxValue
+ _entries = new[] { new Entry { PropertyId = int.MaxValue, Value = default } };
+ }
+
+ private (int, bool) TryFindEntry(int propertyId)
+ {
+ if (_entries.Length <= 12)
+ {
+ // For small lists, we use an optimized linear search. Since the last item in the list
+ // is always int.MaxValue, we can skip a conditional branch in each iteration.
+ // By unrolling the loop, we can skip another unconditional branch in each iteration.
+
+ if (_entries[0].PropertyId >= propertyId)
+ return (0, _entries[0].PropertyId == propertyId);
+ if (_entries[1].PropertyId >= propertyId)
+ return (1, _entries[1].PropertyId == propertyId);
+ if (_entries[2].PropertyId >= propertyId)
+ return (2, _entries[2].PropertyId == propertyId);
+ if (_entries[3].PropertyId >= propertyId)
+ return (3, _entries[3].PropertyId == propertyId);
+ if (_entries[4].PropertyId >= propertyId)
+ return (4, _entries[4].PropertyId == propertyId);
+ if (_entries[5].PropertyId >= propertyId)
+ return (5, _entries[5].PropertyId == propertyId);
+ if (_entries[6].PropertyId >= propertyId)
+ return (6, _entries[6].PropertyId == propertyId);
+ if (_entries[7].PropertyId >= propertyId)
+ return (7, _entries[7].PropertyId == propertyId);
+ if (_entries[8].PropertyId >= propertyId)
+ return (8, _entries[8].PropertyId == propertyId);
+ if (_entries[9].PropertyId >= propertyId)
+ return (9, _entries[9].PropertyId == propertyId);
+ if (_entries[10].PropertyId >= propertyId)
+ return (10, _entries[10].PropertyId == propertyId);
+ }
+ else
+ {
+ int low = 0;
+ int high = _entries.Length;
+ int id;
+
+ while (high - low > 3)
+ {
+ int pivot = (high + low) / 2;
+ id = _entries[pivot].PropertyId;
+
+ if (propertyId == id)
+ return (pivot, true);
+
+ if (propertyId <= id)
+ high = pivot;
+ else
+ low = pivot + 1;
+ }
+
+ do
+ {
+ id = _entries[low].PropertyId;
+
+ if (id == propertyId)
+ return (low, true);
+
+ if (id > propertyId)
+ break;
+
+ ++low;
+ }
+ while (low < high);
+ }
+
+ return (0, false);
+ }
+
+ public bool TryGetValue(AvaloniaProperty property, out TValue value)
+ {
+ (int index, bool found) = TryFindEntry(property.Id);
+ if (!found)
+ {
+ value = default;
+ return false;
+ }
+
+ value = _entries[index].Value;
+ return true;
+ }
+
+ public void AddValue(AvaloniaProperty property, TValue value)
+ {
+ Entry[] entries = new Entry[_entries.Length + 1];
+
+ for (int i = 0; i < _entries.Length; ++i)
+ {
+ if (_entries[i].PropertyId > property.Id)
+ {
+ if (i > 0)
+ {
+ Array.Copy(_entries, 0, entries, 0, i);
+ }
+
+ entries[i] = new Entry { PropertyId = property.Id, Value = value };
+ Array.Copy(_entries, i, entries, i + 1, _entries.Length - i);
+ break;
+ }
+ }
+
+ _entries = entries;
+ }
+
+ public void SetValue(AvaloniaProperty property, TValue value)
+ {
+ _entries[TryFindEntry(property.Id).Item1].Value = value;
+ }
+
+ public Dictionary ToDictionary()
+ {
+ var dict = new Dictionary(_entries.Length - 1);
+
+ for (int i = 0; i < _entries.Length - 1; ++i)
+ {
+ dict.Add(AvaloniaPropertyRegistry.Instance.FindRegistered(_entries[i].PropertyId), _entries[i].Value);
+ }
+
+ return dict;
+ }
+
+ private struct Entry
+ {
+ internal int PropertyId;
+ internal TValue Value;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs
index 1b1324b1c5..fd7a66fb52 100644
--- a/src/Avalonia.Base/Utilities/DeferredSetter.cs
+++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs
@@ -1,168 +1,122 @@
-using System;
-using System.Collections.Generic;
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
namespace Avalonia.Utilities
{
+ ///
+ /// Callback invoked when deferred setter wants to set a value.
+ ///
+ /// Value type.
+ /// Property being set.
+ /// Backing field reference.
+ /// New value.
+ internal delegate void SetAndNotifyCallback(AvaloniaProperty property, ref TValue backing, TValue value);
+
///
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// Used to fix #855.
///
/// The type of value with which to track the delayed assignment.
- class DeferredSetter
+ internal sealed class DeferredSetter
{
- private struct NotifyDisposable : IDisposable
+ private readonly SingleOrQueue _pendingValues;
+ private bool _isNotifying;
+
+ public DeferredSetter()
{
- private readonly SettingStatus status;
+ _pendingValues = new SingleOrQueue();
+ }
- internal NotifyDisposable(SettingStatus status)
- {
- this.status = status;
- status.Notifying = true;
- }
+ private static void SetAndRaisePropertyChanged(AvaloniaObject source, AvaloniaProperty property, ref TSetRecord backing, TSetRecord value)
+ {
+ var old = backing;
- public void Dispose()
- {
- status.Notifying = false;
- }
+ backing = value;
+
+ source.RaisePropertyChanged(property, old, value);
}
- ///
- /// Information on current setting/notification status of a property.
- ///
- private class SettingStatus
+ public bool SetAndNotify(
+ AvaloniaObject source,
+ AvaloniaProperty property,
+ ref TSetRecord backing,
+ TSetRecord value)
{
- public bool Notifying { get; set; }
-
- private SingleOrQueue pendingValues;
-
- public SingleOrQueue PendingValues
+ if (!_isNotifying)
{
- get
+ using (new NotifyDisposable(this))
{
- return pendingValues ?? (pendingValues = new SingleOrQueue());
+ SetAndRaisePropertyChanged(source, property, ref backing, value);
}
- }
- }
- private Dictionary _setRecords;
- private Dictionary SetRecords
- => _setRecords ?? (_setRecords = new Dictionary());
+ if (!_pendingValues.Empty)
+ {
+ using (new NotifyDisposable(this))
+ {
+ while (!_pendingValues.Empty)
+ {
+ SetAndRaisePropertyChanged(source, property, ref backing, _pendingValues.Dequeue());
+ }
+ }
+ }
- private SettingStatus GetOrCreateStatus(AvaloniaProperty property)
- {
- if (!SetRecords.TryGetValue(property, out var status))
- {
- status = new SettingStatus();
- SetRecords.Add(property, status);
+ return true;
}
- return status;
+ _pendingValues.Enqueue(value);
+
+ return false;
}
- ///
- /// Mark the property as currently notifying.
- ///
- /// The property to mark as notifying.
- /// Returns a disposable that when disposed, marks the property as done notifying.
- private NotifyDisposable MarkNotifying(AvaloniaProperty property)
+ public bool SetAndNotifyCallback(AvaloniaProperty property, SetAndNotifyCallback setAndNotifyCallback, ref TValue backing, TValue value)
+ where TValue : TSetRecord
{
- Contract.Requires(!IsNotifying(property));
-
- SettingStatus status = GetOrCreateStatus(property);
-
- return new NotifyDisposable(status);
- }
+ if (!_isNotifying)
+ {
+ using (new NotifyDisposable(this))
+ {
+ setAndNotifyCallback(property, ref backing, value);
+ }
- ///
- /// Check if the property is currently notifying listeners.
- ///
- /// The property.
- /// If the property is currently notifying listeners.
- private bool IsNotifying(AvaloniaProperty property)
- => SetRecords.TryGetValue(property, out var value) && value.Notifying;
+ if (!_pendingValues.Empty)
+ {
+ using (new NotifyDisposable(this))
+ {
+ while (!_pendingValues.Empty)
+ {
+ setAndNotifyCallback(property, ref backing, (TValue) _pendingValues.Dequeue());
+ }
+ }
+ }
- ///
- /// Add a pending assignment for the property.
- ///
- /// The property.
- /// The value to assign.
- private void AddPendingSet(AvaloniaProperty property, TSetRecord value)
- {
- Contract.Requires(IsNotifying(property));
+ return true;
+ }
- GetOrCreateStatus(property).PendingValues.Enqueue(value);
- }
+ _pendingValues.Enqueue(value);
- ///
- /// Checks if there are any pending assignments for the property.
- ///
- /// The property to check.
- /// If the property has any pending assignments.
- private bool HasPendingSet(AvaloniaProperty property)
- {
- return SetRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty;
+ return false;
}
///
- /// Gets the first pending assignment for the property.
+ /// Disposable that marks the property as currently notifying.
+ /// When disposed, marks the property as done notifying.
///
- /// The property to check.
- /// The first pending assignment for the property.
- private TSetRecord GetFirstPendingSet(AvaloniaProperty property)
+ private readonly struct NotifyDisposable : IDisposable
{
- return GetOrCreateStatus(property).PendingValues.Dequeue();
- }
-
- public delegate bool SetterDelegate(TSetRecord record, ref TValue backing, Action notifyCallback);
+ private readonly DeferredSetter _setter;
- ///
- /// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
- ///
- /// The property to set.
- /// The backing field for the property
- ///
- /// A callback that actually sets the property.
- /// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification.
- ///
- /// The value to try to set.
- public bool SetAndNotify(
- AvaloniaProperty property,
- ref TValue backing,
- SetterDelegate setterCallback,
- TSetRecord value)
- {
- Contract.Requires(setterCallback != null);
- if (!IsNotifying(property))
+ internal NotifyDisposable(DeferredSetter setter)
{
- bool updated = false;
- if (!object.Equals(value, backing))
- {
- updated = setterCallback(value, ref backing, notification =>
- {
- using (MarkNotifying(property))
- {
- notification();
- }
- });
- }
- while (HasPendingSet(property))
- {
- updated |= setterCallback(GetFirstPendingSet(property), ref backing, notification =>
- {
- using (MarkNotifying(property))
- {
- notification();
- }
- });
- }
-
- return updated;
+ _setter = setter;
+ _setter._isNotifying = true;
}
- else if(!object.Equals(value, backing))
+
+ public void Dispose()
{
- AddPendingSet(property, value);
+ _setter._isNotifying = false;
}
- return false;
}
}
}
diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs
index 24f85ea6b1..1bdbd4ca7c 100644
--- a/src/Avalonia.Base/ValueStore.cs
+++ b/src/Avalonia.Base/ValueStore.cs
@@ -7,21 +7,15 @@ namespace Avalonia
{
internal class ValueStore : IPriorityValueOwner
{
- private struct Entry
- {
- internal int PropertyId;
- internal object Value;
- }
-
+ private readonly AvaloniaPropertyValueStore _propertyValues;
+ private readonly AvaloniaPropertyValueStore _deferredSetters;
private readonly AvaloniaObject _owner;
- private Entry[] _entries;
public ValueStore(AvaloniaObject owner)
{
_owner = owner;
-
- // The last item in the list is always int.MaxValue
- _entries = new[] { new Entry { PropertyId = int.MaxValue, Value = null } };
+ _propertyValues = new AvaloniaPropertyValueStore();
+ _deferredSetters = new AvaloniaPropertyValueStore();
}
public IDisposable AddBinding(
@@ -31,7 +25,7 @@ namespace Avalonia
{
PriorityValue priorityValue;
- if (TryGetValue(property, out var v))
+ if (_propertyValues.TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
@@ -39,13 +33,13 @@ namespace Avalonia
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
- SetValueInternal(property, priorityValue);
+ _propertyValues.SetValue(property, priorityValue);
}
}
else
{
priorityValue = CreatePriorityValue(property);
- AddValueInternal(property, priorityValue);
+ _propertyValues.AddValue(property, priorityValue);
}
return priorityValue.Add(source, (int)priority);
@@ -55,7 +49,7 @@ namespace Avalonia
{
PriorityValue priorityValue;
- if (TryGetValue(property, out var v))
+ if (_propertyValues.TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
@@ -63,7 +57,7 @@ namespace Avalonia
{
if (priority == (int)BindingPriority.LocalValue)
{
- SetValueInternal(property, Validate(property, value));
+ _propertyValues.SetValue(property, Validate(property, value));
Changed(property, priority, v, value);
return;
}
@@ -71,7 +65,7 @@ namespace Avalonia
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
- SetValueInternal(property, priorityValue);
+ _propertyValues.SetValue(property, priorityValue);
}
}
}
@@ -84,14 +78,14 @@ namespace Avalonia
if (priority == (int)BindingPriority.LocalValue)
{
- AddValueInternal(property, Validate(property, value));
+ _propertyValues.AddValue(property, Validate(property, value));
Changed(property, priority, AvaloniaProperty.UnsetValue, value);
return;
}
else
{
priorityValue = CreatePriorityValue(property);
- AddValueInternal(property, priorityValue);
+ _propertyValues.AddValue(property, priorityValue);
}
}
@@ -110,14 +104,9 @@ namespace Avalonia
public IDictionary GetSetValues()
{
- var dict = new Dictionary(_entries.Length - 1);
- for (int i = 0; i < _entries.Length - 1; ++i)
- {
- dict.Add(AvaloniaPropertyRegistry.Instance.FindRegistered(_entries[i].PropertyId), _entries[i].Value);
- }
-
- return dict;
+ return _propertyValues.ToDictionary();
}
+
public void LogError(AvaloniaProperty property, Exception e)
{
_owner.LogBindingError(property, e);
@@ -127,7 +116,7 @@ namespace Avalonia
{
var result = AvaloniaProperty.UnsetValue;
- if (TryGetValue(property, out var value))
+ if (_propertyValues.TryGetValue(property, out var value))
{
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value;
}
@@ -137,12 +126,12 @@ namespace Avalonia
public bool IsAnimating(AvaloniaProperty property)
{
- return TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
+ return _propertyValues.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
}
public bool IsSet(AvaloniaProperty property)
{
- if (TryGetValue(property, out var value))
+ if (_propertyValues.TryGetValue(property, out var value))
{
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue;
}
@@ -152,7 +141,7 @@ namespace Avalonia
public void Revalidate(AvaloniaProperty property)
{
- if (TryGetValue(property, out var value))
+ if (_propertyValues.TryGetValue(property, out var value))
{
(value as PriorityValue)?.Revalidate();
}
@@ -189,113 +178,28 @@ namespace Avalonia
return value;
}
- private DeferredSetter _deferredSetter;
-
- public DeferredSetter Setter
+ private DeferredSetter GetDeferredSetter(AvaloniaProperty property)
{
- get
+ if (_deferredSetters.TryGetValue(property, out var deferredSetter))
{
- return _deferredSetter ??
- (_deferredSetter = new DeferredSetter());
+ return (DeferredSetter)deferredSetter;
}
- }
- private bool TryGetValue(AvaloniaProperty property, out object value)
- {
- (int index, bool found) = TryFindEntry(property.Id);
- if (!found)
- {
- value = null;
- return false;
- }
-
- value = _entries[index].Value;
- return true;
- }
-
- private void AddValueInternal(AvaloniaProperty property, object value)
- {
- Entry[] entries = new Entry[_entries.Length + 1];
-
- for (int i = 0; i < _entries.Length; ++i)
- {
- if (_entries[i].PropertyId > property.Id)
- {
- if (i > 0)
- {
- Array.Copy(_entries, 0, entries, 0, i);
- }
+ var newDeferredSetter = new DeferredSetter();
- entries[i] = new Entry { PropertyId = property.Id, Value = value };
- Array.Copy(_entries, i, entries, i + 1, _entries.Length - i);
- break;
- }
- }
+ _deferredSetters.AddValue(property, newDeferredSetter);
- _entries = entries;
+ return newDeferredSetter;
}
- private void SetValueInternal(AvaloniaProperty property, object value)
+ public DeferredSetter GetNonDirectDeferredSetter(AvaloniaProperty property)
{
- _entries[TryFindEntry(property.Id).Item1].Value = value;
+ return GetDeferredSetter(property);
}
- private (int, bool) TryFindEntry(int propertyId)
+ public DeferredSetter GetDirectDeferredSetter(AvaloniaProperty property)
{
- if (_entries.Length <= 12)
- {
- // For small lists, we use an optimized linear search. Since the last item in the list
- // is always int.MaxValue, we can skip a conditional branch in each iteration.
- // By unrolling the loop, we can skip another unconditional branch in each iteration.
-
- if (_entries[0].PropertyId >= propertyId) return (0, _entries[0].PropertyId == propertyId);
- if (_entries[1].PropertyId >= propertyId) return (1, _entries[1].PropertyId == propertyId);
- if (_entries[2].PropertyId >= propertyId) return (2, _entries[2].PropertyId == propertyId);
- if (_entries[3].PropertyId >= propertyId) return (3, _entries[3].PropertyId == propertyId);
- if (_entries[4].PropertyId >= propertyId) return (4, _entries[4].PropertyId == propertyId);
- if (_entries[5].PropertyId >= propertyId) return (5, _entries[5].PropertyId == propertyId);
- if (_entries[6].PropertyId >= propertyId) return (6, _entries[6].PropertyId == propertyId);
- if (_entries[7].PropertyId >= propertyId) return (7, _entries[7].PropertyId == propertyId);
- if (_entries[8].PropertyId >= propertyId) return (8, _entries[8].PropertyId == propertyId);
- if (_entries[9].PropertyId >= propertyId) return (9, _entries[9].PropertyId == propertyId);
- if (_entries[10].PropertyId >= propertyId) return (10, _entries[10].PropertyId == propertyId);
- }
- else
- {
- int low = 0;
- int high = _entries.Length;
- int id;
-
- while (high - low > 3)
- {
- int pivot = (high + low) / 2;
- id = _entries[pivot].PropertyId;
-
- if (propertyId == id)
- return (pivot, true);
-
- if (propertyId <= id)
- high = pivot;
- else
- low = pivot + 1;
- }
-
- do
- {
- id = _entries[low].PropertyId;
-
- if (id == propertyId)
- return (low, true);
-
- if (id > propertyId)
- break;
-
- ++low;
- }
- while (low < high);
- }
-
- return (0, false);
+ return GetDeferredSetter(property);
}
}
}
diff --git a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
index 2f1b7862a7..63e1790cce 100644
--- a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
+++ b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
@@ -307,7 +307,7 @@ namespace Avalonia.Base.UnitTests
private static Mock GetMockOwner()
{
var owner = new Mock();
- owner.SetupGet(o => o.Setter).Returns(new DeferredSetter());
+ owner.Setup(o => o.GetNonDirectDeferredSetter(It.IsAny())).Returns(new DeferredSetter());
return owner;
}
}
diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
index 6550a23b7b..f503bf66a7 100644
--- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
+++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/tests/Avalonia.Benchmarks/Base/DirectPropertyBenchmark.cs b/tests/Avalonia.Benchmarks/Base/DirectPropertyBenchmark.cs
new file mode 100644
index 0000000000..02f9397e2c
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Base/DirectPropertyBenchmark.cs
@@ -0,0 +1,65 @@
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Base
+{
+ [MemoryDiagnoser]
+ public class DirectPropertyBenchmark
+ {
+ [Benchmark(Baseline = true)]
+ public void SetAndRaiseOriginal()
+ {
+ var obj = new DirectClass();
+
+ for (var i = 0; i < 100; ++i)
+ {
+ obj.IntValue += 1;
+ }
+ }
+
+ [Benchmark]
+ public void SetAndRaiseSimple()
+ {
+ var obj = new DirectClass();
+
+ for (var i = 0; i < 100; ++i)
+ {
+ obj.IntValueSimple += 1;
+ }
+ }
+
+ class DirectClass : AvaloniaObject
+ {
+ private int _intValue;
+
+ public static readonly DirectProperty IntValueProperty =
+ AvaloniaProperty.RegisterDirect(nameof(IntValue),
+ o => o.IntValue,
+ (o, v) => o.IntValue = v);
+
+ public int IntValue
+ {
+ get => _intValue;
+ set => SetAndRaise(IntValueProperty, ref _intValue, value);
+ }
+
+ public int IntValueSimple
+ {
+ get => _intValue;
+ set
+ {
+ VerifyAccess();
+
+ if (_intValue == value)
+ {
+ return;
+ }
+
+ var old = _intValue;
+ _intValue = value;
+
+ RaisePropertyChanged(IntValueProperty, old, _intValue);
+ }
+ }
+ }
+ }
+}
diff --git a/tests/Avalonia.Benchmarks/Base/Properties.cs b/tests/Avalonia.Benchmarks/Base/Properties.cs
index 0a020961d5..45fc68ac96 100644
--- a/tests/Avalonia.Benchmarks/Base/Properties.cs
+++ b/tests/Avalonia.Benchmarks/Base/Properties.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Reactive.Subjects;
+using System.Reactive.Subjects;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Base