C++ Generalize delegates

94 views Asked by At

i have a similiar situation:

ref class Manager
{ 
    delegate void answer1(String^ Message, int Avalue);
    delegate void answer2(Object^ Obj, double Something);

    answer1 ^ Callbackfor1;
    answer2 ^ Callbackfor2;

    void RegisterCallback1(answer1 ^ Address);
    void RegisterCallback2(answer2 ^ Address);
}

How can i manage that in a better solution? I had to create each time a delegate, a pointer and a procedure for each Callback. There is a better way to generalize? I think that delegate should be recreate each time, but i want avoid to recreate a procedure and a pointer.

1

There are 1 answers

2
Adriano Repetti On BEST ANSWER

First of all if you expose Callbackfor1 and Callbackfor2 (I assume your class members are all public) then you don't also need RegisterCallback1 and RegisterCallback2: they're just helper methods that make caller code even more prolix:

manager->RegisterCallback1(YourMethod);

Instead of:

manager->Callbackfor1 += YourMethod;

However note that having your delegates public you're exposing them completely, caller may write this:

manager->Answer1 = nullptr;

Then in my opinion you should remove that helper methods (keeping your fields public) or keep helper methods (making fields private). There is another option: event. They're exactly delegates but caller isn't allowed to set them (only add/remove a function in the call list):

ref class Manager {
public:
    event answer1^ Callback1;
};

Note that making them events (or keeping them private) also prevents callers to directly invoke them, you can raise events only from within class you declared them). Who uses your class:

manager->Callback1 += YourMethod;    // Add to invocation list
manager->Callback1 -= YourMethod;    // Remove from invocation list
manager->Callback1 = nullptr;        // Not allowed: it doesn't compile
manager->Callback1(L"some text", 0); // Not allowed: it doesn't compile

About delegates declaration and signature: if you're target new .NET Framework versions you can use Action and Func generic delegates. Like this:

ref class Manager {
public:
    event Action<String^, int>^ Callback1;
    event Action<Object^, double>^ Callback2;
};

That's all. Note that if you're instead targeting .NET Framework 2.0 where these delegates were not available then you can write it once and reuse them:

generic <class T1, class T2>
public delegate Action(T1 t1, T2 t2);

And use them as described before. Let's do a further step in the common .NET pattern of events. Let's create two classes (I don't know your domain so I pick random names):

ref class Callback1EventArgs sealed : EventArgs {
public:
    Callback1EventArgs(String^ message, int value) {
        Message = message;
        Value = value;
    }

    property String^ Message;
    property int Value;
};

Now change your Manager class:

ref class Manager {
public:
    event EventHandler<Callback1EventArgs^>^ Callback1;
};

Usage is the same:

manager->Callback1 += YourMethod;

But method signature is different:

void YourMethod(Object^ sender, Callback1EventArgs^ e) {
    // In case you need it you have a reference to object
    // that generated this "event".
    Manager^ manager = safe_cast<Manager^>(sender);

    // You find all your properties inside event arguments
    Console::WriteLine(L"Message is {0} and value is {1}",
        e->Message, e->Value);
}

Inside your Manager class you'll raise an event like this (no need to check for nullptr and to make it thread-safe, C++/CLI generates right code for you, note that this happens because of event keyword regardless delegate signature). This code:

answer1^ callbackfor1 = Callbackfor1;
if (callbackfor1 != nullptr)
    callbackfor1(L"Some message", 0);

Will become:

Callback1(this, gcnew Callback1EventArgs(L"Some message", 0));

Is this better? Well to have a class makes your code little bit more prolix but it helps a lot when parameters change: you just need to add a property and recompile (with method parameters you'll need to find and update every method registerd as delegate for Callback1 and add that parameter, even if unused).