简体   繁体   中英

Why do I have to type-cast a strongly typed function using a dynamic input-parameter?

I have a converter method:

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);
}

I have to explictly cast the result to MyPoco, otherwise poco will become a dynamic variable, too.

But, if I inline the Convert method;

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);
}

I don't need to cast ConvertItem to MyPoco. Is there a reason for this behavior? It should be easy for the compiler to know that Convert return-type is MyPoco, right?

There is a difference between them which could be the reason - local functions don't support overloading. I think it is important - imagine we have two methods with the same name and with different input and output types

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;
}

It means the result may be string or int - we don't know it in compile time.

While for the following code we get the error that local variable or function already declared

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!");
}

We can only write something like this

static void Main(string[] args)
{
    string Hello(string s)
    {
        return s;
    }

    dynamic d = null;
    var result = Hello(d);
    Console.WriteLine("Hello World!");
}

So when compiler sees the local Hello(...) call it knows that return type is string.

Upd:

Regarding the compiler ability to infer correct type in case of dynamic.

I think yes, it is possible for compiler to catch such cases - if we know in compile time that there is the only one method, there is no chance that in runtime another one will appear.

I could imagine eg the method we call is in another assembly, and in runtime we loaded newer version which has different signature - with dynamic it will work, but for the, say, private static method with no overloads I think we could infer non-dynamic type.

But I think, it is was decided to implement it that way for the sake of simplicity - it is easier to keep in mind simple rule - everything that touches dynamic - dynamic.

For local functions for simplicity I think it would be easier to also have them dynamic. I think it is just decision made by different people implementing that.

I checked the roslyn source code trying to find information about that.

The place where it is defined is Binder_Invocation.cs, BindMethodGroupInvocation method.

For local function the following method is called

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.

As you can see the also say about overloading, but for the normal method call no any information about the reason

                    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);
                        }

They create dynamic if there is at least one canditate. So, as I said, I think it could be done non-dynamic, but the people who implemented it originally kept it dynamic, probably for simplicity.

What you could do in order to find more details is to try to implement the case when no overloading methods, changing the code

if (finalApplicableCandidates.Length > 0)
{
    result = BindDynamicInvocation(syntax, methodGroup, resolution.AnalyzedArguments, finalApplicableCandidates, diagnostics, queryClause);
}

by adding the check if Length == 1 then call BindInvocationExpressionContinued instead of BindDynamicInvocation and run the tests and check if something fails, maybe it helps.(I didn't even manage to get the roslyn project built, dotnet core is a bit weird)

PS

According to this

if (boundMethodGroup.TypeArgumentsOpt.IsDefaultOrEmpty && localFunction.IsGenericMethod)
{
    Error(diagnostics, ErrorCode.ERR_DynamicLocalFunctionTypeParameter, syntax, localFunction.Name);
    return BindDynamicInvocation(

For local function we could get dynamic instead of concrete type.

If you type something like this

static void Main(string[] args)
    {
        int TestFunc<T>(T data)
        {
            return 1;
        }

        dynamic d = 2;

        var r = TestFunc(d);
    }

Yes, it will give the error, but if you check the inferred type it of r will show dynamic))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM