我对某些重载方法有疑问,我将尝试给出一个简单的实现。
所以这是一个包含以下两种方法的类:
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
...
}
问题是我只有两种方法具有相同的名称和不同的参数,因此基于OOP多态性概念,我希望.NET能够理解我想要的方法。
但是,.NET 显然不能理解它,因为Expression<Func<TEntity, Boolean>>
和Func<TEntity, Boolean>
的实例形式是相同的,并且这是.NET引发的编译时错误:< / p>
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);
答案 0 :(得分:12)
Lambda表达式(x=> x.Id==1
)本身没有类型-当类型已知时,它们会自动“投射”到匹配类型为的Expression或Func / delegate。即Why must a lambda expression be cast when supplied as a plain Delegate parameter只是在不同的委托人类型之间处理类似的问题。
在您的情况下,可能的候选方法建议变体和编译器都无法选择。
如果您确实必须保留相同的名称,则呼叫者必须自己指定类型:
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)。
答案 1 :(得分:8)
我相信,消除重载混乱的最简单方法是在将输入发送给函数之前先进行强制转换。这可以隐式(内联)完成,也可以以定义类型化输入(推荐方式)的形式完成,而不是匿名输入。这是我测试此方法的方法,它在不发出警告的情况下起作用。
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#编译器不够精确,无法区分两者,因为它需要一些启发式行为,并且匿名输入在本质上是相同的。无论如何,这种解决方法可以解决问题。
答案 2 :(得分:4)
问题在于,当编译表达式时,您将创建一个具有与第二个签名相同的签名的方法。
我建议您更改第一种方法的名称
此外,如果要使用Expression,请返回IQueryable以利用延迟执行。
答案 3 :(得分:4)
我改变了班级并解决了问题:
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);
}
答案 4 :(得分:4)
请考虑对这两种方法使用接口继承。根据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);
但是,如果不能,或者想要更改调用这些方法的方式,那么Alexei Levenkov的答案就很好