7

In C#, we have delegates, which are essentially variables (holders) for methods that follow a signature. So, I could write

    delegate void MyDelegate(int num);
    MyDelegate myMethodHolder;

Now, what's always perplexed me about this is the reason for the first line. The language designers decided upon a syntax like the one above rather than something more along the lines of

   delegate myDelegate = new delegate (int) -> void

Normally, we would want something in a variable in order to either reuse it, or to be able to keep multiple versions of (potentially the same) data. But the first line in simply marks out a method signature. There would never be a need for something like:

    delegate void MyDelegate(int num);
    delegate void MyDelegate2(int num);

Even if I wanted to use more meaningful variable names, the value is questionable, since what we've designated is a signature:

    delegate void intToVoid(int num)

So, my question then, is what is the possible advantage of storing the signature separately from the creation of myMethodHolder?

I know that this question (as I understand it) is somewhat speculative, so I would understand if it gets closed for that reason. However, I'm hoping that someone will be able to point to a more fundamental misunderstanding on my part, in which case the question wouldn't be speculative at all :)

10
  • its so you can define a function signature that calling code (that you havent written) can pass in.
    – Ewan
    Commented Oct 26, 2021 at 19:53
  • 1
    @Ewan I think OP is asking more along the lines "why C# 1 that did not have any functional programming love did not select more functional-language-like syntax for defining name of delegates"... But I probably misunderstanding the question. Commented Oct 26, 2021 at 19:58
  • yeah the question is confusing. Ben, can you explain what you are asking?
    – Ewan
    Commented Oct 26, 2021 at 20:01
  • 1
    Type Safety Software architecture and syntax is not just about what works now - it's about what is least likely to end up with people making mistakes later.
    – J...
    Commented Oct 27, 2021 at 16:51
  • 1
    Another consideration about the way event and delegate were designed is that they make it easy to implement the Event-based Asynchronous Pattern (EAP) -- which tended to exist in most popular Windows-based languages/frameworks even before C# existed (The pattern and similar syntax was widely used in older Microsoft and Borland technologies. e.g. VB6, MFC, Win32 API, Delphi, Borland VCL, etc.). A lot of design choices for C# and .NET Framework were about trying to encourage the entire Windows developer community along for the ride. Commented Oct 28, 2021 at 6:33

6 Answers 6

9

Using explicitly defined delegates has fallen out of favour in more modern versions of C#; you rarely need or want to do that nowadays.

But giving a name to a delegate type surely isn't that hard to understand. Even today, plenty of people find it hard to work with anonymous function signatures. And when you look at more functional languages than C#, you'll also find people heavily prefer to use very specific types; that certainly wasn't a thing in C# 1.0.

Now consider a method like this:

int HandleDoodad(Func<string, string, int, string, int> doodad) ...

You have absolutely no idea what kind of function you're supposed to pass in. Being able to name things is a great help:

delegate int WeightCalculator(string catalogue, string itemId, int quote, string language);
int HandleDoodad(WeightCalculator doodad) ...

And people often went way beyond that; they defined interfaces and classes, not just delegates (even when delegates would have sufficed). Most people interested in C# didn't work at the kind of abstraction that was common in the more functional languages of the time.

When you're thinking about delegates nowadays, you're probably thinking about very generic things, like a search predicate in a LINQ clause. But that wasn't done back when C# 1.0 was being designed. Delegates were mostly used for extension points and event handlers. And while C# would allow you to use delegate types interchangeably as long as they had the same signature, it was still helpful to have distinct types for delegates that happened to have the same signature, but also had a completely different meaning.

Needless to say, effort was also a part of the deliberation. C# was a pretty small project when it got off the ground. They had to cover a lot of ground, fast. They managed to make something that was a decent competition to Borland Delphi, Visual Basic, Java, PHP... in one "small" version. If C# 1.0 had generics from the get go, for example, a lot of the weird things in C# wouldn't exist today. There were a lot of interesting proposals for C# features, but they had to be cut or simplified to make a good, early release. And as someone who worked with C# all the way from early previews, it's hard to explain just how awesome each new version was back in the day :)

