为什么我必须使用动态输入参数对强类型函数进行类型转换?
Why do I have to type-cast a strongly typed function using a dynamic input-parameter?
我有一个转换器方法:
MyPoco Convert(dynamic p) => new MyPoco { A = p.X, B = p.Y };
void Test()
{
dynamic item = new { X = 1, Y = 2 };
var poco = (MyPoco)Convert(item);
}
我必须明确地将结果转换为 MyPoco,否则 poco 也会成为一个动态变量。
但是,如果我内联 Convert 方法;
void Test()
{
MyPoco Convert(dynamic p) => new MyPoco { A = p.X, B = p.Y };
dynamic item = new { X = 1, Y = 2 };
var poco = Convert(item);
}
我不需要将 ConvertItem 转换为 MyPoco。这种行为有原因吗?编译器应该很容易知道 Convert return-type 是 MyPoco 吧?
它们之间存在差异,这可能是因为局部函数不支持重载。我认为这很重要 - 假设我们有两个名称相同但输入和输出类型不同的方法
static void Main(string[] args)
{
dynamic d = null;
var result = Hello(d);
Console.WriteLine("Hello World!");
}
static string Hello(string s)
{
return s;
}
static int Hello(int i)
{
return i;
}
这意味着结果可能是字符串或整数——我们在编译时不知道。
而对于以下代码,我们得到局部变量或函数已声明的错误
static void Main(string[] args)
{
string Hello(string s)
{
return s;
}
int Hello(int s) // error - local variable or function with the same name already declared
{
return s;
}
dynamic d = null;
var result = Hello(d);
Console.WriteLine("Hello World!");
}
只能这样写
static void Main(string[] args)
{
string Hello(string s)
{
return s;
}
dynamic d = null;
var result = Hello(d);
Console.WriteLine("Hello World!");
}
因此,当编译器看到本地 Hello(...) 调用时,它知道 return 类型是字符串。
更新:
关于编译器在动态情况下推断正确类型的能力。
我认为是的,编译器有可能捕捉到这种情况——如果我们在编译时知道只有一种方法,
在 运行 时间内不可能出现另一个。
我可以想象,例如我们调用的方法在另一个程序集中,在 运行 时间我们加载了具有不同签名的较新版本 - 使用动态它会起作用,但是对于没有重载的私有静态方法,我认为我们可以推断出非-动态类型。
但我认为,为了简单起见,决定以这种方式实现它 - 更容易记住简单的规则 - 涉及动态的一切 - 动态。
为了简单起见,对于局部函数,我认为将它们也动态化会更容易。我认为这只是不同实施者做出的决定。
我检查了 roslyn 源代码,试图找到相关信息。
定义的地方是Binder_Invocation.cs,BindMethodGroupInvocation方法
对于本地函数,调用以下方法
private BoundExpression BindLocalFunctionInvocationWithDynamicArgument(
SyntaxNode syntax,
SyntaxNode expression,
string methodName,
BoundMethodGroup boundMethodGroup,
DiagnosticBag diagnostics,
CSharpSyntaxNode queryClause,
MethodGroupResolution resolution)
{
// Invocations of local functions with dynamic arguments don't need
// to be dispatched as dynamic invocations since they cannot be
// overloaded. Instead, we'll just emit a standard call with
// dynamic implicit conversions for any dynamic arguments. There
// are two exceptions: "params", and unconstructed generics. While
// implementing those cases with dynamic invocations is possible,
// we have decided the implementation complexity is not worth it.
// Refer to the comments below for the exact semantics.
如你所见,也有关于重载的说法,但对于正常的方法调用,没有任何关于原因的信息
else
{
if (HasApplicableConditionalMethod(resolution.OverloadResolutionResult))
{
// warning CS1974: The dynamically dispatched call to method 'Goo' may fail at runtime
// because one or more applicable overloads are conditional methods
Error(diagnostics, ErrorCode.WRN_DynamicDispatchToConditionalMethod, syntax, methodGroup.Name);
}
// Note that the runtime binder may consider candidates that haven't passed compile-time final validation
// and an ambiguity error may be reported. Also additional checks are performed in runtime final validation
// that are not performed at compile-time.
// Only if the set of final applicable candidates is empty we know for sure the call will fail at runtime.
var finalApplicableCandidates = GetCandidatesPassingFinalValidation(syntax, resolution.OverloadResolutionResult,
methodGroup.ReceiverOpt,
methodGroup.TypeArgumentsOpt,
diagnostics);
if (finalApplicableCandidates.Length > 0)
{
result = BindDynamicInvocation(syntax, methodGroup, resolution.AnalyzedArguments, finalApplicableCandidates, diagnostics, queryClause);
}
else
{
result = CreateBadCall(syntax, methodGroup, methodGroup.ResultKind, analyzedArguments);
}
如果至少有一名候选人,他们会创造动态。
所以,正如我所说,我认为它可以非动态地完成,但实施它的人最初保持动态,可能是为了简单起见。
为了找到更多细节,你可以做的是尝试实现没有重载方法的情况,更改代码
if (finalApplicableCandidates.Length > 0)
{
result = BindDynamicInvocation(syntax, methodGroup, resolution.AnalyzedArguments, finalApplicableCandidates, diagnostics, queryClause);
}
通过添加检查如果 Length == 1 然后调用 BindInvocationExpressionContinued 而不是 BindDynamicInvocation
和 运行 测试并检查是否有问题,也许它有帮助。(我什至没有设法构建 roslyn 项目,dotnet 核心有点奇怪)
P.S.
据此
if (boundMethodGroup.TypeArgumentsOpt.IsDefaultOrEmpty && localFunction.IsGenericMethod)
{
Error(diagnostics, ErrorCode.ERR_DynamicLocalFunctionTypeParameter, syntax, localFunction.Name);
return BindDynamicInvocation(
对于本地函数,我们可以获得动态类型而不是具体类型。
如果你输入这样的内容
static void Main(string[] args)
{
int TestFunc<T>(T data)
{
return 1;
}
dynamic d = 2;
var r = TestFunc(d);
}
是的,它会给出错误,但是如果你检查 r 的推断类型,它会显示动态))
我有一个转换器方法:
MyPoco Convert(dynamic p) => new MyPoco { A = p.X, B = p.Y };
void Test()
{
dynamic item = new { X = 1, Y = 2 };
var poco = (MyPoco)Convert(item);
}
我必须明确地将结果转换为 MyPoco,否则 poco 也会成为一个动态变量。
但是,如果我内联 Convert 方法;
void Test()
{
MyPoco Convert(dynamic p) => new MyPoco { A = p.X, B = p.Y };
dynamic item = new { X = 1, Y = 2 };
var poco = Convert(item);
}
我不需要将 ConvertItem 转换为 MyPoco。这种行为有原因吗?编译器应该很容易知道 Convert return-type 是 MyPoco 吧?
它们之间存在差异,这可能是因为局部函数不支持重载。我认为这很重要 - 假设我们有两个名称相同但输入和输出类型不同的方法
static void Main(string[] args)
{
dynamic d = null;
var result = Hello(d);
Console.WriteLine("Hello World!");
}
static string Hello(string s)
{
return s;
}
static int Hello(int i)
{
return i;
}
这意味着结果可能是字符串或整数——我们在编译时不知道。
而对于以下代码,我们得到局部变量或函数已声明的错误
static void Main(string[] args)
{
string Hello(string s)
{
return s;
}
int Hello(int s) // error - local variable or function with the same name already declared
{
return s;
}
dynamic d = null;
var result = Hello(d);
Console.WriteLine("Hello World!");
}
只能这样写
static void Main(string[] args)
{
string Hello(string s)
{
return s;
}
dynamic d = null;
var result = Hello(d);
Console.WriteLine("Hello World!");
}
因此,当编译器看到本地 Hello(...) 调用时,它知道 return 类型是字符串。
更新:
关于编译器在动态情况下推断正确类型的能力。
我认为是的,编译器有可能捕捉到这种情况——如果我们在编译时知道只有一种方法, 在 运行 时间内不可能出现另一个。
我可以想象,例如我们调用的方法在另一个程序集中,在 运行 时间我们加载了具有不同签名的较新版本 - 使用动态它会起作用,但是对于没有重载的私有静态方法,我认为我们可以推断出非-动态类型。
但我认为,为了简单起见,决定以这种方式实现它 - 更容易记住简单的规则 - 涉及动态的一切 - 动态。
为了简单起见,对于局部函数,我认为将它们也动态化会更容易。我认为这只是不同实施者做出的决定。
我检查了 roslyn 源代码,试图找到相关信息。
定义的地方是Binder_Invocation.cs,BindMethodGroupInvocation方法
对于本地函数,调用以下方法
private BoundExpression BindLocalFunctionInvocationWithDynamicArgument(
SyntaxNode syntax,
SyntaxNode expression,
string methodName,
BoundMethodGroup boundMethodGroup,
DiagnosticBag diagnostics,
CSharpSyntaxNode queryClause,
MethodGroupResolution resolution)
{
// Invocations of local functions with dynamic arguments don't need
// to be dispatched as dynamic invocations since they cannot be
// overloaded. Instead, we'll just emit a standard call with
// dynamic implicit conversions for any dynamic arguments. There
// are two exceptions: "params", and unconstructed generics. While
// implementing those cases with dynamic invocations is possible,
// we have decided the implementation complexity is not worth it.
// Refer to the comments below for the exact semantics.
如你所见,也有关于重载的说法,但对于正常的方法调用,没有任何关于原因的信息
else
{
if (HasApplicableConditionalMethod(resolution.OverloadResolutionResult))
{
// warning CS1974: The dynamically dispatched call to method 'Goo' may fail at runtime
// because one or more applicable overloads are conditional methods
Error(diagnostics, ErrorCode.WRN_DynamicDispatchToConditionalMethod, syntax, methodGroup.Name);
}
// Note that the runtime binder may consider candidates that haven't passed compile-time final validation
// and an ambiguity error may be reported. Also additional checks are performed in runtime final validation
// that are not performed at compile-time.
// Only if the set of final applicable candidates is empty we know for sure the call will fail at runtime.
var finalApplicableCandidates = GetCandidatesPassingFinalValidation(syntax, resolution.OverloadResolutionResult,
methodGroup.ReceiverOpt,
methodGroup.TypeArgumentsOpt,
diagnostics);
if (finalApplicableCandidates.Length > 0)
{
result = BindDynamicInvocation(syntax, methodGroup, resolution.AnalyzedArguments, finalApplicableCandidates, diagnostics, queryClause);
}
else
{
result = CreateBadCall(syntax, methodGroup, methodGroup.ResultKind, analyzedArguments);
}
如果至少有一名候选人,他们会创造动态。 所以,正如我所说,我认为它可以非动态地完成,但实施它的人最初保持动态,可能是为了简单起见。
为了找到更多细节,你可以做的是尝试实现没有重载方法的情况,更改代码
if (finalApplicableCandidates.Length > 0)
{
result = BindDynamicInvocation(syntax, methodGroup, resolution.AnalyzedArguments, finalApplicableCandidates, diagnostics, queryClause);
}
通过添加检查如果 Length == 1 然后调用 BindInvocationExpressionContinued 而不是 BindDynamicInvocation 和 运行 测试并检查是否有问题,也许它有帮助。(我什至没有设法构建 roslyn 项目,dotnet 核心有点奇怪)
P.S.
据此
if (boundMethodGroup.TypeArgumentsOpt.IsDefaultOrEmpty && localFunction.IsGenericMethod)
{
Error(diagnostics, ErrorCode.ERR_DynamicLocalFunctionTypeParameter, syntax, localFunction.Name);
return BindDynamicInvocation(
对于本地函数,我们可以获得动态类型而不是具体类型。
如果你输入这样的内容
static void Main(string[] args)
{
int TestFunc<T>(T data)
{
return 1;
}
dynamic d = 2;
var r = TestFunc(d);
}
是的,它会给出错误,但是如果你检查 r 的推断类型,它会显示动态))