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