Of course, as C# matures as a more functional language, it becomes ever easier to avoid primitive types altogether, and we get more descriptive types in general. The more descriptive the types themselves are, the less you need that from delegates using that type (especially with compile-time type-safety) - and that's how you get back to something like Func<Catalogue, ItemId, Quote, Language, Weight>.

6
  • I'm still using delegate types instead of Func/Action where possible. Having names for the parameters is very nice, but using boxes to name parameters seems like a hack.
    – Domi
    Commented Oct 27, 2021 at 12:57
  • "And while C# would allow you to use delegate types interchangeably as long as they had the same signature" isn't quite true: you can convert any method (anonymous or otherwise) to any delegate of the same signature, but you can't convert between delegates themselves. (otherwise a nice answer, but I think this point is important) Commented Oct 27, 2021 at 13:14
  • 1
    I've never done this before, but this answer was so good that I unaccepted my accepted response and accepted this one instead. The first one I accepted was also excellent, of course, but in addition to the deeper insights you shared about language design considerations, this made everything click for me. Thank you!
    – Ben I.
    Commented Oct 27, 2021 at 14:25
  • 1
    @VisualMelon: I think many people assume that because method references can be target-typed to a compatible delegate type. And besides parameter names, there's also the benefit of documentation for the parameters.
    – Joey
    Commented Oct 27, 2021 at 21:38
  • 1
    Being able to name things is a great help - no kidding; tell a newbie "oh just use the IEnumerable<T>.GroupJoin<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,IEnumerable<TInner>,TResult>, IEqualityComparer<TKey>) overload for that one" and watch their eyes roll round in their head :D
    – Caius Jard
    Commented Oct 29, 2021 at 16:41
15

In C#, we have delegates, which are essentially variables (holders) for methods that follow a signature.

This is not quite correct. Delegates are types, not variables. So the syntax:

delegate void MyDelegate(int num);

is of that form because you are declaring a new type, MyDelegate (which is actually a concrete class that inherits from the abstract class System.Delegate)

When you then declare:

MyDelegate myMethodHolder;

you are declaring a variable, myMethodHolder, which is of type MyDelegate.

When Microsoft introduced generics into C#, they also created a whole set of generic delegate types, such as Action<T>, which is defined as:

public delegate void Action<T>(T obj);

These days it's therefore unusual to declare your own delegate types, so your variable declaration would more usually be:

Action<int> myMethodHolder;
15

C# is a historically grown language. It is frequently useful to pass around references to methods, especially when writing event-driven code in GUIs. To address this use case, C# 1.0 provided delegates. Delegates were very good, especially compared with alternative languages like Java (which had no comparable mechanism at all at the time).

A delegate must have a specific signature so that calls and assignments to the delegate variable can be type-checked. There are two ways to achieve this:

  • Either, there is some type syntax that represents a delegate type with some signature. This syntax will have to be used wherever the delegate type is referenced.
  • Or, there is a way to declare a type name that represents a signature.

C# 1.0 settled on the second approach, which is advantageous if the delegate type is referenced in multiple places.

The C language used the first approach. Very roughly, the equivalent to your first example would be written in C as:

void (*myMethodHolder)(int) = NULL;

Though C made it also possible to declare an alias:

typedef void (*MyDelegate)(int);
MyDelegate myMethodHolder = NULL;

In C, this second solution is usually preferred since the function type syntax is pretty confusing, especially when denoting functions that return functions or an array of functions.

With C# 2.0, generic type names were introduced that represent common delegates. In a way, this also lets you use the first approach, albeit with less crazy syntax than C. Your example could be written as:

Action<int> myMethodHolder;

Where the delegate returns a value, a Func<...> should be used instead of an Action<...>.

In my opinion, using the Action/Func names are vastly preferable to declaring delegate types yourself. Of course, it is still possible to provide custom names when desired with a type alias like using MyDelegate = Action<int>.

