You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过反射构建AutoFixture ICustomization以忽略继承自模型基类的属性赋值

Solution to Your AutoFixture Customization Problem

First off, yes, your requirement is absolutely feasible—AutoFixture's extensibility model is built to handle exactly this kind of reflection-driven customization. The missing piece is manually constructing the expression trees needed to call Without() for each nested DiscosModelBase property. Let's break down how to implement this step by step.

Step 1: Understand the Expression Tree Requirements

To replicate c => c.Without(p => p.PropertyName) via reflection, we need two nested expressions:

  1. A property selector: p => p.PropertyName (type Expression<Func<T, TProperty>>)
  2. A composer transformation: c => c.Without(...) (type Func<ICustomizationComposer<T>, ISpecimenBuilder>)

We'll build both dynamically using reflection and expression tree APIs.

Step 2: Build the Chained Customization Lambda

Instead of calling Customize<T> for every single property (which works but is inefficient), we'll chain all Without() calls into one customization per model type. Here's the full, revised customization class:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoFixture;
using AutoFixture.Kernel;

public class DiscosModelFixtureCustomizationNoLinks : ICustomization
{
    public void Customize(IFixture fixture)
    {
        // Get all non-abstract DiscosModelBase types
        Type[] discosModelTypes = typeof(DiscosModelBase).Assembly.GetTypes()
            .Where(t => t.IsDiscosModel() && !t.IsAbstract)
            .ToArray();

        // Get the generic Customize<T> method from IFixture
        MethodInfo customizeMethod = fixture.GetType().GetMethods()
            .Single(m => m.Name == nameof(fixture.Customize) && m.IsGenericMethod);

        foreach (Type modelType in discosModelTypes)
        {
            // Get all properties that are also DiscosModelBase instances
            PropertyInfo[] propsToIgnore = modelType.GetProperties()
                .Where(p => p.PropertyType.IsDiscosModel())
                .ToArray();

            if (!propsToIgnore.Any())
                continue; // Skip types with no nested model properties

            // Build a single lambda that chains all Without calls
            object composerLambda = BuildChainedComposerLambda(modelType, propsToIgnore);

            // Invoke the generic Customize<T> method with our lambda
            MethodInfo constructedCustomizeMethod = customizeMethod.MakeGenericMethod(modelType);
            constructedCustomizeMethod.Invoke(fixture, new[] { composerLambda });
        }
    }

    private static object BuildChainedComposerLambda(Type modelType, PropertyInfo[] propertiesToIgnore)
    {
        Type composerType = typeof(ICustomizationComposer<>).MakeGenericType(modelType);
        ParameterExpression composerParam = Expression.Parameter(composerType, "c");

        // Start with the composer parameter, then chain each Without call
        Expression currentExpression = composerParam;

        foreach (PropertyInfo prop in propertiesToIgnore)
        {
            // Get the generic Without method for this property's type
            MethodInfo withoutMethod = composerType.GetMethod(
                nameof(ICustomizationComposer<object>.Without),
                new[] { typeof(Expression<>) })
                .MakeGenericMethod(prop.PropertyType);

            // Build the property selector expression: p => p.PropertyName
            ParameterExpression modelParam = Expression.Parameter(modelType, "p");
            MemberExpression propertyAccess = Expression.Property(modelParam, prop);
            Expression propertySelector = Expression.Lambda(
                typeof(Func<,>).MakeGenericType(modelType, prop.PropertyType),
                propertyAccess,
                modelParam);

            // Chain the Without call to the current expression
            currentExpression = Expression.Call(
                currentExpression,
                withoutMethod,
                propertySelector);
        }

        // Convert the final chained result to ISpecimenBuilder (matches the delegate return type)
        UnaryExpression convertToSpecimenBuilder = Expression.Convert(currentExpression, typeof(ISpecimenBuilder));

        // Create and compile the final lambda
        LambdaExpression composerLambda = Expression.Lambda(
            typeof(Func<,>).MakeGenericType(composerType, typeof(ISpecimenBuilder)),
            convertToSpecimenBuilder,
            composerParam);

        return composerLambda.Compile();
    }
}

Key Details to Note

  • Chaining Efficiency: By chaining all Without() calls in one Customize<T> invocation, we avoid redundant configuration calls and keep the fixture setup clean.
  • Type Conversion: Since Without() returns IPostprocessComposer<T> (which inherits from ISpecimenBuilder), we add an explicit conversion to match the delegate's return type requirement.
  • Reflection Safety: Ensure your IsDiscosModel() extension method correctly identifies types that inherit from DiscosModelBase (a common implementation would check typeof(DiscosModelBase).IsAssignableFrom(t) && t != typeof(DiscosModelBase)).

内容的提问来源于stack exchange,提问作者ScottishTapWater

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 00:37:46