Skip to content

Commit

Permalink
Extract non-generic members from frequently used generic types (#16585)
Browse files Browse the repository at this point in the history
* Extract EffectiveValue notification methods to reduce code size

* Extract non-generic members from frequently used generic types
  • Loading branch information
MrJul authored Sep 14, 2024
1 parent 20f77a3 commit e3c6418
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 77 deletions.
47 changes: 35 additions & 12 deletions src/Avalonia.Base/AvaloniaObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,17 @@ public IDisposable Bind<T>(
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue)
{
return TryBindStyledPropertyUntyped(property, source, priority)
?? _values.AddBinding(property, source, priority);
}

// Non-generic path extracted to avoid unnecessary generic code duplication
private BindingExpressionBase? TryBindStyledPropertyUntyped(
AvaloniaProperty property,
IObservable<object?> source,
BindingPriority priority)
{
Debug.Assert(!property.IsDirect);
ThrowHelper.ThrowIfNull(property, nameof(property));
ThrowHelper.ThrowIfNull(source, nameof(source));
VerifyAccess();
Expand All @@ -466,18 +477,20 @@ public IDisposable Bind<T>(
if (source is IBinding2 b)
{
if (b.Instance(this, property, null) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException("Binding returned unsupported IBindingExpression.");
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");

if (priority != expression.Priority)
{
throw new NotSupportedException(
$"The binding priority passed to AvaloniaObject.Bind ('{priority}') " +
"conflicts with the binding priority of the provided binding expression " +
$" ({expression.Priority}').");
}

return GetValueStore().AddBinding(property, expression);
}
else
{
return _values.AddBinding(property, source, priority);
}

return null;
}

/// <summary>
Expand Down Expand Up @@ -539,10 +552,22 @@ public IDisposable Bind<T>(
DirectPropertyBase<T> property,
IObservable<object?> source)
{
AvaloniaProperty untypedProperty = property;

return TryBindDirectPropertyUntyped(ref untypedProperty, source)
?? _values.AddBinding((DirectPropertyBase<T>)untypedProperty, source);
}

// Non-generic path extracted to avoid unnecessary generic code duplication
private BindingExpressionBase? TryBindDirectPropertyUntyped(
ref AvaloniaProperty property,
IObservable<object?> source)
{
Debug.Assert(property.IsDirect);
ThrowHelper.ThrowIfNull(property, nameof(property));
VerifyAccess();

property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirectUntyped(this, property);

if (property.IsReadOnly)
{
Expand All @@ -552,13 +577,11 @@ public IDisposable Bind<T>(
if (source is IBinding2 b)
{
if (b.Instance(this, property, null) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException("Binding returned unsupported IBindingExpression.");
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");
return GetValueStore().AddBinding(property, expression);
}
else
{
return _values.AddBinding(property, source);
}

return null;
}

/// <summary>
Expand Down Expand Up @@ -638,7 +661,7 @@ internal BindingExpressionBase Bind(AvaloniaProperty property, IBinding binding,
if (binding is not IBinding2 b)
throw new NotSupportedException($"Unsupported IBinding implementation '{binding}'.");
if (b.Instance(this, property, anchor) is not UntypedBindingExpressionBase expression)
throw new NotSupportedException("Binding returned unsupported IBindingExpression.");
throw new NotSupportedException($"Binding returned unsupported {nameof(BindingExpressionBase)}.");

return GetValueStore().AddBinding(property, expression);
}
Expand Down
22 changes: 16 additions & 6 deletions src/Avalonia.Base/AvaloniaPropertyRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Avalonia
{
Expand Down Expand Up @@ -259,7 +257,12 @@ public DirectPropertyBase<T> GetRegisteredDirect<T>(
AvaloniaObject o,
DirectPropertyBase<T> property)
{
return FindRegisteredDirect(o, property) ??
return (DirectPropertyBase<T>)GetRegisteredDirectUntyped(o, property);
}

internal AvaloniaProperty GetRegisteredDirectUntyped(AvaloniaObject o, AvaloniaProperty property)
{
return FindRegisteredDirectUntyped(o, property) ??
throw new ArgumentException($"Property '{property.Name} not registered on '{o.GetType()}");
}

Expand Down Expand Up @@ -331,7 +334,14 @@ public DirectPropertyBase<T> GetRegisteredDirect<T>(
AvaloniaObject o,
DirectPropertyBase<T> property)
{
if (property.Owner == o.GetType())
return (DirectPropertyBase<T>?)FindRegisteredDirectUntyped(o, property);
}

private AvaloniaProperty? FindRegisteredDirectUntyped(AvaloniaObject o, AvaloniaProperty property)
{
Debug.Assert(property.IsDirect);

if (property.OwnerType == o.GetType())
{
return property;
}
Expand All @@ -345,7 +355,7 @@ public DirectPropertyBase<T> GetRegisteredDirect<T>(

if (p == property)
{
return (DirectPropertyBase<T>)p;
return p;
}
}

Expand Down
8 changes: 3 additions & 5 deletions src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using Avalonia.Reactive;
using Avalonia.Data;
using Avalonia.Threading;
using static Avalonia.PropertyStore.BindingEntryBaseNonGenericHelper;

namespace Avalonia.PropertyStore
{
Expand All @@ -11,8 +11,6 @@ internal abstract class BindingEntryBase<TValue, TSource> : IValueEntry<TValue>,
IObserver<BindingValue<TSource>>,
IDisposable
{
private static readonly IDisposable s_creating = Disposable.Empty;
private static readonly IDisposable s_creatingQuiet = Disposable.Create(() => { });
private IDisposable? _subscription;
private bool _hasValue;
private TValue? _value;
Expand Down Expand Up @@ -119,7 +117,7 @@ protected virtual void Start(bool produceValue)
if (_subscription is not null)
return;

_subscription = produceValue ? s_creating : s_creatingQuiet;
_subscription = produceValue ? Creating : CreatingQuiet;
_subscription = Source switch
{
IObservable<BindingValue<TSource>> bv => bv.Subscribe(this),
Expand Down Expand Up @@ -153,7 +151,7 @@ static void Execute(BindingEntryBase<TValue, TSource> instance, BindingValue<TVa
{
instance._value = value.Value;
instance._hasValue = true;
if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
if (instance._subscription is not null && instance._subscription != CreatingQuiet)
instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using Avalonia.Reactive;

namespace Avalonia.PropertyStore;

/// <summary>
/// Contains fields for <see cref="BindingEntryBase{TValue,TSource}"/> that aren't using generic arguments.
/// Separated to avoid unnecessary generic instantiations.
/// </summary>
internal static class BindingEntryBaseNonGenericHelper
{
public static readonly IDisposable Creating = Disposable.Empty;
public static readonly IDisposable CreatingQuiet = Disposable.Create(() => { });
}
29 changes: 17 additions & 12 deletions src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ protected override void CoerceDefaultValueAndRaise(ValueStore owner, AvaloniaPro
}

protected override object? GetBoxedValue() => Value;

private static T GetValue(IValueEntry entry)
{
if (entry is IValueEntry<T> typed)
Expand Down Expand Up @@ -240,14 +240,11 @@ private void SetAndRaiseCore(

if (valueChanged)
{
using var notifying = PropertyNotifying.Start(owner.Owner, property);
owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueChanged(property, oldValue, this);
NotifyValueChanged(owner, property, oldValue);
}
else if (baseValueChanged)
{
owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false);
NotifyBaseValueChanged(owner, property);
}
}

Expand Down Expand Up @@ -296,19 +293,27 @@ private void SetAndRaiseCore(

if (valueChanged)
{
using var notifying = PropertyNotifying.Start(owner.Owner, property);
owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueChanged(property, oldValue, this);
NotifyValueChanged(owner, property, oldValue);
}

if (baseValueChanged)
{
owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false);
NotifyBaseValueChanged(owner, property);
}
}

private class UncommonFields
private void NotifyValueChanged(ValueStore owner, StyledProperty<T> property, T oldValue)
{
using var notifying = PropertyNotifying.Start(owner.Owner, property);
owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueChanged(property, oldValue, this);
}

private void NotifyBaseValueChanged(ValueStore owner, StyledProperty<T> property)
=> owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false);

private sealed class UncommonFields
{
public Func<AvaloniaObject, T, T>? _coerce;
public T? _uncoercedValue;
Expand Down
29 changes: 15 additions & 14 deletions src/Avalonia.Base/PropertyStore/PropertyNotifying.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;

namespace Avalonia.PropertyStore
{
Expand All @@ -10,26 +9,28 @@ namespace Avalonia.PropertyStore
/// Uses the disposable pattern to ensure that the closing Notifying call is made even in the
/// presence of exceptions.
/// </remarks>
internal readonly struct PropertyNotifying : IDisposable
internal struct PropertyNotifying : IDisposable
{
private readonly AvaloniaObject _owner;
private readonly AvaloniaProperty _property;
private readonly AvaloniaObject? _owner;
private Action<AvaloniaObject, bool>? _notifying;

private PropertyNotifying(AvaloniaObject owner, AvaloniaProperty property)
private PropertyNotifying(AvaloniaObject owner, Action<AvaloniaObject, bool>? notifying)
{
Debug.Assert(property.Notifying is not null);
_owner = owner;
_property = property;
_property.Notifying!(owner, true);
_notifying = notifying;
notifying?.Invoke(owner, true);
}

public void Dispose() => _property.Notifying!(_owner, false);

public static PropertyNotifying? Start(AvaloniaObject owner, AvaloniaProperty property)
public void Dispose()
{
if (property.Notifying is null)
return null;
return new PropertyNotifying(owner, property);
if (_notifying is null)
return;

_notifying(_owner!, false);
_notifying = null;
}

public static PropertyNotifying Start(AvaloniaObject owner, AvaloniaProperty property)
=> new(owner, property.Notifying);
}
}
4 changes: 1 addition & 3 deletions src/Avalonia.Base/Reactive/AnonymousObserver.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using static Avalonia.Reactive.AnonymousObserverNonGenericHelper;

namespace Avalonia.Reactive;

Expand All @@ -10,8 +10,6 @@ namespace Avalonia.Reactive;
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
public class AnonymousObserver<T> : IObserver<T>
{
private static readonly Action<Exception> ThrowsOnError = ex => throw ex;
private static readonly Action NoOpCompleted = () => { };
private readonly Action<T> _onNext;
private readonly Action<Exception> _onError;
private readonly Action _onCompleted;
Expand Down
13 changes: 13 additions & 0 deletions src/Avalonia.Base/Reactive/AnonymousObserverNonGenericHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Avalonia.Reactive;

/// <summary>
/// Contains fields for <see cref="AnonymousObserver{T}"/> that aren't using generic arguments.
/// Separated to avoid unnecessary generic instantiations.
/// </summary>
internal static class AnonymousObserverNonGenericHelper
{
public static readonly Action<Exception> ThrowsOnError = ex => throw ex;
public static readonly Action NoOpCompleted = () => { };
}
Loading

0 comments on commit e3c6418

Please sign in to comment.