Reputation: 2748
I am using reflection to create a lambda function. It works with most items I try it with, however on one of the properties it keeps throwing an Ambiguous Match Exception.
The code looks like this. The error happens when it hits Expression.PropertyOrField
. The property I am using is of type decimal?
. I think it might have to do with the fact that it is a nullable type, but I'm not sure.
public static LambdaExpression CreateExpression(Type type, string propertyName, ref Type returnType)
{
var param = Expression.Parameter(type, "x");
Expression body = param;
foreach (var member in propertyName.Split('.'))
{
body = Expression.PropertyOrField(body, member);
}
returnType = body.Type;
return Expression.Lambda(body, param);
}
Upvotes: 6
Views: 1046
Reputation: 478
This is how I solved the issue. I am using Expression.PropertyOrField
in a predicate builder. I have an extension method that gets a deep/nested property using the Expression
builder. I hope it will help someone else.
public static Expression GetDeepProperty(this ParameterExpression param, string propertyName)
{
if (propertyName.IndexOf('.') != -1)
{
return propertyName.Split('.').Aggregate<string, Expression>(param, (c, m) =>
{
try
{
return Expression.PropertyOrField(c, m);
}
catch (Exception ex)
{
var type = c.Type;
var prop = type.GetProperty(m, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
if (prop != null)
{
return Expression.Property(param, prop);
}
var field = type.GetField(m, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
if (field != null)
{
return Expression.Field(param, field);
}
throw ex;
}
});
}
else
{
return Expression.PropertyOrField(param, propertyName);
}
}
This is what the actual code that can fix the ambiguous match issue. Please change the property/field name as per your requirements. My sample is based on a
and b
classes
var o = new b();
var param = Expression.Parameter(o.GetType());
try
{
var prop = Expression.PropertyOrField(param, "Entity").Dump();
}
catch
{
//param.Dump();
var type = param.Type;
var prop = type.GetProperty("Entity", BindingFlags.DeclaredOnly| BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
if(prop != null)
{
Expression.Property(param, prop).Dump();
}
else
{
var field = type.GetField("field", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance).Dump();
if(field != null)
{
Expression.Field(param, field).Dump();
}
}
//type.DeclaredProperties
}
//Testing entities and re-producing ambiguity.
class a
{
public object Entity { get; set; }
public object field;
}
class b:a{
public new int Entity { get; set; }
public new int field;
}
Upvotes: 1
Reputation: 101453
I see only one way such exception might be thrown in this case: you have multiple properties with the same name but different casing, like this:
public class Test {
public decimal? testProp { get; set; }
public decimal? TestProp { get; set; }
}
Or fields:
public class Test {
public decimal? testProp;
public decimal? TestProp;
}
Note that property with the same name might be in any parent class up hierarchy also:
public class BaseTest {
public decimal? testProp { get; set; }
}
public class Test : BaseTest {
public decimal? TestProp { get; set; } // also fails
}
The reason is Expression.PropertyOrField
does this to search for desired property:
type.GetProperty(propertyOrFieldName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
Note BindingFlags.IgnoreCase
and BindingFlags.FlattenHierarchy
. So you cannot tell Expression.PropertyOrField
to search in case-sensitive manner.
However I would argue that to have multiple properties with the same name is a bad practice anyway and should be avoided.
Upvotes: 7