diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index cc0bba6d99..5aca179d24 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -25,8 +25,8 @@
$(NoWarn);CS1591;CS1573
true
true
- netstandard2.1;net6.0;net472
- netstandard2.1;net6.0
+ net8.0;net6.0;netstandard2.1;net472
+ net8.0;net6.0;netstandard2.1
true
false
diff --git a/src/Framework/Framework/Binding/ActiveDotvvmProperty.cs b/src/Framework/Framework/Binding/ActiveDotvvmProperty.cs
index 831b85d787..7d87b9982e 100644
--- a/src/Framework/Framework/Binding/ActiveDotvvmProperty.cs
+++ b/src/Framework/Framework/Binding/ActiveDotvvmProperty.cs
@@ -13,6 +13,10 @@ namespace DotVVM.Framework.Binding
/// An abstract DotvvmProperty which contains code to be executed when the assigned control is being rendered.
public abstract class ActiveDotvvmProperty : DotvvmProperty
{
+ internal ActiveDotvvmProperty(string name, Type declaringType, bool isValueInherited) : base(name, declaringType, isValueInherited)
+ {
+ }
+
public abstract void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context, DotvvmControl control);
diff --git a/src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs b/src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs
index b982ee6569..971ca845d8 100644
--- a/src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs
+++ b/src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs
@@ -11,7 +11,7 @@ namespace DotVVM.Framework.Binding
///
public class CompileTimeOnlyDotvvmProperty : DotvvmProperty
{
- public CompileTimeOnlyDotvvmProperty()
+ private CompileTimeOnlyDotvvmProperty(string name, Type declaringType) : base(name, declaringType, isValueInherited: false)
{
}
@@ -37,7 +37,7 @@ public override bool IsSet(DotvvmBindableObject control, bool inherit = true)
///
public static CompileTimeOnlyDotvvmProperty Register(string propertyName)
{
- var property = new CompileTimeOnlyDotvvmProperty();
+ var property = new CompileTimeOnlyDotvvmProperty(propertyName, typeof(TDeclaringType));
return (CompileTimeOnlyDotvvmProperty)Register(propertyName, property: property);
}
}
diff --git a/src/Framework/Framework/Binding/DelegateActionProperty.cs b/src/Framework/Framework/Binding/DelegateActionProperty.cs
index 81c1e23c86..3bb96daef1 100644
--- a/src/Framework/Framework/Binding/DelegateActionProperty.cs
+++ b/src/Framework/Framework/Binding/DelegateActionProperty.cs
@@ -13,9 +13,9 @@ namespace DotVVM.Framework.Binding
/// DotvvmProperty which calls the function passed in the Register method, when the assigned control is being rendered.
public sealed class DelegateActionProperty: ActiveDotvvmProperty
{
- private Action func;
+ private readonly Action func;
- public DelegateActionProperty(Action func)
+ public DelegateActionProperty(Action func, string name, Type declaringType, bool isValueInherited) : base(name, declaringType, isValueInherited)
{
this.func = func;
}
@@ -27,7 +27,8 @@ public override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestCon
public static DelegateActionProperty Register(string name, Action func, [AllowNull] TValue defaultValue = default(TValue))
{
- return (DelegateActionProperty)DotvvmProperty.Register(name, defaultValue, false, new DelegateActionProperty(func));
+ var property = new DelegateActionProperty(func, name, typeof(TDeclaringType), isValueInherited: false);
+ return (DelegateActionProperty)DotvvmProperty.Register(name, defaultValue, false, property);
}
}
diff --git a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs
index a0b7500aad..6c3d6c737b 100644
--- a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs
+++ b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs
@@ -105,8 +105,10 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyG
valueParameter
)
);
-
}
+
+ static readonly ConstructorInfo DotvvmPropertyIdConstructor = typeof(DotvvmPropertyId).GetConstructor(new [] { typeof(uint) }).NotNull();
+
public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyAccessors(Type type, DotvvmProperty property)
{
if (property is DotvvmPropertyAlias propertyAlias)
@@ -114,27 +116,31 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
// if the property does not override GetValue/SetValue, we'll use
// control.properties dictionary directly to avoid virtual method calls
- var canUseDirectAccess =
- !property.IsValueInherited && (
- property.GetType() == typeof(DotvvmProperty) ||
- property.GetType().GetMethod(nameof(DotvvmProperty.GetValue), new [] { typeof(DotvvmBindableObject), typeof(bool) })!.DeclaringType == typeof(DotvvmProperty) &&
- property.GetType().GetMethod(nameof(DotvvmProperty.SetValue), new [] { typeof(DotvvmBindableObject), typeof(object) })!.DeclaringType == typeof(DotvvmProperty));
+ var canUseDirectAccess = !property.IsValueInherited && DotvvmPropertyIdAssignment.TypeCanUseAnyDirectAccess(property.GetType());
var valueParameter = Expression.Parameter(type, "value");
var unwrappedType = type.UnwrapNullableType();
+ var defaultObj = TypeConversion.BoxToObject(Constant(property.DefaultValue));
+ // try to access the readonly static field, as .NET can optimize that better than whatever Linq.Expression Constant compiles to
+ var propertyExpr =
+ property.AttributeProvider is FieldInfo field && field.IsStatic && field.IsInitOnly && field.GetValue(null) == property
+ ? Field(null, field)
+ : (Expression)Constant(property);
+ var propertyIdExpr = New(DotvvmPropertyIdConstructor, Constant(property.Id.Id, typeof(uint)));
+
var boxedValueParameter = TypeConversion.BoxToObject(valueParameter);
var setValueRaw =
canUseDirectAccess
- ? Call(typeof(Helpers), nameof(Helpers.SetValueDirect), Type.EmptyTypes, currentControlParameter, Constant(property), boxedValueParameter)
- : Call(currentControlParameter, nameof(DotvvmBindableObject.SetValueRaw), Type.EmptyTypes, Constant(property), boxedValueParameter);
+ ? Call(typeof(Helpers), nameof(Helpers.SetValueDirect), Type.EmptyTypes, currentControlParameter, propertyIdExpr, defaultObj, boxedValueParameter)
+ : Call(currentControlParameter, nameof(DotvvmBindableObject.SetValueRaw), Type.EmptyTypes, propertyExpr, boxedValueParameter);
if (typeof(IBinding).IsAssignableFrom(type))
{
var getValueRaw =
canUseDirectAccess
- ? Call(typeof(Helpers), nameof(Helpers.GetValueRawDirect), Type.EmptyTypes, currentControlParameter, Constant(property))
- : Call(currentControlParameter, nameof(DotvvmBindableObject.GetValueRaw), Type.EmptyTypes, Constant(property), Constant(property.IsValueInherited));
+ ? Call(typeof(Helpers), nameof(Helpers.GetValueRawDirect), Type.EmptyTypes, currentControlParameter, propertyIdExpr, defaultObj)
+ : Call(currentControlParameter, nameof(DotvvmBindableObject.GetValueRaw), Type.EmptyTypes, propertyExpr, Constant(property.IsValueInherited));
return (
Lambda(
Convert(getValueRaw, type),
@@ -173,11 +179,17 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
Expression.Call(
getValueOrBindingMethod,
currentControlParameter,
- Constant(property)),
+ canUseDirectAccess ? propertyIdExpr : propertyExpr,
+ defaultObj),
currentControlParameter
),
Expression.Lambda(
- Expression.Call(setValueOrBindingMethod, currentControlParameter, Expression.Constant(property), valueParameter),
+ Expression.Call(
+ setValueOrBindingMethod,
+ currentControlParameter,
+ canUseDirectAccess ? propertyIdExpr : propertyExpr,
+ defaultObj,
+ valueParameter),
currentControlParameter, valueParameter
)
);
@@ -191,13 +203,13 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
Expression getValue;
if (canUseDirectAccess && unwrappedType.IsValueType)
{
- getValue = Call(typeof(Helpers), nameof(Helpers.GetStructValueDirect), new Type[] { unwrappedType }, currentControlParameter, Constant(property));
+ getValue = Call(typeof(Helpers), nameof(Helpers.GetStructValueDirect), [ unwrappedType ], currentControlParameter, propertyIdExpr, Constant(property.DefaultValue, type.MakeNullableType()));
if (!type.IsNullable())
getValue = Expression.Property(getValue, "Value");
}
else
{
- getValue = Call(currentControlParameter, getValueMethod, Constant(property), Constant(property.IsValueInherited));
+ getValue = Call(currentControlParameter, getValueMethod, propertyExpr, Constant(property.IsValueInherited));
getValue = Convert(getValue, type);
}
return (
diff --git a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs
index a276c1b989..0375742a28 100644
--- a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs
+++ b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs
@@ -12,7 +12,7 @@ public partial class DotvvmCapabilityProperty
{
internal static class Helpers
{
- public static ValueOrBinding? GetOptionalValueOrBinding(DotvvmBindableObject c, DotvvmProperty p)
+ public static ValueOrBinding? GetOptionalValueOrBinding(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue)
{
if (c.properties.TryGet(p, out var x))
{
@@ -25,13 +25,13 @@ internal static class Helpers
}
else return null;
}
- public static ValueOrBinding GetValueOrBinding(DotvvmBindableObject c, DotvvmProperty p)
+ public static ValueOrBinding GetValueOrBinding(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue)
{
if (!c.properties.TryGet(p, out var x))
- x = p.DefaultValue;
+ x = defaultValue;
return ValueOrBinding.FromBoxedValue(x);
}
- public static ValueOrBinding? GetOptionalValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p)
+ public static ValueOrBinding? GetOptionalValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue)
{
if (c.IsPropertySet(p))
{
@@ -45,42 +45,42 @@ public static ValueOrBinding GetValueOrBinding(DotvvmBindableObject c, Dot
}
else return null;
}
- public static ValueOrBinding GetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p)
+ public static ValueOrBinding GetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue)
{
return ValueOrBinding.FromBoxedValue(c.GetValue(p));
}
- public static void SetOptionalValueOrBinding(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding? val)
+ public static void SetOptionalValueOrBinding(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, ValueOrBinding? val)
{
if (val.HasValue)
{
- SetValueOrBinding(c, p, val.GetValueOrDefault());
+ SetValueOrBinding(c, p, defaultValue, val.GetValueOrDefault());
}
else
{
c.properties.Remove(p);
}
}
- public static void SetValueOrBinding(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding val)
+ public static void SetValueOrBinding(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, ValueOrBinding val)
{
var boxedVal = val.UnwrapToObject();
- SetValueDirect(c, p, boxedVal);
+ SetValueDirect(c, p, defaultValue, boxedVal);
}
- public static void SetOptionalValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding? val)
+ public static void SetOptionalValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue, ValueOrBinding? val)
{
if (val.HasValue)
{
- SetValueOrBindingSlow(c, p, val.GetValueOrDefault());
+ SetValueOrBindingSlow(c, p, defaultValue, val.GetValueOrDefault());
}
else
{
- c.SetValue(p, p.DefaultValue); // set to default value, just in case this property is backed in a different place than c.properties[p]
+ c.SetValue(p, defaultValue); // set to default value, just in case this property is backed in a different place than c.properties[p]
c.properties.Remove(p);
}
}
- public static void SetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding val)
+ public static void SetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue, ValueOrBinding val)
{
var boxedVal = val.UnwrapToObject();
- if (Object.Equals(boxedVal, p.DefaultValue) && !c.IsPropertySet(p))
+ if (Object.Equals(boxedVal, defaultValue) && !c.IsPropertySet(p))
{
// setting to default value and the property is not set -> do nothing
}
@@ -90,15 +90,15 @@ public static void SetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProper
}
}
- public static object? GetValueRawDirect(DotvvmBindableObject c, DotvvmProperty p)
+ public static object? GetValueRawDirect(DotvvmBindableObject c, DotvvmPropertyId p, object defaultValue)
{
if (c.properties.TryGet(p, out var x))
{
return x;
}
- else return p.DefaultValue;
+ else return defaultValue;
}
- public static T? GetStructValueDirect(DotvvmBindableObject c, DotvvmProperty p)
+ public static T? GetStructValueDirect(DotvvmBindableObject c, DotvvmPropertyId p, T? defaultValue)
where T: struct
{
// T being a struct allows us to invert the rather expensive `is IBinding` typecheck in EvalPropertyValue
@@ -111,11 +111,11 @@ public static void SetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProper
return xValue;
return (T?)c.EvalPropertyValue(p, x);
}
- else return (T?)p.DefaultValue;
+ else return defaultValue;
}
- public static void SetValueDirect(DotvvmBindableObject c, DotvvmProperty p, object? value)
+ public static void SetValueDirect(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, object? value)
{
- if (Object.Equals(p.DefaultValue, value) && !c.properties.Contains(p))
+ if (Object.Equals(defaultValue, value) && !c.properties.Contains(p))
{
// setting to default value and the property is not set -> do nothing
}
diff --git a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs
index aa22586320..dedcac62dc 100644
--- a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs
+++ b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs
@@ -45,13 +45,9 @@ private DotvvmCapabilityProperty(
Type declaringType,
ICustomAttributeProvider? attributeProvider,
DotvvmCapabilityProperty? declaringCapability
- ): base()
+ ): base(name ?? prefix + type.Name, declaringType, isValueInherited: false)
{
- name ??= prefix + type.Name;
-
- this.Name = name;
this.PropertyType = type;
- this.DeclaringType = declaringType;
this.Prefix = prefix;
this.AddUsedInCapability(declaringCapability);
@@ -63,14 +59,15 @@ private DotvvmCapabilityProperty(
AssertPropertyNotDefined(this, postContent: false);
- var dotnetFieldName = ToPascalCase(name.Replace("-", "_").Replace(":", "_"));
+ var dotnetFieldName = ToPascalCase(Name.Replace("-", "_").Replace(":", "_"));
attributeProvider ??=
declaringType.GetProperty(dotnetFieldName) ??
declaringType.GetField(dotnetFieldName) ??
(ICustomAttributeProvider?)declaringType.GetField(dotnetFieldName + "Property") ??
- throw new Exception($"Capability backing field could not be found and capabilityAttributeProvider argument was not provided. Property: {declaringType.Name}.{name}. Please declare a field or property named {dotnetFieldName}.");
+ throw new Exception($"Capability backing field could not be found and capabilityAttributeProvider argument was not provided. Property: {declaringType.Name}.{Name}. Please declare a field or property named {dotnetFieldName}.");
DotvvmProperty.InitializeProperty(this, attributeProvider);
+ this.MarkupOptions._mappingMode ??= MappingMode.Exclude;
}
public override object GetValue(DotvvmBindableObject control, bool inherit = true) => Getter(control);
@@ -200,15 +197,8 @@ static DotvvmCapabilityProperty RegisterCapability(DotvvmCapabilityProperty prop
{
var declaringType = property.DeclaringType.NotNull();
var capabilityType = property.PropertyType.NotNull();
- var name = property.Name.NotNull();
AssertPropertyNotDefined(property);
- var attributes = new CustomAttributesProvider(
- new MarkupOptionsAttribute
- {
- MappingMode = MappingMode.Exclude
- }
- );
- DotvvmProperty.Register(name, capabilityType, declaringType, DBNull.Value, false, property, attributes);
+ DotvvmProperty.Register(property);
if (!capabilityRegistry.TryAdd((declaringType, capabilityType, property.Prefix), property))
throw new($"unhandled naming conflict when registering capability {capabilityType}.");
capabilityListRegistry.AddOrUpdate(
diff --git a/src/Framework/Framework/Binding/DotvvmProperty.cs b/src/Framework/Framework/Binding/DotvvmProperty.cs
index ffc12a820e..5fdb7d3315 100644
--- a/src/Framework/Framework/Binding/DotvvmProperty.cs
+++ b/src/Framework/Framework/Binding/DotvvmProperty.cs
@@ -25,11 +25,13 @@ namespace DotVVM.Framework.Binding
[DebuggerDisplay("{FullName}")]
public class DotvvmProperty : IPropertyDescriptor
{
+ public DotvvmPropertyId Id { get; }
///
/// Gets or sets the name of the property.
///
- public string Name { get; protected set; }
+ public string Name { get; }
+
[JsonIgnore]
ITypeDescriptor IControlAttributeDescriptor.DeclaringType => new ResolvedTypeDescriptor(DeclaringType);
@@ -50,7 +52,7 @@ public class DotvvmProperty : IPropertyDescriptor
///
/// Gets the type of the class where the property is registered.
///
- public Type DeclaringType { get; protected set; }
+ public Type DeclaringType { get; }
///
/// Gets whether the value can be inherited from the parent controls.
@@ -61,17 +63,17 @@ public class DotvvmProperty : IPropertyDescriptor
/// Gets or sets the Reflection property information.
///
[JsonIgnore]
- public PropertyInfo? PropertyInfo { get; private set; }
+ public PropertyInfo? PropertyInfo { get; protected set; }
///
/// Provider of custom attributes for this property.
///
- internal ICustomAttributeProvider AttributeProvider { get; set; }
+ internal ICustomAttributeProvider AttributeProvider { get; private protected set; }
///
/// Gets or sets the markup options.
///
- public MarkupOptionsAttribute MarkupOptions { get; set; }
+ public MarkupOptionsAttribute MarkupOptions { get; protected set; }
///
/// Determines if property type inherits from IBinding
@@ -109,6 +111,8 @@ public string FullName
IPropertyDescriptor? IControlAttributeDescriptor.OwningCapability => OwningCapability;
IEnumerable IControlAttributeDescriptor.UsedInCapabilities => UsedInCapabilities;
+ private bool initialized = false;
+
internal void AddUsedInCapability(DotvvmCapabilityProperty? p)
{
@@ -123,12 +127,24 @@ internal void AddUsedInCapability(DotvvmCapabilityProperty? p)
}
}
- ///
- /// Prevents a default instance of the class from being created.
- ///
#pragma warning disable CS8618 // DotvvmProperty is usually initialized by InitializeProperty
- internal DotvvmProperty()
+ internal DotvvmProperty(string name, Type declaringType, bool isValueInherited)
{
+ if (name is null) throw new ArgumentNullException(nameof(name));
+ if (declaringType is null) throw new ArgumentNullException(nameof(declaringType));
+ this.Name = name;
+ this.DeclaringType = declaringType;
+ this.IsValueInherited = isValueInherited;
+ this.Id = DotvvmPropertyIdAssignment.RegisterProperty(this);
+ }
+ internal DotvvmProperty(DotvvmPropertyId id, string name, Type declaringType)
+ {
+ if (name is null) throw new ArgumentNullException(nameof(name));
+ if (declaringType is null) throw new ArgumentNullException(nameof(declaringType));
+ if (id.Id == 0) throw new ArgumentException("DotvvmProperty must have an ID", nameof(id));
+ this.Name = name;
+ this.DeclaringType = declaringType;
+ this.Id = id;
}
internal DotvvmProperty(
#pragma warning restore CS8618
@@ -137,7 +153,8 @@ internal DotvvmProperty(
Type declaringType,
object? defaultValue,
bool isValueInherited,
- ICustomAttributeProvider attributeProvider
+ ICustomAttributeProvider attributeProvider,
+ DotvvmPropertyId id = default
)
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
@@ -146,6 +163,9 @@ ICustomAttributeProvider attributeProvider
this.DefaultValue = defaultValue;
this.IsValueInherited = isValueInherited;
this.AttributeProvider = attributeProvider ?? throw new ArgumentNullException(nameof(attributeProvider));
+ if (id.Id == 0)
+ id = DotvvmPropertyIdAssignment.RegisterProperty(this);
+ this.Id = id;
InitializeProperty(this);
}
@@ -162,6 +182,23 @@ public T[] GetAttributes()
return attrA.Concat(attrB).ToArray();
}
+ public T? GetAttribute() where T: Attribute
+ {
+ var t = typeof(T);
+ var provider = AttributeProvider;
+ if (provider.IsDefined(t, true))
+ {
+ return (T)provider.GetCustomAttributes(t, true).Single();
+ }
+ var property = PropertyInfo;
+ if (property is {} && !object.ReferenceEquals(property, provider))
+ {
+ return (T?)property.GetCustomAttribute(t, true);
+ }
+
+ return null;
+ }
+
public bool IsOwnedByCapability(Type capability) =>
(this is DotvvmCapabilityProperty && this.PropertyType == capability) ||
OwningCapability?.IsOwnedByCapability(capability) == true;
@@ -174,7 +211,7 @@ public bool IsOwnedByCapability(DotvvmCapabilityProperty capability) =>
{
for (var p = control.Parent; p is not null; p = p.Parent)
{
- if (p.properties.TryGet(this, out var v))
+ if (p.properties.TryGet(Id, out var v))
return v;
}
return DefaultValue;
@@ -185,7 +222,7 @@ public bool IsOwnedByCapability(DotvvmCapabilityProperty capability) =>
///
public virtual object? GetValue(DotvvmBindableObject control, bool inherit = true)
{
- if (control.properties.TryGet(this, out var value))
+ if (control.properties.TryGet(Id, out var value))
{
return value;
}
@@ -196,20 +233,29 @@ public bool IsOwnedByCapability(DotvvmCapabilityProperty capability) =>
return DefaultValue;
}
+ private bool IsSetInHierarchy(DotvvmBindableObject control)
+ {
+ for (var p = control.Parent; p is not null; p = p.Parent)
+ {
+ if (p.properties.Contains(Id))
+ return true;
+ }
+ return false;
+ }
///
/// Gets whether the value of the property is set
///
public virtual bool IsSet(DotvvmBindableObject control, bool inherit = true)
{
- if (control.properties.Contains(this))
+ if (control.properties.Contains(Id))
{
return true;
}
- if (IsValueInherited && inherit && control.Parent != null)
+ if (IsValueInherited && inherit)
{
- return IsSet(control.Parent);
+ return IsSetInHierarchy(control);
}
return false;
@@ -221,7 +267,7 @@ public virtual bool IsSet(DotvvmBindableObject control, bool inherit = true)
///
public virtual void SetValue(DotvvmBindableObject control, object? value)
{
- control.properties.Set(this, value);
+ control.properties.Set(Id, value);
}
///
@@ -258,14 +304,32 @@ public virtual void SetValue(DotvvmBindableObject control, object? value)
public static DotvvmProperty Register(string propertyName, Type propertyType, Type declaringType, object? defaultValue, bool isValueInherited, DotvvmProperty? property, ICustomAttributeProvider attributeProvider, bool throwOnDuplicateRegistration = true)
{
- if (property == null) property = new DotvvmProperty();
+ if (propertyName is null) throw new ArgumentNullException(nameof(propertyName));
+ if (propertyType is null) throw new ArgumentNullException(nameof(propertyType));
+ if (declaringType is null) throw new ArgumentNullException(nameof(declaringType));
+ if (attributeProvider is null) throw new ArgumentNullException(nameof(attributeProvider));
- property.Name = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
- property.IsValueInherited = isValueInherited;
- property.DeclaringType = declaringType ?? throw new ArgumentNullException(nameof(declaringType));
- property.PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType));
- property.DefaultValue = defaultValue;
- property.AttributeProvider = attributeProvider ?? throw new ArgumentNullException(nameof(attributeProvider));
+ if (property == null)
+ {
+ property = new DotvvmProperty(propertyName, propertyType, declaringType, defaultValue, isValueInherited, attributeProvider);
+ }
+ else
+ {
+ if (!property.initialized)
+ {
+ property.PropertyType = propertyType;
+ property.DefaultValue = defaultValue;
+ property.IsValueInherited = isValueInherited;
+ property.AttributeProvider = attributeProvider;
+ InitializeProperty(property, attributeProvider);
+ }
+ if (property.Name != propertyName) throw new ArgumentException("The property name does not match the existing property.", nameof(propertyName));
+ if (property.IsValueInherited != isValueInherited) throw new ArgumentException("The IsValueInherited does not match the existing property.", nameof(isValueInherited));
+ if (property.DeclaringType != declaringType) throw new ArgumentException("The declaring type does not match the existing property.", nameof(declaringType));
+ if (property.PropertyType != propertyType) throw new ArgumentException("The property type does not match the existing property.", nameof(propertyType));
+ if (property.DefaultValue != defaultValue) throw new ArgumentException("The default value does not match the existing property.", nameof(defaultValue));
+ if (property.AttributeProvider != attributeProvider) throw new ArgumentException("The attribute provider does not match the existing property.", nameof(attributeProvider));
+ }
return Register(property, throwOnDuplicateRegistration);
}
@@ -288,7 +352,11 @@ public override string Message { get {
internal static DotvvmProperty Register(DotvvmProperty property, bool throwOnDuplicateRegistration = true)
{
- InitializeProperty(property);
+ if (property.Id.Id == 0)
+ throw new Exception("DotvvmProperty must have an ID");
+
+ if (!property.initialized)
+ throw new Exception("DotvvmProperty must be initialized before registration.");
var key = (property.DeclaringType, property.Name);
if (!registeredProperties.TryAdd(key, property))
@@ -373,8 +441,8 @@ public static DotvvmPropertyAlias RegisterAlias(
aliasName,
declaringType,
aliasAttribute.AliasedPropertyName,
- aliasAttribute.AliasedPropertyDeclaringType ?? declaringType);
- propertyAlias.AttributeProvider = attributeProvider;
+ aliasAttribute.AliasedPropertyDeclaringType ?? declaringType,
+ attributeProvider);
propertyAlias.ObsoleteAttribute = attributeProvider.GetCustomAttribute();
var key = (propertyAlias.DeclaringType, propertyAlias.Name);
@@ -396,6 +464,8 @@ public static DotvvmPropertyAlias RegisterAlias(
public static void InitializeProperty(DotvvmProperty property, ICustomAttributeProvider? attributeProvider = null)
{
+ if (property.initialized)
+ throw new Exception("DotvvmProperty should not be initialized twice.");
if (string.IsNullOrWhiteSpace(property.Name))
throw new Exception("DotvvmProperty must not have empty name.");
if (property.DeclaringType is null || property.PropertyType is null)
@@ -409,24 +479,26 @@ public static void InitializeProperty(DotvvmProperty property, ICustomAttributeP
property.PropertyInfo ??
throw new ArgumentNullException(nameof(attributeProvider));
property.MarkupOptions ??=
- property.GetAttributes().SingleOrDefault()
+ property.GetAttribute()
?? new MarkupOptionsAttribute();
if (string.IsNullOrEmpty(property.MarkupOptions.Name))
property.MarkupOptions.Name = property.Name;
property.DataContextChangeAttributes ??=
- property.GetAttributes().ToArray();
+ property.GetAttributes();
property.DataContextManipulationAttribute ??=
- property.GetAttributes().SingleOrDefault();
- if (property.DataContextManipulationAttribute != null && property.DataContextChangeAttributes.Any())
+ property.GetAttribute();
+ if (property.DataContextManipulationAttribute != null && property.DataContextChangeAttributes.Length != 0)
throw new ArgumentException($"{nameof(DataContextChangeAttributes)} and {nameof(DataContextManipulationAttribute)} cannot be set both at property '{property.FullName}'.");
property.IsBindingProperty = typeof(IBinding).IsAssignableFrom(property.PropertyType);
- property.ObsoleteAttribute = property.AttributeProvider.GetCustomAttribute();
+ property.ObsoleteAttribute = property.GetAttribute();
if (property.IsBindingProperty)
{
property.MarkupOptions.AllowHardCodedValue = false;
}
+
+ property.initialized = true;
}
public static void CheckAllPropertiesAreRegistered(Type controlType)
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyAlias.cs b/src/Framework/Framework/Binding/DotvvmPropertyAlias.cs
index dca78455b9..1adb9deeee 100644
--- a/src/Framework/Framework/Binding/DotvvmPropertyAlias.cs
+++ b/src/Framework/Framework/Binding/DotvvmPropertyAlias.cs
@@ -12,10 +12,9 @@ public DotvvmPropertyAlias(
string aliasName,
Type declaringType,
string aliasedPropertyName,
- Type aliasedPropertyDeclaringType)
+ Type aliasedPropertyDeclaringType,
+ System.Reflection.ICustomAttributeProvider attributeProvider): base(aliasName, declaringType, isValueInherited: false)
{
- Name = aliasName;
- DeclaringType = declaringType;
AliasedPropertyName = aliasedPropertyName;
AliasedPropertyDeclaringType = aliasedPropertyDeclaringType;
MarkupOptions = new MarkupOptionsAttribute();
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.cs b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.cs
new file mode 100644
index 0000000000..ad93bd5e05
--- /dev/null
+++ b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.cs
@@ -0,0 +1,536 @@
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using DotVVM.Framework.Compilation.ControlTree;
+using DotVVM.Framework.Controls;
+using DotVVM.Framework.Controls.Infrastructure;
+using FastExpressionCompiler;
+
+namespace DotVVM.Framework.Binding
+{
+ public readonly struct DotvvmPropertyId: IEquatable, IEquatable, IComparable
+ {
+ public readonly uint Id;
+ public DotvvmPropertyId(uint id)
+ {
+ Id = id;
+ }
+
+ public DotvvmPropertyId(ushort typeOrGroupId, ushort memberId)
+ {
+ Id = ((uint)typeOrGroupId << 16) | memberId;
+ }
+
+ [MemberNotNullWhen(true, nameof(PropertyGroupInstance), nameof(GroupMemberName))]
+ public bool IsPropertyGroup => (int)Id < 0;
+ public ushort TypeId => (ushort)(Id >> 16);
+ public ushort GroupId => (ushort)((Id >> 16) ^ 0x80_00);
+ public ushort MemberId => (ushort)(Id & 0xFFFF);
+
+ /// Returns true if the property does not have GetValue/SetValue overrides and is not inherited. That means it is sufficient
+ public bool CanUseFastAccessors
+ {
+ get
+ {
+ // properties: we encode this information as the LSB bit of the member ID (i.e. odd/even numbers)
+ // property groups: always true, i.e.
+ const uint mask = (1u << 31) | (1u);
+ const uint targetValue = 1u;
+ return (Id & mask) != targetValue;
+ }
+ }
+
+ public bool IsZero => Id == 0;
+
+ public DotvvmProperty PropertyInstance => DotvvmPropertyIdAssignment.GetProperty(Id) ?? throw new Exception($"Property with ID {Id} not registered.");
+ public DotvvmPropertyGroup? PropertyGroupInstance => !IsPropertyGroup ? null : DotvvmPropertyIdAssignment.GetPropertyGroup(GroupId);
+ public string? GroupMemberName => !IsPropertyGroup ? null : DotvvmPropertyIdAssignment.GetGroupMemberName(MemberId);
+
+ public bool IsInPropertyGroup(ushort id) => (this.Id >> 16) == ((uint)id | 0x80_00u);
+
+ public static DotvvmPropertyId CreatePropertyGroupId(ushort groupId, ushort memberId) => new DotvvmPropertyId((ushort)(groupId | 0x80_00), memberId);
+
+ public static implicit operator DotvvmPropertyId(uint id) => new DotvvmPropertyId(id);
+
+ public bool Equals(DotvvmPropertyId other) => Id == other.Id;
+ public bool Equals(uint other) => Id == other;
+ public override bool Equals(object? obj) => obj is DotvvmPropertyId id && Equals(id);
+ public override int GetHashCode() => (int)Id;
+
+ public static bool operator ==(DotvvmPropertyId left, DotvvmPropertyId right) => left.Equals(right);
+ public static bool operator !=(DotvvmPropertyId left, DotvvmPropertyId right) => !left.Equals(right);
+
+ public override string ToString() => $"PropId={Id}";
+ public int CompareTo(DotvvmPropertyId other) => Id.CompareTo(other.Id);
+ }
+
+ static class DotvvmPropertyIdAssignment
+ {
+ const int DEFAULT_PROPERTY_COUNT = 16;
+ static readonly ConcurrentDictionary typeIds;
+ private static readonly object controlTypeRegisterLock = new object();
+ private static int controlCounter = 256; // first 256 types are reserved for DotVVM controls
+ private static ControlTypeInfo[] controls = new ControlTypeInfo[1024];
+ private static readonly object groupRegisterLock = new object();
+ private static int groupCounter = 256; // first 256 types are reserved for DotVVM controls
+ private static DotvvmPropertyGroup?[] propertyGroups = new DotvvmPropertyGroup[1024];
+ private static ulong[] propertyGroupActiveBitmap = new ulong[1024 / 64];
+ static readonly ConcurrentDictionary propertyGroupMemberIds = new(concurrencyLevel: 1, capacity: 256) {
+ ["id"] = GroupMembers.id,
+ ["class"] = GroupMembers.@class,
+ ["style"] = GroupMembers.style,
+ ["name"] = GroupMembers.name,
+ ["data-bind"] = GroupMembers.databind,
+ };
+ private static readonly object groupMemberRegisterLock = new object();
+ static string?[] propertyGroupMemberNames = new string[1024];
+
+ static DotvvmPropertyIdAssignment()
+ {
+ foreach (var n in propertyGroupMemberIds)
+ {
+ propertyGroupMemberNames[n.Value] = n.Key;
+ }
+
+ typeIds = new() {
+ [typeof(DotvvmBindableObject)] = TypeIds.DotvvmBindableObject,
+ [typeof(DotvvmControl)] = TypeIds.DotvvmControl,
+ [typeof(HtmlGenericControl)] = TypeIds.HtmlGenericControl,
+ [typeof(RawLiteral)] = TypeIds.RawLiteral,
+ [typeof(Literal)] = TypeIds.Literal,
+ [typeof(ButtonBase)] = TypeIds.ButtonBase,
+ [typeof(Button)] = TypeIds.Button,
+ [typeof(LinkButton)] = TypeIds.LinkButton,
+ [typeof(TextBox)] = TypeIds.TextBox,
+ [typeof(RouteLink)] = TypeIds.RouteLink,
+ [typeof(CheckableControlBase)] = TypeIds.CheckableControlBase,
+ [typeof(CheckBox)] = TypeIds.CheckBox,
+ [typeof(Validator)] = TypeIds.Validator,
+ [typeof(Validation)] = TypeIds.Validation,
+ [typeof(ValidationSummary)] = TypeIds.ValidationSummary,
+ };
+ }
+
+#region Optimized metadata accessors
+ public static bool IsInherited(DotvvmPropertyId propertyId)
+ {
+ if (propertyId.CanUseFastAccessors)
+ return false;
+
+ return BitmapRead(controls[propertyId.TypeId].inheritedBitmap, propertyId.MemberId);
+ }
+
+ public static bool UsesStandardAccessors(DotvvmPropertyId propertyId)
+ {
+ if (propertyId.CanUseFastAccessors)
+ {
+ return true;
+ }
+ else
+ {
+ var bitmap = controls[propertyId.TypeId].standardBitmap;
+ var index = propertyId.MemberId;
+ return BitmapRead(bitmap, index);
+ }
+ }
+
+ public static bool IsActive(DotvvmPropertyId propertyId)
+ {
+ Debug.Assert(DotvvmPropertyIdAssignment.GetProperty(propertyId) != null, $"Property {propertyId} not registered.");
+ ulong[] bitmap;
+ uint index;
+ if (propertyId.IsPropertyGroup)
+ {
+ bitmap = propertyGroupActiveBitmap;
+ index = propertyId.GroupId;
+ }
+ else
+ {
+ bitmap = controls[propertyId.TypeId].activeBitmap;
+ index = propertyId.MemberId;
+ }
+ return BitmapRead(bitmap, index);
+ }
+
+ public static DotvvmProperty? GetProperty(DotvvmPropertyId id)
+ {
+ if (id.IsPropertyGroup)
+ {
+ var groupIx = id.GroupId;
+ if (groupIx >= propertyGroups.Length)
+ return null;
+ var group = propertyGroups[groupIx];
+ if (group is null)
+ return null;
+
+ return group.GetDotvvmProperty(id.MemberId);
+ }
+ else
+ {
+ var typeId = id.TypeId;
+ if (typeId >= controls.Length)
+ return null;
+ var typeProps = controls[typeId].properties;
+ if (typeProps is null)
+ return null;
+ return typeProps[id.MemberId];
+ }
+ }
+
+ public static Compilation.IControlAttributeDescriptor? GetPropertyOrPropertyGroup(DotvvmPropertyId id)
+ {
+ if (id.IsPropertyGroup)
+ {
+ var groupIx = id.GroupId;
+ if (groupIx >= propertyGroups.Length)
+ return null;
+ return propertyGroups[groupIx];
+ }
+ else
+ {
+ var typeId = id.TypeId;
+ if (typeId >= controls.Length)
+ return null;
+ var typeProps = controls[typeId].properties;
+ if (typeProps is null)
+ return null;
+ return typeProps[id.MemberId];
+ }
+ }
+
+ public static object? GetValueRaw(DotvvmBindableObject obj, DotvvmPropertyId id, bool inherit = true)
+ {
+ if (id.CanUseFastAccessors)
+ {
+ if (obj.properties.TryGet(id, out var value))
+ return value;
+
+ return propertyGroups[id.GroupId]!.DefaultValue;
+ }
+ else
+ {
+ var property = controls[id.TypeId].properties[id.MemberId];
+ return property!.GetValue(obj, inherit);
+ }
+ }
+
+ public static MarkupOptionsAttribute GetMarkupOptions(DotvvmPropertyId id)
+ {
+ if (id.IsPropertyGroup)
+ {
+ var groupIx = id.GroupId;
+ return propertyGroups[groupIx]!.MarkupOptions;
+ }
+ else
+ {
+ var typeId = id.TypeId;
+ var typeProps = controls[typeId].properties;
+ return typeProps[id.MemberId]!.MarkupOptions;
+ }
+ }
+
+ /// Property or property group has type assignable to IBinding and bindings should not be evaluated in GetValue
+ public static bool IsBindingProperty(DotvvmPropertyId id)
+ {
+ if (id.IsPropertyGroup)
+ {
+ var groupIx = id.GroupId;
+ return propertyGroups[groupIx]!.IsBindingProperty;
+ }
+ else
+ {
+ var typeId = id.TypeId;
+ var typeProps = controls[typeId].properties;
+ return typeProps[id.MemberId]!.IsBindingProperty;
+ }
+ }
+
+ public static DotvvmPropertyGroup? GetPropertyGroup(ushort id)
+ {
+ if (id >= propertyGroups.Length)
+ return null;
+ return propertyGroups[id];
+ }
+#endregion
+
+#region Registration
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort RegisterType(Type type)
+ {
+ if (typeIds.TryGetValue(type, out var existingId) && controls[existingId].locker is {})
+ return existingId;
+
+ return unlikely(type);
+
+ static ushort unlikely(Type type)
+ {
+ var types = MemoryMarshal.CreateReadOnlySpan(ref type, 1);
+ Span ids = stackalloc ushort[1];
+ RegisterTypes(types, ids);
+ return ids[0];
+ }
+ }
+ public static void RegisterTypes(ReadOnlySpan types, Span ids)
+ {
+ if (types.Length == 0)
+ return;
+
+ lock (controlTypeRegisterLock)
+ {
+ if (controlCounter + types.Length >= controls.Length)
+ {
+ VolatileResize(ref controls, 1 << (BitOperations.Log2((uint)(controlCounter + types.Length)) + 1));
+ }
+ for (int i = 0; i < types.Length; i++)
+ {
+ var type = types[i];
+ if (!typeIds.TryGetValue(type, out var id))
+ {
+ id = (ushort)controlCounter++;
+ }
+ if (controls[id].locker is null)
+ {
+ controls[id].locker = new object();
+ controls[id].controlType = type;
+ controls[id].properties = new DotvvmProperty[DEFAULT_PROPERTY_COUNT];
+ controls[id].inheritedBitmap = new ulong[(DEFAULT_PROPERTY_COUNT - 1) / 64 + 1];
+ controls[id].standardBitmap = new ulong[(DEFAULT_PROPERTY_COUNT - 1) / 64 + 1];
+ controls[id].activeBitmap = new ulong[(DEFAULT_PROPERTY_COUNT - 1) / 64 + 1];
+ typeIds[type] = id;
+ }
+ ids[i] = id;
+ }
+ }
+ }
+ public static DotvvmPropertyId RegisterProperty(DotvvmProperty property)
+ {
+ if (property.GetType() == typeof(GroupedDotvvmProperty))
+ throw new ArgumentException("RegisterProperty cannot be called with GroupedDotvvmProperty!");
+
+ var typeCanUseDirectAccess = TypeCanUseAnyDirectAccess(property.GetType());
+ var canUseDirectAccess = !property.IsValueInherited && typeCanUseDirectAccess;
+
+ var typeId = RegisterType(property.DeclaringType);
+ ref ControlTypeInfo control = ref controls[typeId];
+ lock (control.locker) // single control registrations are sequential anyway
+ {
+ uint id;
+ if (canUseDirectAccess)
+ {
+ control.counterStandard += 1;
+ id = control.counterStandard * 2;
+ }
+ else
+ {
+ control.counterNonStandard += 1;
+ id = control.counterNonStandard * 2 + 1;
+ }
+ if (id > ushort.MaxValue)
+ ThrowTooManyException(property);
+
+ // resize arrays (we hold a write lock, but others may be reading in parallel)
+ if (id >= control.properties.Length)
+ {
+ VolatileResize(ref control.properties, control.properties.Length * 2);
+ }
+ if (id / 64 >= control.inheritedBitmap.Length)
+ {
+ Debug.Assert(control.inheritedBitmap.Length == control.standardBitmap.Length);
+ Debug.Assert(control.inheritedBitmap.Length == control.activeBitmap.Length);
+
+ VolatileResize(ref control.inheritedBitmap, control.inheritedBitmap.Length * 2);
+ VolatileResize(ref control.standardBitmap, control.standardBitmap.Length * 2);
+ VolatileResize(ref control.activeBitmap, control.activeBitmap.Length * 2);
+ }
+
+ if (property.IsValueInherited)
+ BitmapSet(control.inheritedBitmap, (uint)id);
+ if (typeCanUseDirectAccess)
+ BitmapSet(control.standardBitmap, (uint)id);
+ if (property is ActiveDotvvmProperty)
+ BitmapSet(control.activeBitmap, (uint)id);
+
+ control.properties[id] = property;
+ return new DotvvmPropertyId(typeId, (ushort)id);
+ }
+
+ static void ThrowTooManyException(DotvvmProperty property) =>
+ throw new Exception($"Too many properties registered for a single control type ({property.DeclaringType.ToCode()}). Trying to register property '{property.Name}: {property.PropertyType.ToCode()}'");
+ }
+
+ private static readonly ConcurrentDictionary cacheTypeCanUseDirectAccess = new(concurrencyLevel: 1, capacity: 10);
+ /// Does the property use the default GetValue/SetValue methods?
+ public static (bool getter, bool setter) TypeCanUseDirectAccess(Type propertyType)
+ {
+ if (propertyType == typeof(DotvvmProperty) || propertyType == typeof(GroupedDotvvmProperty))
+ return (true, true);
+
+ if (propertyType == typeof(DotvvmCapabilityProperty) || propertyType == typeof(DotvvmPropertyAlias) || propertyType == typeof(CompileTimeOnlyDotvvmProperty))
+ return (false, false);
+
+ if (propertyType.IsGenericType)
+ {
+ propertyType = propertyType.GetGenericTypeDefinition();
+ if (propertyType == typeof(DelegateActionProperty<>))
+ return (true, true);
+ }
+
+ return cacheTypeCanUseDirectAccess.GetOrAdd(propertyType, static t =>
+ {
+ var getter = t.GetMethod(nameof(DotvvmProperty.GetValue), new [] { typeof(DotvvmBindableObject), typeof(bool) })!.DeclaringType == typeof(DotvvmProperty);
+ var setter = t.GetMethod(nameof(DotvvmProperty.SetValue), new [] { typeof(DotvvmBindableObject), typeof(object) })!.DeclaringType == typeof(DotvvmProperty);
+ return (getter, setter);
+ });
+ }
+ public static bool TypeCanUseAnyDirectAccess(Type propertyType)
+ {
+ var (getter, setter) = TypeCanUseDirectAccess(propertyType);
+ return getter && setter;
+ }
+
+ public static ushort RegisterPropertyGroup(DotvvmPropertyGroup group)
+ {
+ lock (groupRegisterLock)
+ {
+ var id = (ushort)groupCounter++;
+ if (id == 0)
+ throw new Exception("Too many property groups registered already.");
+
+ if (id >= propertyGroups.Length)
+ {
+ VolatileResize(ref propertyGroups, propertyGroups.Length * 2);
+ VolatileResize(ref propertyGroupActiveBitmap, propertyGroupActiveBitmap.Length * 2);
+ }
+
+ propertyGroups[id] = group;
+ if (group is ActiveDotvvmPropertyGroup)
+ BitmapSet(propertyGroupActiveBitmap, id);
+ return id;
+ }
+ }
+
+ /// Thread-safe to read from the array while we are resizing
+ private static void VolatileResize(ref T[] array, int newSize)
+ {
+ var localRef = array;
+ var newArray = new T[newSize];
+ localRef.AsSpan().CopyTo(newArray.AsSpan(0, localRef.Length));
+ Volatile.Write(ref array, newArray);
+ }
+
+#endregion Registration
+
+#region Group members
+ private static ushort PredefinedPropertyGroupMemberId(ReadOnlySpan name)
+ {
+ switch (name)
+ {
+ case "class": return GroupMembers.@class;
+ case "id": return GroupMembers.id;
+ case "style": return GroupMembers.style;
+ case "name": return GroupMembers.name;
+ case "data-bind": return GroupMembers.databind;
+ default: return 0;
+ }
+ }
+
+ public static ushort GetGroupMemberId(string name, bool registerIfNotFound)
+ {
+ var id = PredefinedPropertyGroupMemberId(name);
+ if (id != 0)
+ return id;
+ if (propertyGroupMemberIds.TryGetValue(name, out id))
+ return id;
+ if (!registerIfNotFound)
+ return 0;
+ return RegisterGroupMember(name);
+ }
+
+ private static ushort RegisterGroupMember(string name)
+ {
+ lock (groupMemberRegisterLock)
+ {
+ if (propertyGroupMemberIds.TryGetValue(name, out var id))
+ return id;
+ id = (ushort)(propertyGroupMemberIds.Count + 1);
+ if (id == 0)
+ throw new Exception("Too many property group members registered already.");
+ if (id >= propertyGroupMemberNames.Length)
+ VolatileResize(ref propertyGroupMemberNames, propertyGroupMemberNames.Length * 2);
+ propertyGroupMemberNames[id] = name;
+ propertyGroupMemberIds[name] = id;
+ return id;
+ }
+ }
+
+ internal static string? GetGroupMemberName(ushort id)
+ {
+ if (id < propertyGroupMemberNames.Length)
+ return propertyGroupMemberNames[id];
+ return null;
+ }
+#endregion Group members
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static bool BitmapRead(ulong[] bitmap, uint index)
+ {
+ return (bitmap[index / 64] & (1ul << (int)(index % 64))) != 0;
+ }
+
+ static void BitmapSet(ulong[] bitmap, uint index)
+ {
+ bitmap[index / 64] |= 1ul << (int)(index % 64);
+ }
+
+ private struct ControlTypeInfo
+ {
+ public DotvvmProperty?[] properties;
+ public ulong[] inheritedBitmap;
+ public ulong[] standardBitmap;
+ public ulong[] activeBitmap;
+ public object locker;
+ public Type controlType;
+ public uint counterStandard;
+ public uint counterNonStandard;
+ }
+
+ public static class GroupMembers
+ {
+ public const ushort id = 1;
+ public const ushort @class = 2;
+ public const ushort style = 3;
+ public const ushort name = 4;
+ public const ushort databind = 5;
+ }
+
+ public static class TypeIds
+ {
+ public const ushort DotvvmBindableObject = 1;
+ public const ushort DotvvmControl = 2;
+ public const ushort HtmlGenericControl = 3;
+ public const ushort RawLiteral = 4;
+ public const ushort Literal = 5;
+ public const ushort ButtonBase = 6;
+ public const ushort Button = 7;
+ public const ushort LinkButton = 8;
+ public const ushort TextBox = 9;
+ public const ushort RouteLink = 10;
+ public const ushort CheckableControlBase = 11;
+ public const ushort CheckBox = 12;
+ public const ushort Validator = 13;
+ public const ushort Validation = 14;
+ public const ushort ValidationSummary = 15;
+ // public const short Internal = 4;
+ }
+
+ public static class PropertyIds
+ {
+ public const uint DotvvmBindableObject_DataContext = TypeIds.DotvvmBindableObject << 16 | 1;
+ }
+ }
+}
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyWithFallback.cs b/src/Framework/Framework/Binding/DotvvmPropertyWithFallback.cs
index 08ede07bb6..f41257d340 100644
--- a/src/Framework/Framework/Binding/DotvvmPropertyWithFallback.cs
+++ b/src/Framework/Framework/Binding/DotvvmPropertyWithFallback.cs
@@ -16,7 +16,7 @@ public sealed class DotvvmPropertyWithFallback : DotvvmProperty
///
public DotvvmProperty FallbackProperty { get; private set; }
- public DotvvmPropertyWithFallback(DotvvmProperty fallbackProperty)
+ public DotvvmPropertyWithFallback(DotvvmProperty fallbackProperty, string name, Type declaringType, bool isValueInherited): base(name, declaringType, isValueInherited)
{
this.FallbackProperty = fallbackProperty;
}
@@ -61,7 +61,7 @@ public static DotvvmProperty Register(Expression<
/// Indicates whether the value can be inherited from the parent controls.
public static DotvvmPropertyWithFallback Register(string propertyName, DotvvmProperty fallbackProperty, bool isValueInherited = false)
{
- var property = new DotvvmPropertyWithFallback(fallbackProperty);
+ var property = new DotvvmPropertyWithFallback(fallbackProperty, propertyName, typeof(TDeclaringType), isValueInherited: isValueInherited);
Register(propertyName, isValueInherited: isValueInherited, property: property);
property.DefaultValue = fallbackProperty.DefaultValue;
return property;
diff --git a/src/Framework/Framework/Binding/GroupedDotvvmProperty.cs b/src/Framework/Framework/Binding/GroupedDotvvmProperty.cs
index 4f5f91c0d1..8556b39e87 100644
--- a/src/Framework/Framework/Binding/GroupedDotvvmProperty.cs
+++ b/src/Framework/Framework/Binding/GroupedDotvvmProperty.cs
@@ -16,28 +16,26 @@ public sealed class GroupedDotvvmProperty : DotvvmProperty, IGroupedPropertyDesc
IPropertyGroupDescriptor IGroupedPropertyDescriptor.PropertyGroup => PropertyGroup;
- public GroupedDotvvmProperty(string groupMemberName, DotvvmPropertyGroup propertyGroup)
+ private GroupedDotvvmProperty(string memberName, ushort memberId, DotvvmPropertyGroup group)
+ : base(DotvvmPropertyId.CreatePropertyGroupId(group.Id, memberId), group.Name + ":" + memberName, group.DeclaringType)
{
- this.GroupMemberName = groupMemberName;
- this.PropertyGroup = propertyGroup;
+ this.GroupMemberName = memberName;
+ this.PropertyGroup = group;
}
- public static GroupedDotvvmProperty Create(DotvvmPropertyGroup group, string name)
+ public static GroupedDotvvmProperty Create(DotvvmPropertyGroup group, string name, ushort id)
{
- var propname = group.Name + ":" + name;
- var prop = new GroupedDotvvmProperty(name, group) {
+ var prop = new GroupedDotvvmProperty(name, id, group) {
PropertyType = group.PropertyType,
- DeclaringType = group.DeclaringType,
DefaultValue = group.DefaultValue,
IsValueInherited = false,
- Name = propname,
ObsoleteAttribute = group.ObsoleteAttribute,
OwningCapability = group.OwningCapability,
UsedInCapabilities = group.UsedInCapabilities
};
- DotvvmProperty.InitializeProperty(prop, group.AttributeProvider);
+ DotvvmProperty.InitializeProperty(prop, group.AttributeProvider); // TODO: maybe inline and specialize to just copy the group attributes
return prop;
}
}
diff --git a/src/Framework/Framework/Binding/ValueOrBinding.cs b/src/Framework/Framework/Binding/ValueOrBinding.cs
index bf06d6d635..54f8b24f25 100644
--- a/src/Framework/Framework/Binding/ValueOrBinding.cs
+++ b/src/Framework/Framework/Binding/ValueOrBinding.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using DotVVM.Framework.Binding.Expressions;
@@ -50,6 +51,7 @@ public ValueOrBinding(IStaticValueBinding binding)
}
/// Creates new ValueOrBinding which contains the specified value. Note that there is an implicit conversion for this, so calling the constructor explicitly may be unnecessary.
+ [DebuggerStepThrough]
public ValueOrBinding(T value)
{
this.value = value;
diff --git a/src/Framework/Framework/Binding/VirtualPropertyGroupDictionary.cs b/src/Framework/Framework/Binding/VirtualPropertyGroupDictionary.cs
index 8be168dd0e..f967e80f9b 100644
--- a/src/Framework/Framework/Binding/VirtualPropertyGroupDictionary.cs
+++ b/src/Framework/Framework/Binding/VirtualPropertyGroupDictionary.cs
@@ -24,17 +24,24 @@ public VirtualPropertyGroupDictionary(DotvvmBindableObject control, DotvvmProper
this.group = group;
}
+ DotvvmPropertyId GetMemberId(string key, bool createNew = false)
+ {
+ var memberId = DotvvmPropertyIdAssignment.GetGroupMemberId(key, registerIfNotFound: createNew);
+ return DotvvmPropertyId.CreatePropertyGroupId(group.Id, memberId);
+ }
+
+ string GetMemberName(DotvvmPropertyId key)
+ {
+ return DotvvmPropertyIdAssignment.GetGroupMemberName((ushort)(key.Id & 0xFF_FF))!;
+ }
+
public IEnumerable Keys
{
get
{
- foreach (var (p, _) in control.properties)
+ foreach (var (p, _) in control.properties.PropertyGroup(group.Id))
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- yield return pg.GroupMemberName;
- }
+ yield return GetMemberName(p);
}
}
}
@@ -44,13 +51,9 @@ public IEnumerable Values
{
get
{
- foreach (var (p, _) in control.properties)
+ foreach (var (p, value) in control.properties.PropertyGroup(group.Id))
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- yield return (TValue)control.GetValue(p)!;
- }
+ yield return (TValue)control.EvalPropertyValue(group, value)!;
}
}
}
@@ -59,48 +62,17 @@ public IEnumerable Properties
{
get
{
- foreach (var (p, _) in control.properties)
+ foreach (var (p, _) in control.properties.PropertyGroup(group.Id))
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- yield return pg;
- }
+ var prop = group.GetDotvvmProperty(p.MemberId);
+ yield return prop;
}
}
}
- public int Count
- {
- get
- {
- // we don't want to use Linq Enumerable.Count() as it would allocate
- // the enumerator. foreach gets the struct enumerator so it does not allocate anything
- var count = 0;
- foreach (var (p, _) in control.properties)
- {
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- count++;
- }
- }
- return count;
- }
- }
+ public int Count => control.properties.CountPropertyGroup(group.Id);
- public bool Any()
- {
- foreach (var (p, _) in control.properties)
- {
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- return true;
- }
- }
- return false;
- }
+ public bool Any() => control.properties.ContainsPropertyGroup(group.Id);
public bool IsReadOnly => false;
@@ -113,65 +85,79 @@ public TValue this[string key]
{
get
{
- var p = group.GetDotvvmProperty(key);
+ var p = GetMemberId(key);
if (control.properties.TryGet(p, out var value))
- return (TValue)control.EvalPropertyValue(p, value)!;
+ return (TValue)control.EvalPropertyValue(group, value)!;
else
- return (TValue)p.DefaultValue!;
+ return (TValue)group.DefaultValue!;
}
set
{
- control.properties.Set(group.GetDotvvmProperty(key), value);
+ control.properties.Set(GetMemberId(key), value);
}
}
/// Gets the value binding set to a specified property. Returns null if the property is not a binding, throws if the binding some kind of command.
- public IValueBinding? GetValueBinding(string key) => control.GetValueBinding(group.GetDotvvmProperty(key));
+ public IValueBinding? GetValueBinding(string key)
+ {
+ var binding = GetBinding(key);
+ if (binding != null && binding is not IStaticValueBinding) // throw exception on incompatible binding types
+ {
+ throw new BindingHelper.BindingNotSupportedException(binding) { RelatedControl = control };
+ }
+ return binding as IValueBinding;
+
+ }
/// Gets the binding set to a specified property. Returns null if the property is not set or if the value is not a binding.
- public IBinding? GetBinding(string key) => control.GetBinding(group.GetDotvvmProperty(key));
+ public IBinding? GetBinding(string key) => GetValueRaw(key) as IBinding;
/// Gets the value or a binding object for a specified property.
public object? GetValueRaw(string key)
{
- var p = group.GetDotvvmProperty(key);
- if (control.properties.TryGet(p, out var value))
+ if (control.properties.TryGet(GetMemberId(key), out var value))
return value;
else
- return p.DefaultValue!;
+ return group.DefaultValue!;
}
/// Adds value or overwrites the property identified by .
public void Set(string key, ValueOrBinding value)
{
- control.properties.Set(group.GetDotvvmProperty(key), value.UnwrapToObject());
+ control.properties.Set(GetMemberId(key, createNew: true), value.UnwrapToObject());
}
/// Adds value or overwrites the property identified by with the value.
public void Set(string key, TValue value) =>
- control.properties.Set(group.GetDotvvmProperty(key), value);
+ control.properties.Set(GetMemberId(key, createNew: true), value);
/// Adds binding or overwrites the property identified by with the binding.
public void SetBinding(string key, IBinding binding) =>
- control.properties.Set(group.GetDotvvmProperty(key), binding);
+ control.properties.Set(GetMemberId(key, createNew: true), binding);
public bool ContainsKey(string key)
{
- return control.Properties.ContainsKey(group.GetDotvvmProperty(key));
+ return control.properties.Contains(GetMemberId(key));
}
- private void AddOnConflict(GroupedDotvvmProperty property, object? value)
+ private void AddOnConflict(DotvvmPropertyId id, string key, object? value)
{
var merger = this.group.ValueMerger;
if (merger is null)
- throw new ArgumentException($"Cannot Add({property.Name}, {value}) since the value is already set and merging is not enabled on this property group.");
- var mergedValue = merger.MergePlainValues(property, control.properties.GetOrThrow(property), value);
- control.properties.Set(property, mergedValue);
+ throw new ArgumentException($"Cannot Add({key}, {value}) since the value is already set and merging is not enabled on this property group.");
+ var mergedValue = merger.MergePlainValues(id, control.properties.GetOrThrow(id), value);
+ control.properties.Set(id, mergedValue);
}
+ internal void AddInternal(ushort key, object? val)
+ {
+ var prop = DotvvmPropertyId.CreatePropertyGroupId(group.Id, key);
+ if (!control.properties.TryAdd(prop, val))
+ AddOnConflict(prop, prop.GroupMemberName.NotNull(), val);
+ }
/// Adds the property identified by . If the property is already set, it tries appending the value using the group's
public void Add(string key, ValueOrBinding value)
{
- var prop = group.GetDotvvmProperty(key);
- object? val = value.UnwrapToObject();
+ var prop = GetMemberId(key, createNew: true);
+ object? val = value.UnwrapToObject(); // TODO VOB boxing
if (!control.properties.TryAdd(prop, val))
- AddOnConflict(prop, val);
+ AddOnConflict(prop, key, val);
}
/// Adds the property identified by . If the property is already set, it tries appending the value using the group's
@@ -206,13 +192,14 @@ public static IDictionary CreateValueDictionary(DotvvmBindableOb
var result = new Dictionary();
foreach (var (p, valueRaw) in control.properties)
{
- if (p is GroupedDotvvmProperty pg && pg.PropertyGroup == group)
+ if (p.IsInPropertyGroup(group.Id))
{
- var valueObj = control.EvalPropertyValue(p, valueRaw);
+ var name = DotvvmPropertyIdAssignment.GetGroupMemberName((ushort)(p.Id & 0xFF_FF))!;
+ var valueObj = control.EvalPropertyValue(group, valueRaw);
if (valueObj is TValue value)
- result.Add(pg.GroupMemberName, value);
+ result.Add(name, value);
else if (valueObj is null)
- result.Add(pg.GroupMemberName, default!);
+ result.Add(name, default!);
}
}
return result;
@@ -223,16 +210,17 @@ public static IDictionary> CreatePropertyDictiona
var result = new Dictionary>();
foreach (var (p, valRaw) in control.properties)
{
- if (p is GroupedDotvvmProperty pg && pg.PropertyGroup == group)
+ if (p.IsInPropertyGroup(group.Id))
{
- result.Add(pg.GroupMemberName, ValueOrBinding.FromBoxedValue(valRaw));
+ var name = DotvvmPropertyIdAssignment.GetGroupMemberName((ushort)(p.Id & 0xFF_FF))!;
+ result.Add(name, ValueOrBinding.FromBoxedValue(valRaw));
}
}
return result;
}
public bool Remove(string key)
{
- return control.Properties.Remove(group.GetDotvvmProperty(key));
+ return control.properties.Remove(GetMemberId(key));
}
/// Tries getting value of property identified by . If the property contains a binding, it will be automatically evaluated.
@@ -240,10 +228,11 @@ public bool Remove(string key)
public bool TryGetValue(string key, [MaybeNullWhen(false)] out TValue value)
#pragma warning restore CS8767
{
- var prop = group.GetDotvvmProperty(key);
- if (control.properties.TryGet(prop, out var valueRaw))
+ var memberId = DotvvmPropertyIdAssignment.GetGroupMemberId(key, registerIfNotFound: false);
+ var p = DotvvmPropertyId.CreatePropertyGroupId(group.Id, memberId);
+ if (control.properties.TryGet(p, out var valueRaw))
{
- value = (TValue)control.EvalPropertyValue(prop, valueRaw)!;
+ value = (TValue)control.EvalPropertyValue(group, valueRaw)!;
return true;
}
else
@@ -262,31 +251,26 @@ public void Add(KeyValuePair item)
public void Clear()
{
// we want to avoid allocating the list if there is only one property
- DotvvmProperty? toRemove = null;
- List? toRemoveRest = null;
+ DotvvmPropertyId toRemove = default;
+ List? toRemoveRest = null;
- foreach (var (p, _) in control.properties)
+ foreach (var (p, _) in control.properties.PropertyGroup(group.Id))
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
+ if (toRemove.Id == 0)
+ toRemove = p;
+ else
{
- if (toRemove is null)
- toRemove = p;
- else
- {
- if (toRemoveRest is null)
- toRemoveRest = new List();
- toRemoveRest.Add(p);
- }
+ toRemoveRest ??= new List();
+ toRemoveRest.Add(p);
}
}
- if (toRemove is {})
- control.Properties.Remove(toRemove);
+ if (toRemove.Id != 0)
+ control.properties.Remove(toRemove);
if (toRemoveRest is {})
foreach (var p in toRemoveRest)
- control.Properties.Remove(p);
+ control.properties.Remove(p);
}
public bool Contains(KeyValuePair item)
@@ -321,31 +305,73 @@ public bool Remove(KeyValuePair item)
/// Enumerates all keys and values. If a property contains a binding, it will be automatically evaluated.
public IEnumerator> GetEnumerator()
{
- foreach (var (p, value) in control.properties)
+ foreach (var (p, value) in control.properties.PropertyGroup(group.Id))
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- yield return new KeyValuePair(pg.GroupMemberName, (TValue)control.EvalPropertyValue(p, value)!);
- }
+ var name = GetMemberName(p);
+ yield return new KeyValuePair(name, (TValue)control.EvalPropertyValue(group, value)!);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// Enumerates all keys and values, without evaluating the bindings.
- public IEnumerable> RawValues
+ public RawValuesCollection RawValues => new RawValuesCollection(this);
+
+ public readonly struct RawValuesCollection: IEnumerable>, IReadOnlyDictionary
{
- get
+ readonly VirtualPropertyGroupDictionary self;
+
+ internal RawValuesCollection(VirtualPropertyGroupDictionary self)
{
- foreach (var (p, value) in control.properties)
+ this.self = self;
+ }
+
+ public object? this[string key] => self.GetValueRaw(key);
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) =>
+ self.control.properties.TryGet(self.GetMemberId(key), out value);
+
+ public IEnumerable Keys => self.Keys;
+
+ public IEnumerable