From ce306a0ae9b72c122f1d4ef075c4307951447045 Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Sun, 3 May 2026 22:24:49 -0700 Subject: [PATCH] Apply small MiniValidator optimizations Refs #78 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/MiniValidation/MiniValidator.cs | 68 +++++++++++++---------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/src/MiniValidation/MiniValidator.cs b/src/MiniValidation/MiniValidator.cs index c4296c5..99c8f36 100644 --- a/src/MiniValidation/MiniValidator.cs +++ b/src/MiniValidation/MiniValidator.cs @@ -378,9 +378,8 @@ private static async Task TryValidateImpl( } // Once we get to this point we have to box the target in order to track whether we've validated it or not - if (validatedObjects.ContainsKey(target)) + if (validatedObjects.TryGetValue(target, out var result)) { - var result = validatedObjects[target]; // If there's a null result it means this object is the one currently being validated // so just skip this reference to it by returning true. If there is a result it means // we already validated this object as part of this validation operation. @@ -436,11 +435,11 @@ private static async Task TryValidateImpl( if (recurse && currentDepth <= MaxDepth) { // Validate IEnumerable - if (target is IEnumerable) + if (target is IEnumerable targets) { RuntimeHelpers.EnsureSufficientExecutionStack(); - var validateTask = TryValidateEnumerable(target, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, prefix, currentDepth); + var validateTask = TryValidateEnumerable(targets, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, prefix, currentDepth); try { @@ -468,11 +467,11 @@ private static async Task TryValidateImpl( { RuntimeHelpers.EnsureSufficientExecutionStack(); - if (propertyDetails.IsEnumerable) + if (propertyDetails.IsEnumerable && propertyValue is IEnumerable propertyValues) { var thePrefix = $"{prefix}{propertyDetails.Name}"; - var validateTask = TryValidateEnumerable(propertyValue, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, thePrefix, currentDepth); + var validateTask = TryValidateEnumerable(propertyValues, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, thePrefix, currentDepth); try { ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync); @@ -486,7 +485,7 @@ private static async Task TryValidateImpl( isValid = await validateTask.ConfigureAwait(false) && isValid; } - else + else if (!propertyDetails.IsEnumerable) { var thePrefix = $"{prefix}{propertyDetails.Name}."; // <-- Note trailing '.' here @@ -509,10 +508,8 @@ private static async Task TryValidateImpl( } } - if (typeof(IValidatableObject).IsAssignableFrom(targetType)) + if (target is IValidatableObject validatable) { - var validatable = (IValidatableObject)target; - // Reset validation context validationContext.MemberName = null; validationContext.DisplayName = validationContext.ObjectType.Name; @@ -524,15 +521,13 @@ private static async Task TryValidateImpl( } } - if ((isValid || allowAsync) && typeof(IAsyncValidatableObject).IsAssignableFrom(targetType)) + if ((isValid || allowAsync) && target is IAsyncValidatableObject asyncValidatable) { - var validatable = (IAsyncValidatableObject)target; - // Reset validation context validationContext.MemberName = null; validationContext.DisplayName = validationContext.ObjectType.Name; - var validateTask = validatable.ValidateAsync(validationContext); + var validateTask = asyncValidatable.ValidateAsync(validationContext); ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync); var validatableResults = await validateTask.ConfigureAwait(false); @@ -566,7 +561,7 @@ private static async ValueTask TryValidateEnumerable( #else private static async Task TryValidateEnumerable( #endif - object target, + IEnumerable items, IServiceProvider? serviceProvider, bool recurse, bool allowAsync, @@ -577,34 +572,31 @@ private static async Task TryValidateEnumerable( int currentDepth = 0) { var isValid = true; - if (target is IEnumerable items) + // Validate each instance in the collection + var index = 0; + foreach (var item in items) { - // Validate each instance in the collection - var index = 0; - foreach (var item in items) + if (item is null) { - if (item is null) - { - continue; - } - - var itemPrefix = $"{prefix}[{index}]."; + continue; + } - var validateTask = TryValidateImpl(item, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, itemPrefix, currentDepth + 1); - try - { - ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync); - } - catch (Exception) - { - // Always observe the ValueTask - _ = await validateTask.ConfigureAwait(false); - throw; - } + var itemPrefix = $"{prefix}[{index}]."; - isValid = await validateTask.ConfigureAwait(false) && isValid; - index++; + var validateTask = TryValidateImpl(item, serviceProvider, recurse, allowAsync, workingErrors, validatedObjects, validationResults, itemPrefix, currentDepth + 1); + try + { + ThrowIfAsyncNotAllowed(validateTask.IsCompleted, allowAsync); } + catch (Exception) + { + // Always observe the ValueTask + _ = await validateTask.ConfigureAwait(false); + throw; + } + + isValid = await validateTask.ConfigureAwait(false) && isValid; + index++; } return isValid; }