【问题标题】:How to remove ambiguous method call in c#如何在c#中删除模棱两可的方法调用
【发布时间】:2017-06-21 10:26:44
【问题描述】:

这里我有一个简单的场景,有两种方法,我从彼此那里得到一个模棱两可的调用:

这是我的代码:

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsNoSubsYpdcResult, bool> predicate = null)
    => predicate == null
        ? Mapper.Map<IEnumerable<JobsViewModel>>(_procedureService.Tech_GetJobsNoSubsYPDC(vesselId, status))
        : Mapper.Map<IEnumerable<JobsViewModel>>(_procedureService.Tech_GetJobsNoSubsYPDC(vesselId, status).Where(predicate));

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsViewModel, bool> predicate = null)
    => predicate == null
        ? GetJobsViewModels(vesselId, status)
        : GetJobsViewModels(vesselId, status).Where(predicate);

我不喜欢更改方法的名称,但从第二个开始我收到错误消息:

模棱两可的调用

我想像我想要做的那样从第二个调用第一个,有人知道我怎样才能在不更改方法名称的情况下做到这一点,否则我应该更改它们?

【问题讨论】:

  • 请显示给您该错误的调用。
  • @LasseV.Karlsen:我认为它已经存在了。第二个表达式的主体包含对所描述方法的调用。
  • 如果跳过可选参数,这两种方法完全一样,编译器应该怎么知道?
  • 或者,如果您省略它,您可以简单地删除您希望它调用的重载的默认值,可能是第二个。换句话说,只有在您实际指定委托时才可调用第二个重载。
  • Lasse 非常到位。没有谓词,第二个重载只会转发到第一个重载 - 所以真的没有必要在没有谓词的情况下调用第二个,所以它不需要默认值。

标签: c# overloading


【解决方案1】:

处理这个问题的最好方法是不要有两个模棱两可的重载,这样你就不需要记住如何在你调用它们的任何地方对它进行特殊处理。

由于您的第二个重载调用第一个,我想说最合乎逻辑的行动原因是删除该委托的默认参数值:

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsViewModel, bool> predicate)
    => predicate == null
        ? GetJobsViewModels(vesselId, status)
        : GetJobsViewModels(vesselId, status).Where(predicate);

您还应该对指定委托参数调用此方法的能力进行推理,但明确指定null。如果这不是真的意味着可能无论如何(即三元表达式的第一部分不会真正使用),那么我将简化整个方法:

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsViewModel, bool> predicate)
    => GetJobsViewModels(vesselId, status).Where(predicate);

现在,说了这么多,无论如何我都没有真正看到第二种方法的意义,因为在外面我可以简单地塞上.Where(...) 过滤器。由于该方法没有对这个谓词做任何聪明的事情,比如传递它并在较低级别上执行它,或者优化它获取数据的方式,所以这实际上只是语法糖。

然而,这只是我的意见,我承认我不知道这一切将如何被使用。我真正的答案是第一部分,你真的应该避免声明模棱两可的方法。


另请参阅此问题:Breaking change in method overload resolution in C# 6 - explanation?

当 Roslyn 进入现场时,重载分辨率发生了变化,他们决定记录更改并使其破坏而不是重新引入旧的“错误”。

【讨论】:

  • “我真的不明白第二种方法的意义,因为在外面我可以简单地塞上.Where(...)” - 嗯,它可能有如果 OP 需要提供一个带有特定签名的方法,该方法需要一个谓词(因为外部代码会像通过委托那样调用它),它的用途。
  • 我本来想这样做,但真正的问题在于两个模型的属性。 JobsViewModelJobsViewModel 非常不同,但在幕后 AutoMapper 正在完成从 JobsViewModel 模型属性提供 JobsViewModel 模型属性的工作,因此短期内我在不同情况下都需要它们。
  • 对,明确了“不需要”的部分,答案真的是第一部分,不要声明模棱两可的方法。
【解决方案2】:

您需要提供可选参数并让编译器清楚其类型,即使值为null

GetJobsViewModels(vesselId, status, (Func<JobsNoSubsYpdcResult, bool>)null)

编辑:在 OP 提出的特定情况下,Lasse's solution 更可取。

另一方面,此答案中显示的解决方案通常可以应用,也可以在消除另一个重载没有可选参数的重载时(例如,在调用某些 @ 时) 987654322@构造函数)。

【讨论】:

    【解决方案3】:

    这样做的唯一方法是用适当类型的值填充可选参数,以便编译器知道要选择哪个重载。例如:

    public IEnumerable<JobsViewModel> GetJobsViewModels(
        Guid vesselId,
        int status,
        Func<JobsViewModel, bool> predicate = null)
    {
        // We're never filtering by JobsNoSubsYpdcResult, but this
        // satisfies overload resolution
        Func<JobsNoSubsYpdcResult, bool> resultPredicate = null;
        return predicate == null
            ? GetJobsViewModels(vesselId, status, resultPredicate)
            : GetJobsViewModels(vesselId, status, resultPredicate).Where(predicate);
    }
    

    或者为了避免重复:

    public IEnumerable<JobsViewModel> GetJobsViewModels(
        Guid vesselId,
        int status,
        Func<JobsViewModel, bool> predicate = null)
    {
        // We're never filtering by JobsNoSubsYpdcResult, but this
        // satisfies overload resolution
        Func<JobsNoSubsYpdcResult, bool> resultPredicate = null;
        var allResults = GetJobsViewModels(vesselId, status, resultPredicate);
        return predicate == null
            ? allResults : allResults.Where(predicate);
    }
    

    或者为了避免更多重复,引入你自己的扩展方法:

    public static IEnumerable<T> OptionalWhere<T>(
        this IEnumerable<T> source,
        Func<T, bool> predicate) =>
        predicate == null ? source : source.Where(predicate);
    

    然后你可以重写这两种方法来使用它:

    public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsNoSubsYpdcResult, bool> predicate = null) =>
        Mapper.Map<IEnumerable<JobsViewModel>>(_procedureService.Tech_GetJobsNoSubsYPDC(vesselId, status))
            .OptionalWhere(predicate);
    
    public IEnumerable<JobsViewModel> GetJobsViewModels(
        Guid vesselId,
        int status,
        Func<JobsViewModel, bool> predicate = null)
    {
        // We're never filtering by JobsNoSubsYpdcResult, but this
        // satisfies overload resolution
        Func<JobsNoSubsYpdcResult, bool> resultPredicate = null;
        return GetJobsViewModels(vesselId, status, resultPredicate)
            .OptionalWhere(predicate);
    }
    

    (如果您愿意将null 转换为Func&lt;JobsNoSubsYpdcResult, bool&gt; 或为此使用静态字段,您可以使用表达式主体方法。)

    【讨论】:

    • 非常感谢@Jon Skeet 解决重复问题。这将在许多其他情况下使用扩展方法对我有所帮助(一件事 - 确保它是通用方法:p)
    • @RajmondBurgaj:哎呀,添加了类型参数。
    • 如果我使用相同的通用方法但使用 IQueryable 是先实现数据还是将 where 条件应用于数据库?
    • @RajmondBurgaj:您需要使用Expression&lt;Func&lt;T, bool&gt;&gt; 而不是Func&lt;T, bool&gt;,但应该没问题。
    【解决方案4】:

    如果你真的想要你所说的,你需要传递正确的谓词参数,例如GetJobsViewModels(vesselId, status, new Func&lt;JobsNoSubsYpdcResult, bool&gt;( x =&gt; true ));

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-13
      • 1970-01-01
      • 2011-06-22
      • 1970-01-01
      • 2016-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多