18

C# 4, to simplify COM interop, allow callers to COM interfaces to omit the ref keyword in front of arguments for by ref parameters.

I was surprised to see today that this also applies to extension methods that are extending COM interfaces. See the following, compiling, code:

using System;
using System.Runtime.InteropServices;

[ComImport, Guid ("cb4ac859-0589-483e-934d-b27845d5fe74")]
interface IFoo {
}

static class Program {

    public static void Bar (this IFoo self, ref Guid id)
    {
        id = Guid.NewGuid ();
    }

    static void Main ()
    {
        Foo (null);
    }

    static void Foo (IFoo o)
    {
        Guid g = Guid.NewGuid ();
        Console.WriteLine (g);

        // note that g is passed as is, and not as ref g    
        o.Bar (g);

        Console.WriteLine (g);
    }
}

I didn't find anything in the spec to explain this behavior.

My feeling would be that code outside of the COM interface, even if it's an extension method extending a COM interface, should follow the regular C# rules, and enforce the usage of the ref keyword. I therefore filed a bug on connect. Not that I think this will be fixed, even if it's considered as a bug, there's already code out there relying on this.

Bug? Not a bug?

Jb Evain
  • 17,319
  • 2
  • 67
  • 67
  • 3
    Very interesting. The 4.0 spec does appear to be ambiguous here. It says this applies to methods of a COM type (section 22 + 22.1). But I couldn't find anything which explicitly says or doesn't say an extension method is considered a part of a type in this manner. My guess is it's a bug. I'm sure Eric will be along shortly to clarify. – JaredPar Jan 19 '12 at 17:09
  • 11
    That sure sounds like a bug. My bus is still not running today due to freezing rain over snow, and so I'm away from the office. I'll take a look at it next week. Thanks for entering the issue on Connect! – Eric Lippert Jan 19 '12 at 17:15

1 Answers1

2

I don't think it's a bug; it looks more like "COM voodoo" as you say. Under the hood, the C# compiler emits something that is in fact correct, like this:

private static void Foo(IFoo o)
{
    ...
    Guid g = Guid.NewGuid();
    Guid <>r__ComRefCallLocal0 = g;
    Bar(o, ref <>r__ComRefCallLocal0);
    ...
}

C# is in fact full of tricks. If you add a method to IFoo, like this for example,

[ComImport, Guid("cb4ac859-0589-483e-934d-b27845d5fe74")]
interface IFoo
{
    void Test([Optional] ref object test);
}

you, again, will be able to declare this in C# 4:

static void Foo(IFoo o)
{
    Guid g = Guid.NewGuid();
    o.Test(g);
}

Of course, all this only works because CSC.EXE has an intimate knowledge of the ComImport attribute. These new magic Interop tricks were added to C# 4.0 to be able to easily interop with existing COM interfaces. Well, for Microsoft Office interfaces and methods mostly, and especially the armies of dreadful 'ref missing' parameters :-)

I don't think this is fully specified anywhere. This is all what the C# 4 specification has to say:

17.5 Attributes for Interoperation Note: This section is applicable only to the Microsoft .NET implementation of C#. 17.5.1 Interoperation with COM and Win32 components The .NET run-time provides a large number of attributes that enable C# programs to interoperate with components written using COM and Win32 DLLs. For example, the DllImport attribute can be used on a static extern method to indicate that the implementation of the method is to be found in a Win32 DLL. These attributes are found in the System.Runtime.InteropServices namespace, and detailed documentation for these attributes is found in the .NET runtime documentation.

And here are some pages on MSDN:

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • 2
    Simon, I know that what the compiler emit is valid. I also know that call sites of COM interface method can omit the ref for arguments of a by ref parameter, but that's not the question. The question is to know whether this applies to extension methods, as it's not specified in the spec. – Jb Evain Jan 19 '12 at 19:22
  • It's not that clear what you know and don't know from the question. I tried to answer the "bug or not bug". I believe that's not a *bug* because it's Microsoft .NET implementation of C#, so the implementation and supported scenarios are just what Microsoft decide it must be. – Simon Mourier Jan 19 '12 at 21:35
  • @SimonMourier, that would mean the C# could never have any bugs, that's nonsense. The C# compiler is governed by the C# specification. If the compiler and the specification are in conflict, that means there's a bug. It could be a bug in the compiler or in the specification and it could be even deliberate, but it's definitely a bug. – svick Jan 20 '12 at 15:55
  • @svick - The C# spec (the interesting part is completely quoted in my answer) states clearly it's documented in the .NET Runtime documentation, where it's, in fact, not. So there is absolutely no conflict, no rule broken. It's not because it does not sound logical that it's a bug. Automatic ref'd parameter is not specified in the spec either, so should this be a bug that this feature exist? – Simon Mourier Jan 20 '12 at 16:30
  • @SimonMourier, I'm referring to the C# 4.0 specification for the C# 4.0 novelties. You can find it here: http://download.microsoft.com/download/7/E/6/7E6A548C-9C20-4C80-B3B8-860FAF20887A/CSharp%204.0%20Specification.doc To sum the questions up: in regard of the section 22.1, is applying this behavior to extension methods considered a bug, or not? “It's not a bug because Microsoft implementation is the reference” doesn't answer this. There are other cases where the Microsoft implementation violates the spec and could be considered as a bug, and that's ok 's long as its documented. – Jb Evain Jan 21 '12 at 10:23