2

In a managed c++ class, should I be using a reference or an instance of a C# class that I've implemented in another library?

Consider this example:

// MyManagedClass.h
#pragma once

using namespace System::Collections::Generic;
using namespace My::Namespace::MyCSharpLib;

namespace My::Namespace::MyManagedLib
{
    public ref class MyManagedClass
    {
    public:
        MyCSharpClass myInst; // i have an instance!
        MyCSharpClass ^myRef; // need to do gcnew

        List<MyCSharpClass ^> listInst; // i have an instance!
        List<MyCSharpClass ^> ^listRef; // need to do gcnew
    };
}

And then the managed class is called from C# code:

// driver.cs
using My.Namespace.MyCSharpLib;
using My.Namespace.MyManagedLib;

public class Driver
{
    private static void Main(string[] args)
    {
        MyManagedClass mmc = new MyManagedClass();
        DoStuff(mmc);
    }
}

My gut tells me I should be using myRef and listRef because that's what I'd be doing if this was implemented in C#. But why am I allowed to directly instantiate an instance of MyCSharpClass? What are the repercussions of doing this? If my class only ever has one collection of MyCSharpClass objects, is there harm is directly initializing the collection?

djs
  • 1,660
  • 1
  • 17
  • 35

1 Answers1

5

C++/CLI has a feature called stack semantics, which you are using when you declare a reference type member as though it were a value type (MyCSharpClass myInst;).

gcnew is still being called, but you don't see this code because the compiler will inject it automatically into your default constructor. Beware that it will also generate code to dispose myInst!

Below is a (pseudo) C++/CLI code equivalent of the MSIL that the compiler will emit for your class:

Constructor:

MyManagedClass()
{
    myInst = gcnew MyCSharpClass();
}

Dispose:

void Dispose(bool dispose)
{
    if (dispose)
    {
        try
        {
            this->~MyManagedClass();
        }
        finally
        {
            delete myInst;
        }
    }
}

EDIT:

Regarding your question about repercussions: manually allocating with gcnew means that when your MyManagedClass objects die, the object pointed to by myRef will still hang around until the garbage collector cleans it up; whereas by using stack semantics you have a more deterministic means of controlling object lifetimes without having to write a Dispose method yourself.

It also means that when using stack semantics you must be careful who you share the objects with...

easuter
  • 1,167
  • 14
  • 20
  • 1
    By the way, if you're going to be using C++/CLI for anything other than trivial work I'de recommend trying to get your hands on "C++/CLI in Action" by Nishant Sivakumar. It's a great reference for the language's features and will also help you avoid pitfalls. – easuter Apr 29 '15 at 09:42
  • 1
    Thanks! This got me thinking about other things. After some testing I learned that `mmc.myInst` is not available to me in `driver.cs`. Also, this only compiles if `MyCSharpClass` has a default constructor. – djs Apr 29 '15 at 14:25
  • 2
    "generate code to deallocate myInst" NO, it will not. deallocation is still managed by the garbage collector. It will automatically call `Dispose`, if the object implements `IDisposable`. C++/CLI stack semantics are a (more powerful by far) version of a C# `using` block. – Ben Voigt Apr 29 '15 at 14:26
  • @BenVoigt, maybe my phrasing was not as good as it should be...but I tried to be as clear as I could: when using stack semantics the compiler will generate a `Dispose` method that will `delete` the object (deallocation code). If what I wrote is ambiguous or incorrect, I can edit the answer...what do you suggest? – easuter Apr 29 '15 at 14:50
  • 1
    `delete` in C++/CLI is not deallocation. It's a call to `Dispose` on the target object (after safely casting to `IDisposable`) – Ben Voigt Apr 29 '15 at 14:59
  • @BenVoigt, I've updated my answer to make this clearer. – easuter Apr 29 '15 at 15:12
  • 2
    Much better. Although your EDIT paragraph still suggests incorrectly that the garbage collector doesn't control the object lifetime when stack semantics are used. After a stack semantics object goes out of scope, it's been disposed (so mostly useless), but it isn't actually dead yet. Under .NET rules you are at least still allowed to call `Dispose` on it, and some objects may allow other actions following disposal. And references made by "sharing" the object, as you say, aren't wild or dangling pointers, although using them is likely to net you an `ObjectDisposedException`. – Ben Voigt Apr 29 '15 at 15:19