user2657943
user2657943

Reputation: 2748

AmbiguousMatchException in Expression.PropertyOrField

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

Answers (2)

Ali
Ali

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

Evk
Evk

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

Related Questions