Lambda Expressions and Extension methods

c# dynamic-linq extension-methods lambda linq

Question

So I have an extension method defined like so

  public static String FormatString(this String source, String formatString, Object[] parameters)
  {
               return String.Format(formatString, parameters);
  }

and from investigation, I have come to learn that when the above method is invoked, it expects 3 parameters and not two even though in code I would invoke it on a String instance like so

String StringInstance = "MYSTRINGINSTANCE";
String StringFormatExpression = "it.FormatString(\"ui{0:D6}\", @0 )";
**  var ,args = new[] {"1", "Another", "YetAnother"}; <-- an array **
//StringInstance.FormatString(StringFormatExpression ,args );

Trying to create a lambda representation of this, I have the following


return Expression.Call(type, 
                       method.Name, 
                       args.Select(x=>x.GetType()).ToArray<Type>(),
                       args);

When I do invoke this, I get the following error message.

No method 'FormatString' exists on type 'System.String'.

and below is the method signature.

-{System.String FormatString(System.String, System.String, System.Object[])} System.Reflection.MethodBase {System.Reflection.RuntimeMethodInfo}

Somehow it still cannot find my Extension method to call.

When I do specify the static class in which the extension method is defined, and pass that as the first argument to the Expression.Call call, like so,

UPDATE

A.

As Requested, here is more of the code for you to see.

So I created extension methods which simplify File-related processing for me, like so.

public static IEnumerable ForEach(this IEnumerable source, string expression, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (expression == null) throw new ArgumentNullException("selector"); var enumerableList = source.AsEnumerable();

     IEnumerable<T> finalList = from T item in source
                     select (T) DynamicLambdaExpression.ParseLambda(item.GetType(), typeof(T), expression, values).Compile().DynamicInvoke(item);

     return finalList;
}

public static IEnumerable ForEachFileName(this IEnumerable sourceFilePaths, string expression, object[] values) { if (sourceFilePaths == null) throw new ArgumentNullException("source"); IDictionary source = sourceFilePaths.Select((value, index) => { value = Path.GetFileNameWithoutExtension(value); return new { index, value }; } ).ToDictionary(x => x.index, v => v.value); if (expression == null) throw new ArgumentNullException("selector");

       IEnumerable<String> finalList = from int index in source.Keys
                                       select (String)DynamicLambdaExpression.ParseLambda(source[index].GetType(), typeof(String), expression, values).Compile().DynamicInvoke(source[index]);

       return finalList;
  }

B.

Then in my test, I have the following

String StringFormatExpression = "it.FormatString(\"ui{0:D6}\", @0 )";
var param = new[] {"1", "Another", "YetAnother"};

String result = new[] { "MyStringValue", "MySecondStringValue", "MyThirdString"}.ForEachFileName(StringFormatExpression, param).FirstOrDefault();

 Console.WriteLine(result);

C. As the DynamicQuery library was not able to find the extension methods I had defined earlier for my strings, I modified the code to capture extension methods as well.

        internal partial class ExpressionParser
 {
      Expression ParseMemberAccess(Type type, Expression instance)
      {
           if (instance != null) type = instance.Type;
           int errorPos = token.pos;
           string id = GetIdentifier();
           NextToken();
           if (token.id == TokenId.OpenParen)
           {
                if (instance != null && type != typeof(string))
                {
                     Type enumerableType = FindGenericType(typeof(IEnumerable<>), type);
                     if (enumerableType != null)
                     {
                          Type elementType = enumerableType.GetGenericArguments()[0];
                          return ParseAggregate(instance, elementType, id, errorPos);
                     }
                }
                Expression[] args = ParseArgumentList();
                MethodBase mb;

                switch (FindMethod(type, id, instance == null,instance, args,  out mb))
                {
                     case 0:
                          throw ParseError(errorPos, Res.NoApplicableMethod,
                              id, GetTypeName(type));
                     case 1:
                          MethodInfo method = (MethodInfo)mb;
                          if ((method.DeclaringType.IsAbstract && method.DeclaringType.IsSealed))
                          {
                               if (method.ReturnType == typeof(void))
                                    throw ParseError(errorPos, Res.MethodIsVoid,
                                        id, GetTypeName(method.DeclaringType));

                               Type t = mb.ReflectedType;
                               var combined = instance.Concatenate(args);
                               return Expression.Call(method, combined);
                          }

                          else if (IsPredefinedType(method.DeclaringType))
                          {
                               if (method.ReturnType == typeof(void))
                                    throw ParseError(errorPos, Res.MethodIsVoid,
                                        id, GetTypeName(method.DeclaringType));
                               return Expression.Call(instance, (MethodInfo)method, args);
                          }
                          else
                          {

                               throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType));

                          }
                     default:
                          throw ParseError(errorPos, Res.AmbiguousMethodInvocation,
                              id, GetTypeName(type));
                }//end of switch method here 
           }
           else
           {
                MemberInfo member = FindPropertyOrField(type, id, instance == null);
                if (member == null)
                     throw ParseError(errorPos, Res.UnknownPropertyOrField,
                         id, GetTypeName(type));
                return member is PropertyInfo ?
                    Expression.Property(instance, (PropertyInfo)member) :
                    Expression.Field(instance, (FieldInfo)member);
           }
      }


      /// <summary>
      /// Comment Added 9/13/2013
      /// An Extension method would require that the instance be the first argument in it's parameter list
      /// </summary>
      /// <param name="type"></param>
      /// <param name="methodName"></param>
      /// <param name="staticAccess"></param>
      /// <param name="args"></param>
      /// <param name="method"></param>
      /// <returns></returns>
      int FindMethod(Type type, string methodName, bool staticAccess, Expression instance, Expression[] args, out MethodBase method)
      {
           if (type.IsGenericParameter)
           {
                BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly |
                    (staticAccess ? BindingFlags.Static : BindingFlags.Instance);

                foreach (Type t in SelfAndBaseTypes(type))
                {
                     MemberInfo[] members = t.FindMembers(MemberTypes.Method,
                         flags, Type.FilterNameIgnoreCase, methodName);
                     int count = FindBestMethod(members.Cast<MethodBase>(), instance, args, out method);
                     if (count != 0) return count;
                }
           }
           else
           {
                IEnumerable<Type> selfAndBaseTypes = SelfAndBaseTypes(type);

                foreach (Type t in selfAndBaseTypes)
                {
                     List<MethodInfo> methodinfos = new List<MethodInfo>();
                     methodinfos.AddRange(t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
                     //also add extension methods
                     methodinfos.AddRange(t.GetExtensionMethods());
                     int count = FindBestMethod(methodinfos.Cast<MethodBase>(), instance, args, out method);
                     if (count != 0) return count;
                }


           }
           method = null;
           return 0;
      }

      /// <summary>
      /// Comment Added 9/13/2013
      /// An Extension method would require that the instance be the first argument in it's parameter list
      /// </summary>
      /// <param name="type"></param>
      /// <param name="methodName"></param>
      /// <param name="staticAccess"></param>
      /// <param name="args"></param>
      /// <param name="method"></param>
      /// <returns></returns>
      int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method)
      {
           if (type.IsGenericParameter)
           {
                BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly |
                    (staticAccess ? BindingFlags.Static : BindingFlags.Instance);

                foreach (Type t in SelfAndBaseTypes(type))
                {
                     MemberInfo[] members = t.FindMembers(MemberTypes.Method,
                         flags, Type.FilterNameIgnoreCase, methodName);
                     int count = FindBestMethod(members.Cast<MethodBase>(), args, out method);
                     if (count != 0) return count;
                }
           }
           else
           {
                IEnumerable<Type> selfAndBaseTypes = SelfAndBaseTypes(type);

                foreach (Type t in selfAndBaseTypes)
                {
                     List<MethodInfo> methodinfos = new List<MethodInfo>();
                     methodinfos.AddRange(t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));
                     //also add extension methods
                     methodinfos.AddRange(t.GetExtensionMethods());
                     int count = FindBestMethod(methodinfos.Cast<MethodBase>(), args, out method);
                     if (count != 0) return count;
                }


           }
           method = null;
           return 0;
      }

      int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
      {
           //search for the best base method 
           int length = FindBestInstanceMethod(methods, args, out method);

           return length;
      }

      int FindBestMethod(IEnumerable<MethodBase> methods, Expression instance, Expression[] args, out MethodBase method)
      {
            //search for the best base method 
               int length = FindBestInstanceMethod(methods, args, out method);
               //in the case that no best method is found that way, try a search for Extension methods
               if(length == 0)
                    length = FindBestExtensionMethod(methods, instance, args, out method);
               return length;
      }

      private int FindBestExtensionMethod(IEnumerable<MethodBase> methods, Expression instance, Expression[] args, out MethodBase method)
      {
           MethodData[] applicable = methods.
                 Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }).
                 Where(m => m.MethodBase.IsDefined(typeof(ExtensionAttribute), false) 
                      && IsApplicableExtensionMethod(m, instance, args)).
                 ToArray();
           if (applicable.Length > 1)
           {
                applicable = applicable.
                    Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))).
                    ToArray();
           }
           if (applicable.Length == 1)
           {
                MethodData md = applicable[0];

                for (int i = 0; i < args.Length; i++) args[i] = md.Args[i];
                method = md.MethodBase;
           }
           else
           {
                method = null;
           }
           return applicable.Length;
      }

      private int FindBestInstanceMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
      {
           MethodData[] applicable = methods.
              Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }).
              Where(m => IsApplicable(m, args)).
              ToArray();

           if (applicable.Length > 1)
           {
                applicable = applicable.
                    Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))).
                    ToArray();
           }
           if (applicable.Length == 1)
           {
                MethodData md = applicable[0];
                for (int i = 0; i < args.Length; i++) args[i] = md.Args[i];
                method = md.MethodBase;
           }
           else
           {
                method = null;
           }
           return applicable.Length;
      }


      bool IsApplicableExtensionMethod(MethodData method, Expression instance, Expression[] args)
      {
           if ((method.Parameters.Length - 1) != (args.Length)) return false;

           var argsource = instance.Concatenate(args);
           Expression[] promotedArgs = new Expression[argsource.Length];
           for (int i = 0; i < (argsource.Length); i++)
           {   
                ParameterInfo pi = method.Parameters[i];
                if (pi.IsOut) return false;
                Expression promoted = PromoteExpression(argsource[i], pi.ParameterType, false);
                if (promoted == null) return false;
                promotedArgs[i] = argsource[i];
           }
           method.Args = promotedArgs;
           return true;
      }


      bool IsApplicable(MethodData method, Expression[] args)
      {
           if (method.Parameters.Length != args.Length) return false;
           Expression[] promotedArgs = new Expression[args.Length];
           for (int i = 0; i < args.Length; i++)
           {
                ParameterInfo pi = method.Parameters[i];
                if (pi.IsOut) return false;
                Expression promoted = PromoteExpression(args[i], pi.ParameterType, false);
                if (promoted == null) return false;
                promotedArgs[i] = promoted;
           }
           method.Args = promotedArgs;
           return true;
      }

      Expression PromoteExpression(Expression expr, Type type, bool exact)
      {
           if (expr.Type == type) return expr;
           if (expr is ConstantExpression)
           {
                ConstantExpression ce = (ConstantExpression)expr;
                if (ce == nullLiteral)
                {
                     if (!type.IsValueType || IsNullableType(type))
                          return Expression.Constant(null, type);
                }
                else
                {
                     string text;
                     if (literals.TryGetValue(ce, out text))
                     {
                          Type target = GetNonNullableType(type);
                          Object value = null;
                          switch (Type.GetTypeCode(ce.Type))
                          {
                               case TypeCode.Int32:
                               case TypeCode.UInt32:
                               case TypeCode.Int64:
                               case TypeCode.UInt64:
                                    value = ParseNumber(text, target);
                                    break;
                               case TypeCode.Double:
                                    if (target == typeof(decimal)) value = ParseNumber(text, target);
                                    break;
                               case TypeCode.String:
                                    value = ParseEnum(text, target);
                                    break;
                          }
                          if (value != null)
                               return Expression.Constant(value, type);
                     }
                }
           }
           if (IsCompatibleWith(expr.Type, type))
           {
                if (type.IsValueType || exact) return Expression.Convert(expr, type);
                return expr;
           }
           return null;
      }

 }

D. When I run the above, I get the following Error message

No method 'FormatString' exists on type 'System.String'.

1
1
9/13/2013 7:35:41 PM

Popular Answer

Extension methods don't add a method to the type's definition. They are merely syntactic sugar for a static method call. The Type that you are passing in for the containing type of FormatStrign is for string, but it should be for whatever type FormatString is defined in. If you defined it in a class called StringExtensions then that is the Type you need to pass in.

1
9/13/2013 3:03:11 PM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow