如何通过反射构建AutoFixture ICustomization以忽略继承自模型基类的属性赋值
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:
- A property selector:
p => p.PropertyName(typeExpression<Func<T, TProperty>>) - A composer transformation:
c => c.Without(...)(typeFunc<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 oneCustomize<T>invocation, we avoid redundant configuration calls and keep the fixture setup clean. - Type Conversion: Since
Without()returnsIPostprocessComposer<T>(which inherits fromISpecimenBuilder), 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 fromDiscosModelBase(a common implementation would checktypeof(DiscosModelBase).IsAssignableFrom(t) && t != typeof(DiscosModelBase)).
内容的提问来源于stack exchange,提问作者ScottishTapWater

