C# and .NET


Inside .NET’s Delegates and Events Using C++

J. Daniel Smith

The best way to really understand delegates and events is to implement them yourself — in plain old C++.


Introduction

The .NET Framework has introduced delegates and events as a type-safe replacement for C-style callback functions; they are used extensively. Implementing similar functionality using Standard C++ provides a better understanding of these concepts, while also exploring some interesting C++ techniques.

The Delegate and Event Keywords in C#

Let’s start by taking a look at a simple program in C# (the code below is slightly abbreviated). The following program displays this output:

SimpleDelegateFunction called from Ob1,
  string=Event fired!
Event fired!(Ob1): 3:49:46 PM on
  Friday, May 10, 2002
Event fired!(Ob1): 1056318417
SimpleDelegateFunction called from Ob2,
  string=Event fired!
Event fired!(Ob2): 3:49:46 PM on
  Friday, May 10, 2002
Event fired!(Ob2): 1056318417

All of this happens as a result of the line dae.FirePrintString("Event fired!"). In developing a C++ implementation, I wanted to mimic the C# syntax and duplicate the functionality.

namespace DelegatesAndEvents
{
  class DelegatesAndEvents
  {
    public delegate void PrintString
(string s); public event PrintString
MyPrintString; public void FirePrintString(string s) { if (MyPrintString != null) MyPrintString(s); } } class TestDelegatesAndEvents { [STAThread] static void Main(string[] args) { DelegatesAndEvents dae =
new DelegatesAndEvents(); MyDelegates d = new MyDelegates(); d.Name = "Ob1"; dae.MyPrintString +=
new DelegatesAndEvents.PrintString (d.SimpleDelegateFunction); // ... more code similar to the
// above few lines ... dae.FirePrintString("Event fired!"); } } class MyDelegates { // ... "Name" property omitted... public void SimpleDelegateFunction
(string s) { Console.WriteLine ("SimpleDelegateFunction called
from {0}, string={1}", m_name, s); } // ... more methods ... } }

Type-Safe Function Pointers in C++

One of the criticisms of the “old ways” is that they are not type safe [1]. As proof, code such as:

typedef size_t (*FUNC)(const char*);
void printSize(const char* str) {
  FUNC f = strlen;
  (void) printf("%s is %ld chars\n", str, f(str));
}
void crashAndBurn(const char* str) {
  FUNC f = reinterpret_cast<FUNC>(strcat);
  f(str);
}

is offered [2]. Of course, anytime you use reinterpret_cast, you’re probably asking for trouble. The C++ compiler will complain if you take out the cast, and the much safer static_cast won’t do the conversion either. The example is also a bit like comparing apples and oranges since everything in C# is an object, and reinterpret_cast is supposed to be an escape hatch. A better C++ example would avoid using reinterpret_cast with member function pointers:

struct Object { };
struct Str : public Object {
  size_t Len(const char* str) {
    return strlen(str);
  }
  char* Cat(char* s1, const char* s2) {
    return strcat(s1, s2);
  }
};

typedef size_t (Object::*FUNC)(const char*);
void printSize(const char* s) {
  Str str;
  FUNC f = static_cast<FUNC>(&Str::Len);
  (void) printf("%s is %ld chars\n", s, (str.*f)(s));
}
void crashAndBurn(const char* s) {
  Str str;
  FUNC f = static_cast<FUNC>(&Str::Cat);
  (str.*f)(s);
}

The static_cast operator will convert the Str::Len function pointer since Str is derived from Object, but it is type safe in that Str::Cat can’t be converted because the function signatures don’t match.

Member function pointers work much like regular function pointers; the only difference (besides the even more complicated syntax) is that you need a class instance through which to call the member function. Of course, member functions can also be called through a pointer to a class instance using the ->* operator:

  Str* pStr = new Str();
  FUNC f = static_cast<FUNC>(&Str::Len);
  (void) printf("%s is %ld chars\n", s, (str->*f)(s));
  delete pStr;

As long as everything derives from a base Object class (which is the case in C#), you can create type-safe member function pointers in C++.

Creating a Delegate Class

Having type-safe member function pointers is the first step in duplicating the .NET functionality. However, a member function pointer is useless on its own — you always need a class instance; a Delegate object holds on to both pieces making it easy to call a member function. Continuing with the above example, you could write:

struct StrLen_Delegate
{
  typedef size_t (Str::*MF_T)(const char*);
  MF_T m_method;
  Object& m_pTarget;

  StrLen_Delegate(Object& o, const MF_T& mf) :
    m_pTarget(&o), m_method(mf) {}

  MF_T Method() const {
    return m_method;
  }
  Object& Target() const {
    return *m_pTarget;
  }

  size_t Invoke(const char* s) {
    (m_pTarget.*m_method)(s);
  }
};

void printSize2(const char* s) {
  Str str;
  StrLen_Delegate d(str, &Str::Len);
  (void) printf("%s is %ld chars\n", s, 
d.Invoke(s)); }

With the delegate class, calling a member function is much simpler. Making this class a functor by using operator instead of Invoke would reduce the call to just d(s); I used Invoke for clarity and to match .NET conventions. Notice, the class instance is an Object rather than Str. This allows a member function pointer from any class derived from Object to be used to create this delegate as long as the signature matches.

This class works fine for this example, but it’s not very flexible; a new delegate class must be written for each possible member function signature. .NET solves this by using the rich type information maintained by the CLR (Common Language Runtime). This isn’t a very viable option in C++, but templates can be used to achieve similar results. Rather than fixing the parameters of Invoke as const char* s, you specify the type as a template argument:

template <typename ARG1>
struct StrLen_Delegate
{
  typedef size_t (Str::*MF_T)(ARG1);
  // ... as above ...
  size_t Invoke(ARG1 v1) {
    (m_pTarget.*m_method)(v1);
  }
};

This is better, but Invoke will only work for single argument member functions. Also, a delegate just cares about a class instance and member function pointer; it doesn’t really concern itself with the details of the member function pointer. Finally, it will be convenient (for use as a template argument) to be able to easily generate a typedef for the member function pointer. Since everything derives from an Object class, these details can all be moved there:

struct Object
{
  template <typename ARG1>
  struct void1_T {
    typedef void (Object::*mf_t)(ARG1);
  };

  template <typename ARG1, typename ARG2>
  void Invoke(void1_T<ARG1>::mf_t mf, ARG1 v1, ARG2) const {
    (this->*mf)(v1);
  }
};
template <typename CLASS>
class ObjectT : public Object {};
typedef  ObjectT<void> VoidType;

This base Object class contains a typedef for each member function signature; I’ve simplified things by using a void return type. The typedef can be used as follows:

typedef Object::void1_T<std::string>::mf_t StringMF_t;

to easily create a typedef for a member function pointer taking a std::string argument and returning void.

Invoke has been tweaked to account for additional arguments. This is necessary because all of the Invoke methods must have the same number of arguments; the overload resolution is done based on the first argument — the type of the member function pointer. Note that most of the .NET Framework avoids this complication by always using an EventArgs object in delegates. You can add additional arguments by deriving from EventArgs rather than adding to the signature of the delegate.

Finally, the ObjectT template provides a simple way to generate unique types, each of which is ultimately derived from Object. This ensures type safety.

With all of that, the delegate class now looks like:

template <typename MF_T>
class DelegateT_ : public ObjectT<MF_T>
{
  MF_T m_method;
  Object* m_pTarget;

protected:
  DelegateT_() : m_pTarget(NULL), m_method(NULL) {}
  DelegateT_(Object& o, const MF_T& mf) :
    m_pTarget(&o), m_method(mf) {}

public:
  MF_T Method() const {
    return m_method;
  }
  Object& Target() const {
    return *m_pTarget;
  }
};

The template argument is now a member function pointer typedef (generated as shown above), and the Invoke methods are now inherited from the base Object class.

Maintaining a Collection of Delegates

In C#, the Delegate and Event keywords work together to create a list of delegates. Referring to the first example above:

  new DelegatesAndEvents.PrintString(d.SimpleDelegateFunction);

creates a new delegate object similar to my C++ implementation:

StrLen_Delegate d(str, &Str::Len);

The MyPrintString object is an event that has an overloaded operator+= for adding delegates. Similar things in C++ can be done to duplicate this functionality.

The Delegate keyword in C# really creates a MultiCastDelegate object (see [3] for details). You’ll notice that I called the delegate class above DelegateT_ (the trailing underscore indicates that the name is reserved). Strictly speaking, the name _DelegateT is reserved for the implementation (as is __DelegateT) because a capital letter follows the underscore. _delegateT would be OK (only one underscore followed by a lowercase letter), but I tend to avoid any potential for problems with leading underscores (somebody else reading my code might not be aware of all the rules) rather than use a trailing one instead. The reason for making DelegateT_ reserved is that the delegate class that duplicates .NET functionality is derived from MultiCastDelegate.

Delegate objects can easily be stored in a Standard C++ container. I use list since it is closest to what .NET does. Depending on your needs, a vector or deque could also be used. Using a set provides the interesting characteristic of invoking each delegate only once no matter how many times it’s added. The first part of MultiCastDelegate is written as:

template <typename MF_T, typename ARG1 = VoidType,
  typename ARG2 = VoidType>
class MulticastDelegateT : public DelegateT_<MF_T>
{
  typedef DelegateT_<MF_T> Delegate;
  typedef std::list<Delegate> Delegates_t;
protected:
  MulticastDelegateT() {}
public:
  MulticastDelegateT(Object& o, const MF_T& mf) :
    Delegate(o, mf) {}

  MulticastDelegateT& operator+=(const Delegate& d) {
    m_delegates.push_back(d);
    return *this;
  }

private:
  Delegates_t m_delegates;
};

This uses a list together with a few convenient typedefs to store a collection of delegates. It needs to derive from DelegateT_ since I’m in turn going to derive DelegateT from MultiCastDelegateT as the real delegate class.

Firing an event in C# iterates over all the stored delegates and invokes each one. Since I’m using a standard container, this is simple using iterators:

  void operator()(ARG1 v1 = VoidType(),
    ARG2 v2 = VoidType()) const {
    for (Delegates_t::const_iterator it = m_delegates.begin();
      it != m_delegates.end(); ++it)
      (it->Target()).Invoke(it->Method(), v1, v2);
  }

Even if you’re comfortable with Standard C++ containers, this is quite a line of code: using an iterator to call a member function all within a templatized class! Dereferencing the iterator makes it easier to see what’s going on:

const Delegate& d = *it;
d.Invoke(d.Method(), v1, v2);

and if you’re not comfortable with iterators, you can index a deque just like an array:

for (int i=0; i<m_delegates.size(); i++)
  Delegate d = m_delegates[i];

At this point, you could add the following template member function to the DelegateT_ class:

  template <typename ARG1, typename ARG2>
  void Invoke_(ARG1 v1 = ARG1(), ARG2 v2 = ARG2()) const {
    this->Invoke(m_method, v1, v2);
  }

which would free the MultiCastDelegateT::Invoke method from having to pass the member function pointer to Object::Invoke:

d.Invoke_(v1, v2);

However, that requires each argument to have a default constructor, which isn’t always the case. Also, since MultiCastDelegateT is the real delegate base class, it didn’t seem like too much of a burden to call the Object::Invoke directory — even if the code is more complicated because of it. (It also causes the dreaded “Internal Compiler Error” in Visual C++ .NET).

The actual delegate class is now just a simple wrapper around MultiCastDelegateT:

template <typename MF_T, typename ARG1 = VoidType,
  typename ARG2 = VoidType>
struct DelegateT :
  public MulticastDelegateT<MF_T, ARG1, ARG2>
{
  DelegateT(Object& o, const MF_T& mf) :
    MulticastDelegateT<MF_T, ARG1, ARG2>(o, mf) {}
  DelegateT() {}
  typedef DelegateT<MF_T, ARG1, ARG2> Event;
};

Its main purpose is to provide the Event typedef.

Putting It All Together

You can now write the DelegatesAndEvents class from the C# example in C++:

class DelegatesAndEvents
{
  // C#: public delegate void PrintString(string s);
  typedef DelegateT<Object::void1_T<std::string>::mf_t,
    std::string> PrintString_;
public:
  template <typename OBJECT>
    static PrintString_ PrintString(OBJECT& o,
      void (OBJECT::*mf)(std::string)) {
    return PrintString_(o,
      static_cast<Object::void1_T<std::string>::mf_t>(mf));
  }
  // C#: public event PrintString MyPrintString;
  PrintString_::Event MyPrintString;  

  void FirePrintString(std::string s) {
    MyPrintString(s);
  }
};

The syntax is horrible and could probably be simplified with a few clever macros if desired. But macros have a bad reputation these days, and the whole point of this exercise is to understand the details. In any case, you can appreciate the work the C# compiler is doing for you.

The first line creates a private typedef of the member function pointer called PrintString_. It’s a shame that the argument type std::string has to be listed twice, but that’s a result of Visual C++ not supporting partial template specialization. The static method provides an easy way to create a delegate of your type, which lets you write:

DelegatesAndEvents::PrintString_ 
  myDelegate =
  DelegatesAndEvents::PrintString(d,
    &MyDelegates::SimpleDelegateFunction);

which is similar to the C# code above.

Then, the event is created using the Event typedef from DelegateT_. Notice how this series of typedefs allows the C++ code to at least have some resemblance to C#. Finally, there is a method to fire the event, which is practically identical to C#. (Because you’re using a standard container, you don’t have to worry about a NULL list.)

Client code for using this delegate and event is now straightforward and also quite similar to the C# code (again, this code is slightly abbreviated):

struct MyDelegates : public ObjectT<MyDelegates>
{
  // ... Name omitted...
  void SimpleDelegateFunction(std::string s)
  {
    printf("SimpleDelegateFunction called from %s,
      string=%s\n", m_name.c_str(), s.c_str());
  }
  // ... more methods ...
};
void CppStyle()
{
     DelegatesAndEvents dae;

    MyDelegates d;
  d.Name() = "Obj1";

  dae.MyPrintString += DelegatesAndEvents::PrintString
    (d, &MyDelegates::SimpleDelegateFunction);
    // ... more code similar to the above few lines ...

  dae.FirePrintString("Event fired!");
}

Notice how MultiCastDelegateT::operator+= is called to add each new delegate returned by the static method DelegatesAndEvents::PrintString to the list of delegates.

Managed C++

Since delegates and events are part of the .NET framework, all .NET languages can use them. The template-based implementation I’ve described is specific to C++. Microsoft has taken a different approach to exposing this functionality in C++ — an extension to Standard C++ called Managed C++. Perhaps not too surprisingly, writing this sample in Managed C++ is quite similar to the original code:

public __gc struct DelegatesAndEvents {
   __event void MyPrintString(String* s);
   void FirePrintString(String* s) {
    MyPrintString(s);
   }
};

__gc struct MyDelegates
{
  String* Name;
  void SimpleDelegateFunction(String* s) {
    Console::WriteLine
      ("SimpleDelegateFunction called from {0} string={1}",
       Name, s);
  }
};

void ManagedCpp()
{
  DelegatesAndEvents* dae = new DelegatesAndEvents();

  MyDelegates* d = new MyDelegates();
  d->Name = "Obj1";
  __hook(&DelegatesAndEvents::MyPrintString, dae,
    &MyDelegates::SimpleDelegateFunction, d);

  dae->FirePrintString(S"Event fired!");
}

The keyword __gc marks a class as being garbage collected (managed); no calls to delete are needed. The single __event keyword does the work of most of the code presented above. Notice that instead of providing operator+=, Managed C++ uses the __hook keyword instead. You might find it interesting to run the (managed) C++ compiler with the -Fx flag [4] on the above code and examine the resulting .mrg file. It sure is easier to add new functionality at the compiler level instead of writing templates.

Conclusion

By using some rather advanced C++ techniques, I’ve shown that it is possible to implement delegates and events for some simple example code. This implementation is intentionally based on the .NET Framework. A more elegant and pure C++ solution might leverage adapters and binders from the Standard C++ library.

References

[1] Jeffrey Richter. “An Introduction to Delegates,” MSDN Magazine, April 2001, <http://msdn.microsoft.com/msdnmag/issues/01/04/net/net0104.asp>.

[2] Richard Grimes. “.NET Delegates: Making Asynchronous Method Calls in the .NET Environment,” MSDN Magazine, August 2001, <http://msdn.microsoft.com/msdnmag/issues/01/08/Async/Async.asp>.

[3] Jeffrey Richter. “Delegates, Part 2,” MSDN Magazine, June 2001, <http://msdn.microsoft.com/msdnmag/issues/01/06/net/net0106.asp>.

[4] Bobby Schmidt. “The Red Pill,” April 23, 2002, <http://msdn.microsoft.com/library/en-us/dndeepc/html/deep04232002.asp>.

J. Daniel Smith is a software engineer with Autodesk in Novi, Michigan. He holds a B.S. from Calvin College and a M.S. in Computer Science from Michigan State University. You may contact him at cuj@jdanielsmith.org.