Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract non-generic members from frequently used generic types #16585

Merged
merged 2 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading