Search code examples
c#lambdareflectionexpression

C# reflection how to invoke static extensions method with expression lambda parameter


I defined an interface and a class like this:

public interface IXmlKey
{
    string Name { get; set; }
    string Value { get; set; }
    bool HasChildren { get; set; }
}

public interface IXmlKey<T> : IXmlKey where T : IXmlKey<T>
{
    public T[] Children { get; set; }
}

and a method:

private static bool ReadSubXmlKeys(XmlReader subReader, IXmlKey objInstance, bool hasChildren, out string error)
{
    error = null;

    var childrenProp = objInstance.GetType().GetProperties().Single(p => p.Name.Equals("Children"));
    var childType = childrenProp.PropertyType.GetElementType(); 
    var childPis = childType.GetProperties();

    var generateList = typeof(List<>).MakeGenericType(childType);

    var childrenList = Activator.CreateInstance(generateList); 
    var childrenListType = childrenList.GetType();

    //public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    var fun = typeof(Func<,>).MakeGenericType(childType, typeof(bool));

    var anyMethod = typeof(Enumerable).GetMethodWithLinq("Any", typeof(IEnumerable<>), typeof(Func<,>)).MakeGenericMethod(childType);
    
    var startNodeName = objInstance.Name;

    do
    {
        var nodeName = subReader.Name;
        //var isExist = anyMethod.Invoke(null, [childrenList, <what here>]); ---> here
    }
    while (subReader.Read());

    return true;
}

As you can see, I'm not sure what the correct parameter is to invoke

List<T>.Any(t => t.Name.Equals(nodeName )) where T : IXmlKey

Solution

  • You need to pass the Func<childType,bool> predicate.

    One way to have an instance of it is to dynamically build an Expression and compile it like this:

    ...
    var captureClass = new Capture();
    var parameterExpression = Expression.Parameter(childType, "t");
    var propertyInfo = childType.GetProperty(nameof(IXmlKey.Name));
    var propertyCall = Expression.Property(parameterExpression, propertyInfo);
    var fieldAccess = Expression.Field(Expression.Constant(captureClass, typeof(Capture)), nameof(Capture.Name));
    var equalsMethod = typeof(string).GetMethod("Equals", [typeof(string)]);
    var expCall = Expression.Call(propertyCall, equalsMethod, fieldAccess);
    var lambda = Expression.Lambda(expCall, [parameterExpression]);
    var compiledPredicate = lambda.Compile();
    ...    
    
    do
    {
        captureClass.Name = subReader.Name;
        var isExist = anyMethod.Invoke(null, [childrenList, compiledPredicate]);
    
    }
    while (subReader.Read());
    
    return true;
    

    for optimization you need to have this class too:

    public class Capture {
        public string Name;
    }
    

    full example on dotnetfiddle

    However, there is a better alternative in my opinion and that is to just create a generic method to forward to with your initial Any logic:

    private static bool ReadSubXmlKeys<T>(XmlReader subReader, T objInstance, bool hasChildren, out string error) 
    where T:IXmlKey<T>{
        var name = "example";
        var result = objInstance.Children.Any(t => t.Name.Equals(name));
    }
    

    You still need a bit of reflection to call it at the beginning of the original non-generic method:

    var genericDefinition = objInstance.GetType().GetInterfaces()
            .Where(x => x.IsGenericType)
            .Where(x => x.GetGenericTypeDefinition() == typeof(IXmlKey<>));
    
    if (genericDefinition.Count() == 1) {
    
        // you can cache this in a static field too
        var genericMethodInfo = typeof(YourTypeThatHasTheStaticMethods)
            .GetMethod(nameof(ReadSubXmlKeysGeneric), BindingFlags.NonPublic | BindingFlags.Static);
    
        var genericMethod = genericMethodInfo.MakeGenericMethod(
            genericDefinition.First().GetGenericArguments()[0]);
    
        return (bool)genericMethod.Invoke(null, [subReader, objInstance...]);
    }
    ... no need for the first solution
    ... since it relied on the Children property which is only
    ... there for IXmlKey<T>