Mohammad Nikravesh
Mohammad Nikravesh

Reputation: 975

Misunderstanding of .NET on overloaded methods with different parameters (Call Ambiguous)

I have a problem with some overloaded methods and I will try to give a simple implementation of it.

So here is a class contains two methods below:

public class MyRepo<TEntity>
{
    public List<TEntity> GetData(Expression<Func<TEntity, Boolean>> expression)
    {
        //Do something
    }

    public List<TEntity> GetData(Func<TEntity,Boolean> whereClause)
    {
        //Do something
    }
}

and this my entity:

public class MyEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Here is where I'm utilizing it:

{
    ...
    MyRepo<MyEntity> myRepo = new MyRepo<MyEntity>();
    myRepo.GetData(x => x.Id == 1); // The ambiguity point
    ...
}

The problem is that I just have two methods with same name and different arguments so, based on OOP polymorphism concepts, I expect .NET to understand my desired method.

But it's obvious .NET cannot understand it because the instance form of Expression<Func<TEntity, Boolean>> and Func<TEntity, Boolean> are the same and this the compile-time error which .NET raises:

The call is ambiguous between the following methods or properties:
    'Program.MyRepo<TEntity>.GetData(Expression<Func<TEntity, bool>>)' and
    'Program.MyRepo<TEntity>.GetData(Func<TEntity, bool>)'

The question is: how can I prevent this compile-time error?

My preference is to do not touch the way I'm calling GetData() at this line:

myRepo.GetData(x => x.Id == 1);

Upvotes: 14

Views: 525

Answers (5)

Vitor Paulino
Vitor Paulino

Reputation: 164

Consider using Interface inheritance for those two methods. According to SOLID principles you should avoid using references of a concrete type, and instead you should use abstractions via Interfaces. Something like this:

public interface IQueryDataByPredicateExpression 
{
List<TEntity> GetData(Expression<Func<TEntity, Boolean>> whereClause);
}

public interface IQueryDataByPredicate
{
List<TEntity> GetData(Func<TEntity,Boolean> whereClause);

}

    public class MyRepo<TEntity> : IQueryDataByPredicateExpression, IQueryDataByPredicate
    {
        public List<TEntity> GetData(Expression<Func<TEntity, Boolean>> expression)
        {
            //Do something
        }

        public List<TEntity> GetData(Func<TEntity,Boolean> whereClause)
        {
            //Do something
        }
    }

Now depending the way you want to do your queries, you should use the variable of the interface type that you desire. Now you should have one reference per interface referencing the same instance of Repo.

IQueryDataByPredicateExpression  queryRepoWithPredicateExpression = myRepo as IQueryDataByPredicateExpression;

IQueryDataByPredicate queryRepoWithPredicate = myRepo as IQueryDataByPredicate;


 queryRepoWithPredicateExpression.GetData(x => x.Id == 1);
 queryRepoWithPredicate.GetData(x => x.Id == 2);

But if you cannot, or want change the way you are calling those methods then Alexei Levenkov answer looks great

Upvotes: 4

Siavash Rostami
Siavash Rostami

Reputation: 1933

I believe the simplest way you can get rid of overloading confusion is to cast your input prior to sending it to the function. This can be done implicitly(inline) or in the form of defining a typed input(recommended way) rather than an anonymous one. Here is how i tested this and it works without giving off that warning.

MyRepo<MyEntity> myRepo = new MyRepo<MyEntity>();
Func<MyEntity, bool> predicate = x => x.Id == 1;
Expression<Func<MyEntity, bool>> expression = x => x.Id == 1;
// both below lines are fine now
myRepo.GetData(predicate);
myRepo.GetData(expression);

Apparently C# compiler is not precise enough to differentiate between the two because it demands some heuristic behavior, and anonymous inputs are inherently same. Anyhow, this workaround can solve the issue.

Upvotes: 8

CodeMan
CodeMan

Reputation: 706

I changed your class and solved the problem:

public class MyRepo<TEntity>
{
    public void GetData(Expression<Func<TEntity, bool>> expression, out List<TEntity> result)
    {
        result = null;
    }

    public List<TEntity> GetData(Func<TEntity, bool> whereClause)
    {
        return null;
    }
}

private void button1_Click(object sender, EventArgs e)
{
    var myRepo = new MyRepo<MyEntity>();
    var i = myRepo.GetData(x => x.Id == 1);
    myRepo.GetData(x => x.Id == 1, out i);
}

Upvotes: 4

Alexei Levenkov
Alexei Levenkov

Reputation: 100527

Lambda expressions (x=> x.Id==1) do not have type by themselves - they automatically "cast" to Expression or Func/delegate of matching type when type is known. I.e. Why must a lambda expression be cast when supplied as a plain Delegate parameter deals with similar issue just between different delegate types.

In your case methods that are potential candidate suggest both variants and compiler can't make a choice.

If you really have to keep same name then callers will have to specify type themselves:

 myRepo.GetData((Expression<Func<TEntity, Boolean>>)(x => x.Id == 1));
 myRepo.GetData((Func<TEntity, Boolean>)(x => x.Id == 2));

I don't think you can use extension method for one of alternatives as search will stop at the class level. So really having methods with different names is the only real option (if you need both). Consider if just Expression version is enough. Alternatively you can split them between different classes (similar how extensions of IQueryable take Expression when similar methods on IEnumerable take Func (see QueryableExtenasions).

Upvotes: 12

The problem is that when you compile the expression you will create a method with that same signature that the second.

I recommend you change the name of the first method

Also If you are going to use Expression return an IQueryable to take advantage of the deferred execution.

Upvotes: 4

Related Questions