Skip to content

Fix allocations of an unreachable fallback code #528

@fiseni

Description

@fiseni

While working on our benchmarks, I faced a peculiar issue. Some of the unreachable code in our validators and evaluators is allocating memory. It seems an enumerator is created even though we never reach that section. Here is an example.

public bool IsValid<T>(T entity, ISpecification<T> specification)
{
    if (specification is Specification<T> spec)
    {
        if (spec.OneOrManySearchExpressions.IsEmpty) return true;

        if (spec.OneOrManySearchExpressions.SingleOrDefault is { } searchExpression)
        {
            return searchExpression.SelectorFunc(entity)?.Like(searchExpression.SearchTerm) ?? false;
        }

        // The search expressions are already sorted by SearchGroup.
        return IsValid(entity, spec.OneOrManySearchExpressions.List);
    }

    // We'll never reach this point for our specifications.
    // This is just to cover the case where users have custom ISpecification<T> implementation but use our validator.
    // We'll fall back to LINQ for this case.

    foreach (var searchGroup in specification.SearchCriterias.GroupBy(x => x.SearchGroup))
    {
        if (!searchGroup.Any(c => c.SelectorFunc(entity)?.Like(c.SearchTerm) ?? false))
            return false;
    }

    return true;
}

If we move the fallback code to a local function, allocations go away. So, I assume some compiler (or even runtime) optimizations mess up our assumptions. We should apply this fix wherever applicable.

PS. We'll categorize this as a bug. It's not a bug per se, the behavior remains correct, there are just unnecessary allocations.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions