Lambda Expressions and Extension methods
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'.
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.
上一篇: C#IEnumerator / yield结构可能不好?
下一篇: Lambda表达式和扩展方法