Browse Source

Made AvaloniaPropertyMetadata immutable after property initialization (#15384)

* Made AvaloniaPropertyMetadata immutable after property initialization

* Removed redundant throw
release/11.1.0-beta2
Julien Lebosquain 2 years ago
committed by Max Katz
parent
commit
a956e479e9
  1. 13
      src/Avalonia.Base/AvaloniaProperty.cs
  2. 14
      src/Avalonia.Base/AvaloniaPropertyMetadata.cs
  3. 1
      src/Avalonia.Base/DirectProperty.cs
  4. 8
      src/Avalonia.Base/DirectPropertyMetadata`1.cs
  5. 8
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  6. 21
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  7. 20
      tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
  8. 28
      tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs

13
src/Avalonia.Base/AvaloniaProperty.cs

@ -50,7 +50,10 @@ namespace Avalonia
AvaloniaPropertyMetadata metadata, AvaloniaPropertyMetadata metadata,
Action<AvaloniaObject, bool>? notifying = null) Action<AvaloniaObject, bool>? notifying = null)
{ {
_ = name ?? throw new ArgumentNullException(nameof(name)); ThrowHelper.ThrowIfNull(name, nameof(name));
ThrowHelper.ThrowIfNull(valueType, nameof(valueType));
ThrowHelper.ThrowIfNull(ownerType, nameof(ownerType));
ThrowHelper.ThrowIfNull(metadata, nameof(metadata));
if (name.Contains('.')) if (name.Contains('.'))
{ {
@ -60,12 +63,13 @@ namespace Avalonia
_metadata = new Dictionary<Type, AvaloniaPropertyMetadata>(); _metadata = new Dictionary<Type, AvaloniaPropertyMetadata>();
Name = name; Name = name;
PropertyType = valueType ?? throw new ArgumentNullException(nameof(valueType)); PropertyType = valueType;
OwnerType = ownerType ?? throw new ArgumentNullException(nameof(ownerType)); OwnerType = ownerType;
Notifying = notifying; Notifying = notifying;
Id = s_nextId++; Id = s_nextId++;
_metadata.Add(hostType, metadata ?? throw new ArgumentNullException(nameof(metadata))); metadata.Freeze();
_metadata.Add(hostType, metadata);
_defaultMetadata = metadata.GenerateTypeSafeMetadata(); _defaultMetadata = metadata.GenerateTypeSafeMetadata();
_singleMetadata = new(hostType, metadata); _singleMetadata = new(hostType, metadata);
} }
@ -584,6 +588,7 @@ namespace Avalonia
var baseMetadata = GetMetadata(type); var baseMetadata = GetMetadata(type);
metadata.Merge(baseMetadata, this); metadata.Merge(baseMetadata, this);
metadata.Freeze();
_metadata.Add(type, metadata); _metadata.Add(type, metadata);
_metadataCache.Clear(); _metadataCache.Clear();

14
src/Avalonia.Base/AvaloniaPropertyMetadata.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Data; using Avalonia.Data;
namespace Avalonia namespace Avalonia
@ -7,6 +8,7 @@ namespace Avalonia
/// </summary> /// </summary>
public abstract class AvaloniaPropertyMetadata public abstract class AvaloniaPropertyMetadata
{ {
private bool _isReadOnly;
private BindingMode _defaultBindingMode; private BindingMode _defaultBindingMode;
/// <summary> /// <summary>
@ -54,6 +56,11 @@ namespace Avalonia
AvaloniaPropertyMetadata baseMetadata, AvaloniaPropertyMetadata baseMetadata,
AvaloniaProperty property) AvaloniaProperty property)
{ {
if (_isReadOnly)
{
throw new InvalidOperationException("The metadata is read-only.");
}
if (_defaultBindingMode == BindingMode.Default) if (_defaultBindingMode == BindingMode.Default)
{ {
_defaultBindingMode = baseMetadata.DefaultBindingMode; _defaultBindingMode = baseMetadata.DefaultBindingMode;
@ -62,6 +69,13 @@ namespace Avalonia
EnableDataValidation ??= baseMetadata.EnableDataValidation; EnableDataValidation ??= baseMetadata.EnableDataValidation;
} }
/// <summary>
/// Makes this instance read-only.
/// No further modifications are allowed after this call.
/// </summary>
public void Freeze()
=> _isReadOnly = true;
/// <summary> /// <summary>
/// Gets a copy of this object configured for use with any owner type. /// Gets a copy of this object configured for use with any owner type.
/// </summary> /// </summary>

1
src/Avalonia.Base/DirectProperty.cs

@ -94,6 +94,7 @@ namespace Avalonia
enableDataValidation: enableDataValidation); enableDataValidation: enableDataValidation);
metadata.Merge(GetMetadata<TOwner>(), this); metadata.Merge(GetMetadata<TOwner>(), this);
metadata.Freeze();
var result = new DirectProperty<TNewOwner, TValue>( var result = new DirectProperty<TNewOwner, TValue>(
(DirectPropertyBase<TValue>)this, (DirectPropertyBase<TValue>)this,

8
src/Avalonia.Base/DirectPropertyMetadata`1.cs

@ -46,6 +46,12 @@ namespace Avalonia
} }
} }
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() => new DirectPropertyMetadata<TValue>(UnsetValue, DefaultBindingMode, EnableDataValidation); /// <inheritdoc />
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata()
{
var copy = new DirectPropertyMetadata<TValue>(UnsetValue, DefaultBindingMode, EnableDataValidation);
copy.Freeze();
return copy;
}
} }
} }

