Непонимание .NET на перегруженных методах с разными параметрами (Call Ambiguous) - PullRequest
0 голосов
/ 02 января 2019

У меня проблема с некоторыми перегруженными методами, и я постараюсь дать простую реализацию этого.

Итак, вот класс, содержащий два метода ниже:

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
    }
}

а это моя сущность:

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

Вот где я его использую:

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

Проблема в том, что у меня просто есть два метода с одинаковыми именами и разными аргументами, поэтому, исходя из концепций ООП-полиморфизма, я ожидаю, что .NET поймет мой нужный метод.

Но это очевидно .NET не может понять это, потому что формы экземпляров Expression<Func<TEntity, Boolean>> и Func<TEntity, Boolean> одинаковы, и это ошибка времени компиляции, которая возникает .NET:

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>)'

Вопрос: как я могу предотвратить эту ошибку во время компиляции?

Я предпочитаю не трогать способ, которым я звоню GetData() в этой строке:

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

Ответы [ 5 ]

0 голосов
/ 10 января 2019

Рассмотрите возможность использования наследования интерфейса для этих двух методов.В соответствии с принципами SOLID вы должны избегать использования ссылок конкретного типа, а вместо этого вы должны использовать абстракции через интерфейсы.Примерно так:

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
        }
    }

Теперь, в зависимости от того, как вы хотите выполнять свои запросы, вы должны использовать переменную того типа интерфейса, который вам нужен.Теперь у вас должна быть одна ссылка на интерфейс, ссылающаяся на один и тот же экземпляр Repo.

IQueryDataByPredicateExpression  queryRepoWithPredicateExpression = myRepo as IQueryDataByPredicateExpression;

IQueryDataByPredicate queryRepoWithPredicate = myRepo as IQueryDataByPredicate;


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

Но если вы не можете или хотите изменить способ вызова этих методов, тогда ответ Алексея Левенкова выглядит великолепно

0 голосов
/ 09 января 2019

Я полагаю, что самый простой способ избавиться от путаницы, связанной с перегрузкой, - привести данные к входу перед отправкой в ​​функцию.Это может быть сделано неявно (inline) или в форме определения типизированного ввода (рекомендуемый способ), а не анонимного.Вот как я это проверил, и это работает, не выдавая этого предупреждения.

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);

Видимо, компилятор C # недостаточно точен, чтобы различать два, потому что он требует некоторого эвристического поведения, а анонимные входы по своей сути одинаковы.В любом случае, этот обходной путь может решить проблему.

0 голосов
/ 02 января 2019

Лямбда-выражения (x=> x.Id==1) сами по себе не имеют типа - они автоматически «приводятся» к Expression или Func / делегату соответствующего типа , когда тип известен .Т.е. Почему должно быть приведено лямбда-выражение при предоставлении в виде простого параметра Delegate , имеющего дело с похожей проблемой только между различными типами делегатов.

В вашем случае методы, которые являются потенциальными кандидатами, предлагают как варианты, так и компиляторне могу сделать выбор.

Если вам действительно нужно сохранить одно и то же имя, то вызывающим абонентам придется самим указывать тип:

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

Не думаю, что вы можете использовать метод расширения дляодна из альтернатив, так как поиск остановится на уровне класса.Так что на самом деле наличие методов с разными именами - единственный реальный вариант (если вам нужны оба).Подумайте, достаточно ли версии Expression.В качестве альтернативы вы можете разделить их между различными классами (аналогично тому, как расширения IQueryable принимают Expression, когда аналогичные методы на IEnumerable принимают Func (см. QueryableExtenasions ).

0 голосов
/ 02 января 2019

Я изменил ваш класс и решил проблему:

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);
}
0 голосов
/ 02 января 2019

Проблема в том, что когда вы компилируете выражение, вы создадите метод с той же сигнатурой, что и вторая.

Рекомендую сменить название первого метода

Кроме того, если вы собираетесь использовать Expression, верните IQueryable, чтобы воспользоваться преимуществами отложенного выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...