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();
}));
我遇到了类型系统的一个非常烦人的极端情况。
我已将代码减少到最低要求以说明问题。
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();
}));