4

Here's my code:

class Program
{
    static void Main(string[] args)
    {
        dynamic param = null;
        System.Diagnostics.Debug.Assert(whatever(param));
    }

    static bool whatever(object param)
    {
        return true;
    }
}

When I run it I get RuntimeBinderException with the following message:

Cannot dynamically invoke method 'Assert' because it has a Conditional attribute

Yes, Assert() has ConditionalAttribute on it. Yet there's exactly one whatever() method that returns bool no matter what the method accepts.

What exactly does runtime complain about? Why cannot it use the bool and pass it into Assert()?

sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 1
    Try `Assert((bool)whatever(param))` – Lucas Trzesniewski Feb 17 '15 at 13:40
  • 1
    @LucasTrzesniewski: it's even more interesting that that works than the more obvious `Assert(whatever((object)param))` (which also works). No doubt the language specification will explain somewhere why the call to `whatever` is dynamically bound even though there is no other method that could possibly be called. – Jeroen Mostert Feb 17 '15 at 13:47
  • 1
    @Sriram Sakthivel: I cannot see how this question is a duplicate of that one. – sharptooth Feb 17 '15 at 13:49
  • The linked question is not a duplicate. Although it offers roughly the same workaround, it does not answer the *why*. – Jeroen Mostert Feb 17 '15 at 13:50
  • @sharptooth I can't see how that question isn't? you're not passing a bool there. It is typed as `dynamic` which is what compiler is complaining. Even though whatever returns bool, result of a dynamic expression is always dynamic. You need a cast there. – Sriram Sakthivel Feb 17 '15 at 13:50
  • @SriramSakthivel: the question is why it's typed that way. `whatever` cannot return anything other than `bool` regardless of its argument. It would make perfect sense if the type of the expression was `bool`, but apparently any method call involving a `dynamic` parameter has type `dynamic`. It would be nice to have that in an answer somewhere (with references). – Jeroen Mostert Feb 17 '15 at 13:54
  • @SriramSakthivel: I wouldn't ask this if `whatever()` was declared to return `dynamic` but it is declared to return `bool` and I simply don't see how something returning `bool` magically becomes dynamic. – sharptooth Feb 17 '15 at 13:56
  • As usual, Eric Lippert [has the answer](http://blogs.msdn.com/b/ericlippert/archive/2012/10/22/a-method-group-of-one.aspx): static type determination does not take precedence because 1) it's not worth optimizing for and 2) in order to remain compatible with future declarations. This is why the type of `whatever(aDynamic)` is `dynamic` even though `whatever` will "always" return a `bool`. – Jeroen Mostert Feb 17 '15 at 14:07

1 Answers1

2

The actual call in Main gets translated into a CallSite, because you're invoking the call with a dynamic param. The CallSite does all the preparation it needs in order to invoke this method at run-time. But, the problem is, that Assert has a Conditional attribute on it, which means it needs to pass information to the compiler pre-processor at compile-time

This blog post explains:

Conditional attributes can be placed on methods (and attributes in whidbey) to instruct the compiler to conditionally remove calls to the function if a symbol is not defined. This can be useful for debug-only functionality, like Debug.Assert, which has a Conditional("DEBUG") on it.

Conditional takes a string argument. If that string is defined (as determined by the compiler's preprocessor), then the compiler emits the method call. If the symbol is not defined, C# still compiles the method, but does not compile the calls.

And later, to strengthen our point:

The Conditional attribute is entirely handled by the compiler without any cooperation from the runtime. The method is still jitted normally, but the compiler just doesn't emit the calls if the symbol is not defined.

Now, we have a conflict. We can't pass parameters to the compiler pre-processor at run-time (to tell it if "DEBUG" is defined or not), only at compile-time, but the method will only be invoked at run-time, because that's when we'll infer the type of our dynamic value.

That's why the binder yells at run-time that this method can't actually be invoked, because that would be breaking the ConditionalAttribute.

Bonus:

This is what actually gets called:

private static void Main(string[] args)
{
    object param = null;
    if (Program.<Main>o__SiteContainer0.<>p__Site1 == null)
    {
        Program.<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Assert", null, typeof(Program), new CSharpArgumentInfo[]
        {
            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null),
            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
        }));
    }
    Action<CallSite, Type, object> arg_CB_0 = Program.<Main>o__SiteContainer0.<>p__Site1.Target;
    CallSite arg_CB_1 = Program.<Main>o__SiteContainer0.<>p__Site1;
    Type arg_CB_2 = typeof(Debug);
    if (Program.<Main>o__SiteContainer0.<>p__Site2 == null)
    {
        Program.<Main>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, Type, object, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.None, "whatever", null, typeof(Program), new CSharpArgumentInfo[]
        {
            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null),
            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
        }));
    }
    arg_CB_0(arg_CB_1, arg_CB_2, Program.<Main>o__SiteContainer0.<>p__Site2.Target(Program.<Main>o__SiteContainer0.<>p__Site2, typeof(Program), param));
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321