3
  • The comparable Java 1.x mechanism was to define interface MyAction { void doAction(int param); }, and then derive a class that implements the interface function appropriately. Later versions of the language made the syntax more concise.
    – dan04
    Commented Oct 26, 2021 at 23:42
  • 5
    Func, not Function. Also, there are things you can't do with Func/Action, such as delcararing delegate parameters out, ref or in, or having two parameters share a generic type (e.g. delegate void Foo<T>(T a, T b))
    – canton7
    Commented Oct 27, 2021 at 7:28
  • 2
    Interestingly, the C# team has been making progress toward native lambda syntax support during their hackathon with this recent proposal.
    – Kirk Woll
    Commented Oct 27, 2021 at 13:05
3

Not sure I am understanding your question properly.

delegate is perhaps a bit old fashioned now that we have Func, but it allows you to define events and callbacks. (although you also have the generic Eventhander class)

public class EwansClass
{
    public delegate void MyDelegate(string text);

    public event MyDelegate SampleEvent;

    protected virtual void RaiseSampleEvent()
    {
        SampleEvent?.Invoke("Hello"));
    }
}

Here I only define the signature of the method that will be called when the event happens. I don't actually write it. Then Bob can write some calling code which passes a function they have written in to my class.

public class BobsClass
{
     public void CrazyMethod()
     {
        var x = new MyClass();
        x.SampleEvent += MyPassInMethod; //special add as many methods as you like syntax
     }

     private void MyPassInMethod(string e)
     {
       Console.Write(e + " World");
     }
}
2
  • 1
    You could actually do x.SampleEvent = Console.WriteLine; A delegate basically accepts a pointer to a method, which you can assign similar to any other pointer. Commented Oct 26, 2021 at 20:25
  • 1
    yeah, i simplifed the event example because i think its often confusing
    – Ewan
    Commented Oct 26, 2021 at 20:27
2

delegate void MyDelegate(int num); declares a type of delegate function, while MyDelegate myMethodHolder; defines a variable which can hold a delegate function of that type.

The reason for that separation is because it allows for delegate types to be reused. Let's take this example:

public class FoobarScheduler {
    public delegate FoobarResult FoobarOperation(Foobar, FoobarContext, FoobarArguments[]);

    private FoobarOperation currentOperation;
    private FoobarOperation nextOperation;
    private Queue<FoobarOperation> scheduledOperations;
    private List<FoobarOperation> successfulOperatons;
    private List<FoobarOperation> failedOperatons;

    public void Schedule(FoobarOperation operation) {

As you can see, this class declares one type of delegate, and then reuses that type of delegate for a whole bunch of variables.

1

"Why" is always a fun question with a language that has been around a while. Sometimes there's documentation, sometimes there is not.

The C# notation for delegates is basically one-to-one with the CLI, the file format for the executable results of compiling C# code. In CLI, the way one creates a delegate is to create a class that derives from System.Delegate, and implements an invoke function with the correct signature*. Then you create an instance of that delegate class. This design choice permits the definition of delegates without the file format having any special handling for it. All of the handling is done with the class system. It gets to inherit all of the class system behaviors, like how they handle attributes and such.

A low-end CLI interpreter needs only create a plain 'ol class with an invoke function, and it operates like any other class. When I added delegates to the CLI interpreter I wrote, I basically didn't have to do anything at all. A higher end interpreter may replace these with actual function pointers if need be. And, of course, it will want to verify these pointers before executing arbitrary unsafe code. The only "special" requirement to make delegates safe is that there's a particular construction pattern you are obliged to use. It makes it very easy to prove the function pointer is valid.

Having looked at the CLI spec and C#, I get a strong impression that delegates were added reasonably late in the development of CLI. It nests on top of the existing systems suspiciously well. When looking at other late developments Microsoft put into the language, they have a similar flavor. Obviously it would be up to someone at Microsoft to prove or disprove this hypothesis.

* Plus a few fiddly-bits like setting a few flags to let the interpreter know something special is happening.

1
  • OTOH, the existence of MulticastDelegate as a separate type from Delegate, despite there being no real purpose to non-multicast delegates is often explained as it being too "baked in" to change before the earliest release. Commented Oct 28, 2021 at 14:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.