【问题标题】:Misunderstanding of .NET on overloaded methods with different parameters (Call Ambiguous).NET对不同参数的重载方法的误解(Call Ambiguous)
【发布时间】:2019-01-02 04:36:30
【问题描述】:

我有一些重载方法的问题,我会尝试给出一个简单的实现。

所以这是一个包含以下两个方法的类:

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&lt;Func&lt;TEntity, Boolean&gt;&gt; 和Func&lt;TEntity, Boolean&gt; 的实例形式是相同的,这是.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);

【问题讨论】:

    标签: c# polymorphism


    【解决方案1】:

    Lambda 表达式 (x=&gt; x.Id==1) 本身没有类型 - 它们会自动“转换”为匹配类型的表达式或 Func/delegate当类型已知时。 IE。 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 版本是否足够。或者,您可以将它们拆分为不同的类(当IEnumerable 上的类似方法采用 Func 时,IQueryable 的扩展采用 Expression 的方式类似(参见 QueryableExtenasions)。

    【讨论】:

    • 感谢您的回答,似乎可以解决编译时问题,但关键是我必须更改我调用 GetData() 的位置,因为我说我的偏好是避免这种情况,顺便说一句,您的想法似乎很棒且实用。
    【解决方案2】:

    我相信摆脱重载混乱的最简单方法是在将输入发送到函数之前进行转换。这可以隐式(内联)或以定义类型输入(推荐方式)而不是匿名输入的形式完成。这是我测试的方法,它可以在不发出警告的情况下工作。

    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# 编译器不够精确,无法区分两者,因为它需要一些启发式行为,并且匿名输入本质上是相同的。总之,这个变通办法可以解决问题。

    【讨论】:

    • @MohammadNikravesh 只是好奇,但这个答案与@AlexeiLevenkov's answer 有何不同?你在cmets上说过他的答案不是你想要的,但这个是?这两个答案有什么区别,它们不都执行显式转换吗?
    【解决方案3】:

    问题在于,当您编译表达式时,您将创建一个与第二个签名相同的方法。

    我建议你更改第一种方法的名称

    此外,如果您打算使用 Expression 返回一个 IQueryable 以利用延迟执行。

    【讨论】:

      【解决方案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);
      }
      

      【讨论】:

      • 另外,这是另一种更改方法签名的方法。它会解决我的问题,但关键是我必须深入研究我调用 GetData 的所有地方的代码并全部更改,我正在寻找一种解决方案来避免这种情况。顺便说一句,感谢您的有用回答
      • 虽然这确实解决了这个特定的事件,但它并不能解决一般的歧义问题。
      【解决方案5】:

      考虑为这两种方法使用接口继承。根据 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 的答案看起来很棒

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-03-26
        • 2014-10-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多