C# 在红外线 Action 中抛出使其在解析重载时成为 Function

C# Throwing inside infered Action makes it a Func when resolving overloads

我遇到了类型系统的一个非常烦人的极端情况。

我已将代码减少到最低要求以说明问题。

using System;

// Some interface or Base class, doesn't matter
public interface IFace {}
// Some class that implements/extends it
public class Implements: IFace {}

public static class Foo {

  public static void Bar<T, T1>(Func<T> f) where T1: IFace {
    Console.WriteLine("Bar relaxed");
    var _ = f();
  }

  public static void Bar<T1, T2>(Action f)
    where T1: IFace
    where T2: IFace
  {
    Console.WriteLine("Bar strict");
    f();
  }

  public static void Main() {
    try {
      Bar<Implements, Implements>(() => { // Should call Bar strict
        var _ = new Implements();
      });

      Bar<Implements, Implements>(() => { // Should still call Bar strict
        var _ = new Implements();
        throw new NullReferenceException(); // But having unconditional throw in the method
                                            // Makes it a `Func<T>` instead of a `Action`
      });
    } catch(Exception _) {}
  }
}

我想要的输出是

Bar strict
Bar strict

我得到的输出是

Bar strict
Bar relaxed

回复:https://repl.it/repls/WearyImpoliteExponent

这可以修复吗? (不删除第一个 Bar,或更改通用参数的数量)

Bar 方法的真实代码 none 中 return void,它们 return 引用通用参数的东西,以及它们的主体也不一样

编辑:澄清一下,"real world" Bar 方法看起来更像这样:

public static Baz<T, IFace> Bar<T, T1>(Func<T> f) where T1: IFace;

public static Baz<Default, IFace> Bar<T1, T2>(Action f)
  where T1: IFace
  where T2: IFace;

// Where `Default` is a concrete type
struct Default {}

"real world"代码:https://repl.it/repls/NaturalNoisyOpensoundsystem

是的,你可以做到。诀窍是在抛出的异常之后添加一个return;

Foo.Bar<Implements, Implements>(() =>
{ // Should still call Bar strict
    var _ = new Implements();
    throw new NullReferenceException();
    return;
}

lambda 现在可以正确解析为 Action 而不是 Func

发生这种情况的原因是重载解析如何工作的一个非常奇怪的极端情况。本质上,如果一个 lambda 表达式可以被认为是一个 Func,它总是比 Action 更受欢迎。只要所有代码路径 return 与您预期的 return 类型兼容,就会选择 Func 重载。由于 return 为零,因此满足此条件,并使用 Func 重载。

您可以简单地将您的论点转换为 Action

        Bar<Implements, Implements>((Action)(() =>
        { // Should still call Bar strict
            var _ = new Implements();
            throw new NullReferenceException();
        }));