Why does dynamic call dispatch prefer double conversion to invoking object overload?

100 views Asked by At

I am working on arbitrary object tree conversion to some specific format, using dynamic dispatch (to simplify handling generics). The object tree is generated by interop code, wraps some values into containers, which have implicit conversions defined on them.

When I process those containers, I hit StackOverflowException. Initially, I thought maybe there is a stupid mistake, where I just call Accept on the same value, however, I managed to narrow down my issue to this:

using System;
using System.Runtime.CompilerServices;

class Program {
    static void Main(string[] args) {
        var container = new Container<object>("42");
        Accept((dynamic)container);            
    }

    static void Accept(object obj) { Console.WriteLine(nameof(obj)); }
    static void Accept(int i32) { Console.WriteLine(nameof(i32)); }

    static void Accept<T>(IContainer<T> container) {
        RuntimeHelpers.EnsureSufficientExecutionStack();
        Accept((dynamic)container.Get());
    }
}

public interface IContainer<out T>{
    T Get();
}

public struct Container<T>: IContainer<object> {
    readonly T value;
    public Container(T value){ this.value = value; }
    public static implicit operator Container<T>(T value) => new Container<T>(value);
    public object Get() => this.value;
}

Surprisingly, EnsureSufficientExecutionStack fails. Apparently, whatever performs dynamic dispatch on the value of container.Get(), wraps the result back into Container<object> and passes the result to Accept<T>(IContainer<T>) instead of passing it directly to Accept(object).

Question is: what logic does it follow, when it chooses to do so? It made slight sense when I had Accept<T>(Container<T>), as I could see it guessing it can call the implicit conversion, but I can't comprehend how does it even find implicit conversion to some arbitrary interface implementation to dispatch to the overload with an interface parameter.

What blew my mind completely was that if in Container<object>? container = new Container<object>("42"); I replace object with string, the error goes away, and the program correctly prints obj

0

There are 0 answers