2

我有一个转换器方法:

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 返回类型是 MyPoco,对吧?

4

1 回答 1

5

它们之间存在差异,这可能是原因 - 本地函数不支持重载。我认为这很重要——想象一下我们有两个具有相同名称和不同输入和输出类型的方法

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(...) 调用时,它就知道返回类型是字符串。

更新:

关于编译器在动态情况下推断正确类型的能力。

我认为是的,编译器有可能捕捉到这种情况——如果我们在编译时知道只有一种方法,那么在运行时就不可能出现另一种方法。

我可以想象,例如我们调用的方法在另一个程序集中,并且在运行时我们加载了具有不同签名的较新版本 - 动态它可以工作,但是对于没有重载的私有静态方法,我认为我们可以推断出非动态类型。

但我认为,为了简单起见,决定以这种方式实现它 - 更容易记住简单的规则 - 涉及动态的一切 - 动态的。

为了简单起见,对于本地函数,我认为让它们动态化会更容易。我认为这只是由不同的人实施的决定。

我检查了 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);
}

通过添加检查 if Length == 1 然后调用 BindInvocationExpressionContinued 而不是 BindDynamicInvocation 并运行测试并检查是否失败,也许它有帮助。(我什至没有设法构建 roslyn 项目,dotnet 核心有点奇怪)

附言

根据这个

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 将显示动态))

于 2019-09-05T18:29:32.023 回答