I've got a C++/CLI layer that I've been using successfully for a long time. But I just discovered something that makes me think I need to relearn some stuff.
When my C++/CLI functions receive an instance of any managed class, they use the "hat" operator ('^') and when they receive an instance of a managed struct, they do not. I thought this was how I was supposed to write it.
To illustrate as blandly as I can
using Point = System::Windows::Point;
public ref class CppCliClass
{
String^ ReturnText(String^ text) { return text; } // Hat operator for class
Point ReturnStruct(Point pt) { return pt; } // No hat operator for struct
};
I thought this was required. It certainly works. But just today I discovered that CancellationToken is a struct, not a class. My code accepts it with a hat. I thought it was a class when I wrote it. And this code works just fine. My cancellations are honored in the C++/CLI layer.
void DoSomethingWithCancellation(CancellationToken^ token)
{
// Code that uses the token. It works just fine
}
So apparently I can choose either method.
But then what is the difference between passing in a struct by value (as I've done with every other struct type I use, like Point) and by reference (as I'm doing with CancellationToken?). Is there a difference?
^
for reference types and without for value types matches C#, but C++/CLI does give you more flexibility:
Reference type without ^
is called "stack semantics" and automatically tries to call IDisposable::Dispose
on the object at the end of the variable's lifetime. It's like a C# using
block, except more user-friendly. In particular:
The syntax can be used whether the type implements IDisposable
or not. In C#, you can only write a using
block if the type can be proved, at compile time, to implement IDisposable
. C++/CLI scoped resource management works fine in generic and polymorphic cases, where some of the objects do and some do not implement IDisposable
.
The syntax can be used for class members, and automatically implements IDisposable
on the containing class. C# using
blocks only work on local scopes.
Value types used with ^
are boxed, but with the exact type tracked statically. You'll get errors if a boxed value of a different type is passed in.