8
src/Avalonia.Base/StyledPropertyMetadata`1.cs

@ -59,6 +59,12 @@ namespace Avalonia
} }
} }
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata() => new StyledPropertyMetadata<TValue>(DefaultValue, DefaultBindingMode, enableDataValidation: EnableDataValidation ?? false); /// <inheritdoc />
public override AvaloniaPropertyMetadata GenerateTypeSafeMetadata()
{
var copy = new StyledPropertyMetadata<TValue>(DefaultValue, DefaultBindingMode, null, EnableDataValidation ?? false);
copy.Freeze();
return copy;
}
} }
} }

21
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@ -82,6 +82,27 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(BindingMode.TwoWay, result.DefaultBindingMode); Assert.Equal(BindingMode.TwoWay, result.DefaultBindingMode);
} }
[Fact]
public void Default_Metadata_Cannot_Be_Changed_After_Property_Initialization()
{
var metadata = new TestMetadata();
var property = new TestProperty<string>("test", typeof(Class1), metadata);
Assert.Throws<InvalidOperationException>(() => metadata.Merge(new TestMetadata(), property));
}
[Fact]
public void Overridden_Metadata_Cannot_Be_Changed_After_OverrideMetadata()
{
var metadata = new TestMetadata(BindingMode.TwoWay);
var overridden = new TestMetadata();
var property = new TestProperty<string>("test", typeof(Class1), metadata);
property.OverrideMetadata<Class2>(overridden);
Assert.Throws<InvalidOperationException>(() => overridden.Merge(new TestMetadata(), property));
}
[Fact] [Fact]
public void Changed_Observable_Fired() public void Changed_Observable_Fired()
{ {

20
tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs

@ -1,3 +1,4 @@
using System;
using Xunit; using Xunit;
namespace Avalonia.Base.UnitTests namespace Avalonia.Base.UnitTests
@ -46,6 +47,25 @@ namespace Avalonia.Base.UnitTests
Assert.Same(p1.Changed, p2.Changed); Assert.Same(p1.Changed, p2.Changed);
} }
[Fact]
public void Default_GetMetadata_Cannot_Be_Changed()
{
var p1 = Class1.FooProperty;
var metadata = p1.GetMetadata<Class1>();
Assert.Throws<InvalidOperationException>(() => metadata.Merge(new DirectPropertyMetadata<string>(), p1));
}
[Fact]
public void AddOwnered_GetMetadata_Cannot_Be_Changed()
{
var p1 = Class1.FooProperty;
var p2 = p1.AddOwner<Class2>(_ => null, (_, _) => { });
var metadata = p2.GetMetadata<Class2>();
Assert.Throws<InvalidOperationException>(() => metadata.Merge(new DirectPropertyMetadata<string>(), p2));
}
private class Class1 : AvaloniaObject private class Class1 : AvaloniaObject
{ {
public static readonly DirectProperty<Class1, string> FooProperty = public static readonly DirectProperty<Class1, string> FooProperty =

28
tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs

@ -1,3 +1,4 @@
using System;
using Xunit; using Xunit;
namespace Avalonia.Base.UnitTests namespace Avalonia.Base.UnitTests
@ -32,6 +33,33 @@ namespace Avalonia.Base.UnitTests
Assert.Same(p1, p2); Assert.Same(p1, p2);
} }
[Fact]
public void Default_GetMetadata_Cannot_Be_Changed()
{
var p1 = new StyledProperty<string>(
"p1",
typeof(Class1),
typeof(Class1),
new StyledPropertyMetadata<string>());
var metadata = p1.GetMetadata<Class1>();
Assert.Throws<InvalidOperationException>(() => metadata.Merge(new StyledPropertyMetadata<string>(), p1));
}
[Fact]
public void AddOwnered_GetMetadata_Cannot_Be_Changed()
{
var p1 = new StyledProperty<string>(
"p1",
typeof(Class1),
typeof(Class1),
new StyledPropertyMetadata<string>());
var p2 = p1.AddOwner<Class2>();
var metadata = p2.GetMetadata<Class2>();
Assert.Throws<InvalidOperationException>(() => metadata.Merge(new StyledPropertyMetadata<string>(), p2));
}
private class Class1 : AvaloniaObject private class Class1 : AvaloniaObject
{ {
} }

Loading…
Cancel
Save