Optional parameters in managed C++/CLI methods
Asked Answered
S

3

34

How can I declare a managed method in C++/CLI that has an optional parameter when used from C#?

I've decorated the parameter with both an Optional and a DefaultParameterValue attribute (see: How default parameter values are encoded), but only the Optional attribute seems to be honored.


C++/CLI:

public ref class MyClass1
{
 public:
  MyClass1([System::Runtime::InteropServices::Optional]
           [System::Runtime::InteropServices::DefaultParameterValue(2)]
           int myParam1)                                            ↑
  {
    System::Console::WriteLine(myParam1);
  }
};

C#:

var myInstance1 = new MyClass1();  // compiles and runs

Output:

0

Expected Output:

2

Visual C# IntelliSense:

MyClass1.MyClass1([int myParam1 = 0]);  // wrong default value
                                  ↑

Edit: A closer look with a disassembler reveals that the C++/CLI compiler does indeed not generate the required .param [1] = int32(2) directive. The IL code shown by Reflector is wrong.

Reflector:

.method public hidebysig specialname rtspecialname instance void .ctor([opt] int32 myParam1) cil managed
{
    .param [1] = int32(2)  // bug
    ...

ILDASM:

.method public hidebysig specialname rtspecialname instance void .ctor([opt] int32 myParam1) cil managed
{
    .param [1]
    .custom instance void [System]System.Runtime.InteropServices.DefaultParameterValueAttribute::.ctor(object) = ( 01 00 08 02 00 00 00 00 00 ) 
    ...
Satisfied answered 11/2, 2011 at 21:26 Comment(2)
Is there a reason why an overload wouldn't work, for your purposes?Brote
@Wes P: Just aesthetical reasons. I will do overloads when I can't get this to work, but I'd prefer optional parameters if possible.Satisfied
W
33

The C# compiler doesn't use the [DefaultParameterValue] attribute to set the default value, it uses the .param directive to get the value embedded in the metadata. Barely documented in the CLI spec btw, only Partition II, chapter 15.4.1 mentions that it can have a FieldInit value, 15.4.1.4 is silent about it.

That's where the buck stops, the C++/CLI compiler doesn't know how to generate the directive. You cannot make this work.

Wrathful answered 11/2, 2011 at 22:0 Comment(2)
Yup, bug, ildasm.exe shows you the real deal. Only the attribute is present. There's some confuzzlement there because VB.NET used the attribute, it has supported optional arguments for a long time. Your code will work with VB.NET ;)Wrathful
You cannot make this work... - challenge accepted! I bet if you used a post-build event involving something like Mono.Cecil that rewrote a bit of metadata would do it. Not sure if that will work with C++/CLR assemblies containing unmanaged code though.Analyzer
R
13

There is a trick to make this working (workaround). the magic word is nullable, as for nullable types the default is always "null" (.HasValue == false).

Example:

C++ CLI in header:

String^ test([Optional] Nullable<bool> boolTest);

C++ CLI in .cpp file:

String^ YourClass::test(Nullable<bool> boolTest)
{
    if (!boolTest.HasValue) { boolTest = true; }
    return (boolTest ? gcnew String("True") : gcnew String("False"));
}

to Test it in C#:

MessageBox.Show(YourClass.test());

Note that [Optional] in the example above is located in the namespace System::Runtime::InteropServices. To access it, add the following line:

using namespace System::Runtime::InteropServices;
Reify answered 27/9, 2014 at 9:1 Comment(4)
It works ! Don't care about post build steps and ildasm other re-generation tricks. :-)Eastwards
Lots of boxing going on here.Unsocial
this gives me an error Nullable is not a template. How to get over this?Regardant
Works for value types (which are not inherently nullable, I suppose), thanks! Still trying to figure it out for reference types (they are "not a valid template argument" for Nullable which makes sense).Trilogy
F
6

As a workaround, you can just overload the constructor, and use delegation. It will be inlined by the JIT and should end up with the same final result as a default parameter value.

Fescue answered 12/2, 2011 at 0:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.