@@ -20,6 +20,11 @@ namespace Microsoft.Toolkit.Mvvm.ComponentModel
2020 /// </summary>
2121 public abstract class ObservableValidator : ObservableObject , INotifyDataErrorInfo
2222 {
23+ /// <summary>
24+ /// The <see cref="ConditionalWeakTable{TKey,TValue}"/> instance used to track properties to validate for a given viewmodel type.
25+ /// </summary>
26+ private static readonly ConditionalWeakTable < Type , PropertyInfo [ ] > ValidatableProperties = new ( ) ;
27+
2328 /// <summary>
2429 /// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="HasErrors"/>.
2530 /// </summary>
@@ -395,11 +400,21 @@ IEnumerable<ValidationResult> GetAllErrors()
395400 /// </remarks>
396401 protected void ValidateAllProperties ( )
397402 {
398- foreach ( PropertyInfo propertyInfo in
399- GetType ( )
400- . GetProperties ( BindingFlags . Instance | BindingFlags . Public )
401- . Where ( static p => p . GetIndexParameters ( ) . Length == 0 &&
402- p . GetCustomAttributes < ValidationAttribute > ( true ) . Any ( ) ) )
403+ // Helper method to discover all the properties to validate in the current viewmodel type
404+ static PropertyInfo [ ] GetValidatableProperties ( Type type )
405+ {
406+ return (
407+ from property in type . GetProperties ( BindingFlags . Instance | BindingFlags . Public )
408+ where property . GetIndexParameters ( ) . Length == 0 &&
409+ property . GetCustomAttributes < ValidationAttribute > ( true ) . Any ( )
410+ select property ) . ToArray ( ) ;
411+ }
412+
413+ // Get or compute the cached list of properties to validate. Here we're using a static lambda to ensure the
414+ // delegate is cached by the C# compiler, see the related issue at https://github.com/dotnet/roslyn/issues/5835.
415+ PropertyInfo [ ] propertyInfos = ValidatableProperties . GetValue ( GetType ( ) , static t => GetValidatableProperties ( t ) ) ;
416+
417+ foreach ( PropertyInfo propertyInfo in propertyInfos )
403418 {
404419 object ? propertyValue = propertyInfo . GetValue ( this ) ;
405420
0 commit comments