Browse Source

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

* Extract EffectiveValue notification methods to reduce code size

* Extract non-generic members from frequently used generic types
release/11.2.0-beta2
Julien Lebosquain 1 year ago
committed by GitHub
parent
commit
e3c64189a4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 47
      src/Avalonia.Base/AvaloniaObject.cs
  2. 22
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  3. 8
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  4. 14
      src/Avalonia.Base/PropertyStore/BindingEntryBaseNonGenericHelper.cs
  5. 29
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  6. 29
      src/Avalonia.Base/PropertyStore/PropertyNotifying.cs
  7. 4
      src/Avalonia.Base/Reactive/AnonymousObserver.cs
  8. 13
      src/Avalonia.Base/Reactive/AnonymousObserverNonGenericHelper.cs
  9. 44
      src/Avalonia.Base/StyledProperty.cs
  10. 30
      src/Avalonia.Base/StyledPropertyNonGenericHelper.cs
  11. 2
      src/Avalonia.Base/Threading/Dispatcher.cs

47
src/Avalonia.Base/AvaloniaObject.cs

@ -458,6 +458,17 @@ namespace Avalonia
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();
@ -466,18 +477,20 @@ namespace Avalonia
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>
@ -539,10 +552,22 @@ namespace Avalonia
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)
{
@ -552,13 +577,11 @@ namespace Avalonia
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>
@ -638,7 +661,7 @@ namespace Avalonia
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);
}

22
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -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
{
@ -259,7 +257,12 @@ namespace Avalonia
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()}");
}
@ -331,7 +334,14 @@ namespace Avalonia
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;
}
@ -345,7 +355,7 @@ namespace Avalonia
if (p == property)
{
return (DirectPropertyBase<T>)p;
return p;
}
}

8
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@ -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
{
@ -11,8 +11,6 @@ namespace Avalonia.PropertyStore
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;
@ -119,7 +117,7 @@ namespace Avalonia.PropertyStore
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),
@ -153,7 +151,7 @@ namespace Avalonia.PropertyStore
{
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);
}
}

14
src/Avalonia.Base/PropertyStore/BindingEntryBaseNonGenericHelper.cs

@ -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
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -192,7 +192,7 @@ namespace Avalonia.PropertyStore
}
protected override object? GetBoxedValue() => Value;
private static T GetValue(IValueEntry entry)
{
if (entry is IValueEntry<T> typed)
@ -240,14 +240,11 @@ namespace Avalonia.PropertyStore
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);
}
}
@ -296,19 +293,27 @@ namespace Avalonia.PropertyStore
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;

29
src/Avalonia.Base/PropertyStore/PropertyNotifying.cs

@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
namespace Avalonia.PropertyStore
{
@ -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
src/Avalonia.Base/Reactive/AnonymousObserver.cs

@ -1,6 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using static Avalonia.Reactive.AnonymousObserverNonGenericHelper;
namespace Avalonia.Reactive;
@ -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;

13
src/Avalonia.Base/Reactive/AnonymousObserverNonGenericHelper.cs

@ -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 = () => { };
}

44
src/Avalonia.Base/StyledProperty.cs

@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
using Avalonia.Data;
using Avalonia.PropertyStore;
using Avalonia.Utilities;
using static Avalonia.StyledPropertyNonGenericHelper;
namespace Avalonia
{
@ -44,9 +45,7 @@ namespace Avalonia
if (validate?.Invoke(metadata.DefaultValue) == false)
{
throw new ArgumentException(
$"'{metadata.DefaultValue}' is not a valid default value for '{name}'.",
nameof(metadata));
ThrowInvalidDefaultValue(name, metadata.DefaultValue, name);
}
_singleDefaultValue = metadata.DefaultValue;
@ -175,8 +174,7 @@ namespace Avalonia
{
if (!ValidateValue(metadata.DefaultValue))
{
throw new ArgumentException(
$"'{metadata.DefaultValue}' is not a valid default value for '{Name}'.");
ThrowInvalidDefaultValue(Name, metadata.DefaultValue, nameof(metadata));
}
}
@ -273,27 +271,25 @@ namespace Avalonia
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConversionSupressWarningMessage)]
private bool ShouldSetValue(AvaloniaObject target, object? value, [NotNullWhen(true)] out TValue? converted)
{
if (value == BindingOperations.DoNothing)
if (value != BindingOperations.DoNothing)
{
converted = default;
return false;
}
if (value == UnsetValue)
{
target.ClearValue(this);
converted = default;
return false;
}
else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var v))
{
converted = (TValue)v!;
return true;
}
else
{
var type = value?.GetType().FullName ?? "(null)";
throw new ArgumentException($"Invalid value for Property '{Name}': '{value}' ({type})");
if (value == UnsetValue)
{
target.ClearValue(this);
}
else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var v))
{
converted = (TValue)v!;
return true;
}
else
{
ThrowInvalidValue(Name, value, nameof(value));
}
}
converted = default;
return false;
}
}
}

30
src/Avalonia.Base/StyledPropertyNonGenericHelper.cs

@ -0,0 +1,30 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Avalonia;
/// <summary>
/// Contains methods for <see cref="StyledProperty{TValue}"/> that aren't using generic arguments.
/// Separated to avoid unnecessary generic instantiations.
/// </summary>
internal static class StyledPropertyNonGenericHelper
{
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowInvalidValue(string propertyName, object? value, string paramName)
{
var type = value?.GetType().FullName ?? "(null)";
throw new ArgumentException(
$"Invalid value for Property '{propertyName}': '{value}' ({type})",
paramName);
}
public static void ThrowInvalidDefaultValue(string propertyName, object? defaultValue, string paramName)
{
throw new ArgumentException(
$"'{defaultValue}' is not a valid default value for '{propertyName}'.",
paramName);
}
}

2
src/Avalonia.Base/Threading/Dispatcher.cs

@ -64,7 +64,7 @@ public partial class Dispatcher : IDispatcher
/// <summary>
/// Checks that the current thread is the UI thread.
/// </summary>
public bool CheckAccess() => _impl?.CurrentThreadIsLoopThread ?? true;
public bool CheckAccess() => _impl.CurrentThreadIsLoopThread;
/// <summary>
/// Checks that the current thread is the UI thread and throws if not.

Loading…
Cancel
Save