Jamie Keeling
Jamie Keeling

Reputation: 9966

Invoking generic extension method with parameters

Consider the following extension method:

public static class ValiDoc
{
        public static IEnumerable<RuleDescription> GetRules<T>(this AbstractValidator<T> validator, bool documentNested = false)
        {
             //....
        }
}

An implementation of AbstractValidator:

public class AddressValidator : AbstractValidator<Address>
{
    public AddressValidator()
    {
        RuleFor(address => address.HouseNumber).NotEmpty();
        RuleFor(address => address.StreetName).NotEmpty();
        RuleFor(address => address.PostCode).NotEmpty();
    }
}

I want to invoke the GetRules() extension method on ValiDoc via reflection and pass in an instance of AddressValidator as the first parameter, with a boolean for the second.

Having never played with Reflection before I am pretty overwhelmed, however by following the examples available here I have made some progress.

//Parameter 1
Type type = typeof(AbstractValidator<>);

// Instance of Address
Type constructed = type.MakeGenericType(childValidator.ValidatorType.GetTypeInfo().BaseType.GenericTypeArguments[0]);

// Find the extension method based on the signature I have defined for the usage
// public static IEnumerable<RuleDescription> GetRules<T>(this AbstractValidator<T> validator, bool documentNested = false)
var runtimeMethods = typeof(ValiDoc).GetRuntimeMethods();

MethodInfo generatedGetRules = null;

// Nothing fancy for now, just pick the first option as we know it is GetRules
using (IEnumerator<MethodInfo> enumer = runtimeMethods.GetEnumerator())
{
    if (enumer.MoveNext()) generatedGetRules = enumer.Current;
}

// Create the generic method instance of GetRules()
generatedGetRules = generatedGetRules.MakeGenericMethod(constructed);

//Parameter 1 = Derived from AbstractValidator<T>, Parameter 2 = boolean
var parameterArray = new object[] { childValidator.GetValidator(new PropertyValidatorContext(new ValidationContext(rule.Member.DeclaringType), rule, propertyName)), true };

//Invoke extension method with validator instance
generatedGetRules.Invoke(null, parameterArray);

When executing generatedGetRules.Invoke, I receive the following error message:

'Object of type 'ValiDoc.Tests.TestData.Validators.AddressValidator' cannot be converted to type 'FluentValidation.AbstractValidator1[FluentValidation.AbstractValidator1[ValiDoc.Tests.TestData.POCOs.Address]]'.'

Have I missed anything obvious? I can't tell if I am nearly there or miles away.

Supporting values:

typeof(AbstractValidator<>) = {FluentValidation.AbstractValidator`1[T]}

childValidator.ValidatorType.GetTypeInfo().BaseType.GenericTypeArguments[0] = {ValiDoc.Tests.TestData.POCOs.Address}

constructed = {FluentValidation.AbstractValidator`1[ValiDoc.Tests.TestData.POCOs.Address]}

generatedGetRules = {System.Collections.Generic.IEnumerable1[ValiDoc.Output.RuleDescription] GetRules[AbstractValidator1](FluentValidation.AbstractValidator1[FluentValidation.AbstractValidator1[ValiDoc.Tests.TestData.POCOs.Address]], Boolean)}

Parameter array = {ValiDoc.Tests.TestData.Validators.AddressValidator}, boolean

EDIT: Working implementation:

// Find the extension method based on the signature I have defined for the usage
                // public static IEnumerable<RuleDescription> GetRules<T>(this AbstractValidator<T> validator, bool documentNested = false)
                var runtimeMethods = typeof(ValiDoc).GetRuntimeMethods();

                MethodInfo generatedGetRules = null;

                // Nothing fancy for now, just pick the first option as we know it is GetRules
                using (IEnumerator<MethodInfo> enumer = runtimeMethods.GetEnumerator())
                {
                    if (enumer.MoveNext()) generatedGetRules = enumer.Current;
                }

                // Create the generic method instance of GetRules()
                generatedGetRules = generatedGetRules.MakeGenericMethod(childValidator.ValidatorType.GetTypeInfo().BaseType.GenericTypeArguments[0]);

                //Parameter 1 = Derived from AbstractValidator<T>, Parameter 2 = boolean
                var parameterArray = new object[] { childValidator.GetValidator(new PropertyValidatorContext(new ValidationContext(rule.Member.DeclaringType), rule, propertyName)), true };

                //Invoke extension method with validator instance
                var output = generatedGetRules.Invoke(null, parameterArray) as IEnumerable<RuleDescription>;

Upvotes: 1

Views: 300

Answers (1)

plinth
plinth

Reputation: 49179

In your error message, I see AddressValidator (AbstractValidator<Address>) can't be converted to AbstractValidator<AbstractValidator<Address>>, which is exactly what you're doing. Constructed is not what you want to pass into MakeGenericMethod, I'm assuming, since the method is generic on T, but the first argument is this AbstractValidator<T> so you're getting a double generic specialization instead of what you want.

Upvotes: 1

Related Questions