Click here to Skip to main content
15,860,861 members
Articles / Programming Languages / C#

Interfaces vs Delegates

Rate me:
Please Sign up or sign in to vote.
4.91/5 (68 votes)
17 Jan 2014CPOL11 min read 148.9K   1.2K   177   63
This article explores the advantages and disadvantages of interfaces that can be used to replace delegates, considering performance, ease of use and versatility.

Background

Having different options can be great if one of the options is really more suited for a specific task, but having more options can also make decisions harder when different options have different advantages and disadvantages.

I often need to stop and carefully think if an interface will or will not do a better job than a delegate and sometimes I even go back to my old code, that I initially implemented using delegates, to replace the delegates by interfaces. So, I decided it was time to write an article and show the advantages and disadvantages of both approaches.

Note: I know that interfaces have a different purpose than delegates, yet we can always use an interface where a delegate is expected and, in some cases, it is useful to do it.

Performance

I often see people asking if interfaces are faster than delegates or if delegates are faster than interfaces and then I see answers like:

  • Interfaces are faster. Delegates are too damn slow;
  • Delegates are faster because they are only a pointer to a method. Interfaces need to use a v-table to then find a delegate;
  • They are equal, but delegates are easier to use.

Well, those are not true. Maybe in .NET 1 delegates were really slower but, actually, the truth is:

  • Delegates are faster... to execute.
  • Interfaces are faster... to get.

For example, in this code:

C#
Action action = SomeMethod;

We are getting an Action (a type of delegate) to call SomeMethod. The problem is: delegates are reference types that contain both the instance and the pointer to a method. They are not a single pointer to a method and, by being reference types, they need to allocate memory. So, each time you transform a method to a delegate you are allocating a new object.

If delegates were value-types that could be different, but they are not and we need to live with them this way.

On the other hand, if we do this:

C#
IRunnable runnable = this;

If the actual object implements IRunnable we simple get the same reference with a different cast. There is no allocation involved. So, when I saw a speed comparison like this:

  • For Delegates:
    C#
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    for(int i=0; i<COUNT; i++)
    {
      Action action = SomeMethod;
      action();
    }
    stopwatch.Stop();
    Console.WriteLine(stopwatch.Elapsed);
  • For Interfaces:
    C#
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    for(int i=0; i<COUNT; i++)
    {
      IRunnable runnable = this;
      runnable.Run();
    }
    stopwatch.Stop();
    Console.WriteLine(stopwatch.Elapsed);

I know that the interfaces are going to win. Not because they execute faster, but because at each iteration a new Action delegate is allocated. But put the delegate getter and the interface getter outside of the loop and the delegate will be a little faster.

When creating events, for example, we put the delegate only once on the event and then the event can be invoked thousands of times. That is, a single allocation is done.

So, is there a winner?

Well, for events (aside from the fact that the event keyword requires a delegate), delegates are really better.

But before saying that delegates are better or faster, let's see different cases.

Anonymous methods

In my opinion anonymous methods are the worst use of delegates but, at the same time, are becoming the most common ones.

When you do a call like this:

C#
for(int i=0; i<count; i++)
  MethodThatReceivesADelegate(() => SomeCall(i));

The compiler is, in fact, creating an instance to hold the value of i. Then, it is creating another instance (the delegate) with a reference to that instance.

If an interface was used instead, it will need to allocate a single object, which will implement the interface.

Possible complaint

Someone could complain that a single object is allocated to hold the value of i then, at each iteration, the value inside that instance is changed. And it is even possible to argue that the compiler could optimize the delegate allocation, allocating it only once.

Well, for the delegate allocation I don't know, but about a single instance to hold the i value that's true, and that's also a bug. If MethodThatReceivesADelegate is giving the delegate to another thread, such other thread may receive the wrong value of i. In C# 5 such behavior was corrected. That is, at each iteration a new object is created. This guarantees the good result if the delegate is given to another thread, but that also means that a new delegate instance should be allocated each time.

If that MethodThatReceivesADelegate is going to use the delegate only once, using an interface would do a better job. Unfortunately, we don't have the option to implement anonymous interfaces.

So, if it is for the convenience, the delegate will still be better. But if it is for performance, in this situation, an interface will be better because it will avoid one unnecessary allocation.

In fact I created the IRunnable interface to force the users of my remoting framework to implement a new type instead of using anonymous delegates to solve the problem of the mutable i value used in fors (or any value used in foreachs, as both kinds of for share the same problem) but that also gave me a little performance benefit.

Invoke and DynamicInvoke

At this moment we know that there are anonymous delegates but there are no anonymous interfaces and that for single use-cases interfaces will have a better performance than delegates because they will require a single object instead of two.

This already makes me think if I should use a delegate or an interface when I have a performance critical method that's going to receive a method to be executed but is going to execute it only once.

But there are more situations we can use such objects that are also performance related.

Do you ever need to call DynamicInvoke instead of the direct delegate invoke? Maybe because you don't know the delegate's parameter types at compile time?

Well, having an interface you have the option to use a base interface to do the untyped call. I don't know why, but reflection invokes and delegates' DynamicInvokes are extremely slow, much slower than simple doing the casts, the array-length validation and putting a try/catch to generate the TargetInvocationException.

So, if you have something like:

C#
public interface IDynamicInvokable
{
  object DynamicInvoke(params object[] parameters);
}

And then you create any of your delegate like interfaces as sub-interfaces of IDynamicInvokable, like this:

C#
public interface IAction<T>:
  IDynamicInvokable
{
  void Invoke(T parameter);
}

You will be giving your users the possibility to call your interface with the typed Invoke method or, if they don't know the exactly interface type at compile-time, they could use the more generic one, the IDynamicInvoke.

Note: I hate the name generics. To me, the IDynamicInvokable is the most generic way to do the calls, and the IAction<T> is the typed interface. So, when I said generic I was refering to the common and untyped way of doing the call, and not to generics, which are typed.

So, if I am going to do thousands of calls to a delegate, but using DynamicInvoke instead of Invoke, an interface will do a better job.

Again, I will question myself: Is the easy of use of anonymous delegates worth? Should I make it harder for the callers of my method only to have the best performance? Is this really going to impact the overall application performance?

Generics, variance and untyped uses

I just said that I hate the name generics, as the code that uses generics is the typed code and we may want to have an untyped code, which I consider more generic.

But let's really talk about .NET generics. Imagine that you know the number of parameters a delegate has but you don't know their exact types. This is not the same as DynamicInvoke, as such method simple receives the parameters as an array.

The generic covariance and contravariance can help a little on that situation. Only a little.

For example, we can get a Func<string> as a Func<object>. Or an Action<object> as an Action<string>.

The reason is simple. When returning a value (the Func case), a string is an object. It will not do any kind of conversion, it will simple return a string that the caller will see as an untyped object, but that's ok. And for the Action case, it expects an object and, again, a string will be a valid object, that's OK too.

But, what happens if I want to get a Func<int> as a Func<object>? Or the more common case, I want to pass all parameters cast as object. Will it work?

The answer is no. Even if an int is an object in the .NET concept, all value types need boxing, which is an extra action. Simple trying to get an int as an object, without the boxing process, will create serious problems, and that's why it is not supported.

But interfaces have an advantage here, if well designed. I have a personal rule: Everytime I have a generic type (be it a class or even an interface) I also create a more generic interface... well, an untyped interface, with all the same methods and properties, but using object instead of the generic argument types, and that's a base interface for the generic type.

That is, if I have an IAction<T>, I will have an IAction interface. If I have an IAction<T1, T2> I will have an IAction2 interface.

The truth is: I will love to have the option of getting an Action<int> as an Action<> and let the .NET know I want to use a generic delegate untyped. But the .NET does not support untyped-uses of generic types, so I add an extra interface with the untyped methods and properties to my generic classes and interfaces and I achieve that. But that's simple impossible to do for delegates. So the interfaces are winning this point.

Different uses

We already have the Invoke and the DynamicInvoke. What about a TryInvoke?

My last two articles talked about conversions and I will return to that kind of situation.

If I use the Converter<TInput, TOutput> delegate, the conversion should work or should throw an exception. But exceptions are the wrong way to say that a conversion failed if the code is prepared to deal with invalid values.

I considered creating another delegate (TryConverter) that, well, will return a boolean value to tell if the conversion worked and use an out parameter for the result.

That will be great for the cases where we have an exception-free conversion, like int.TryParse, but if we don't have one (like when the conversion is done by a TypeConverter) we will need to catch the exception to return false.

That's not a real problem. The problem is that I still want to give the version that generates exception. In such case, the exception will be caugh to return false, to then generate another exception. Terrible.

But an interface solves such problem. With an interface we can have both methods, the Convert and the TryConvert.

So, the Convert can use a conversion that throws an exception and the TryConvert can use a conversion that does not throws an exception.

If there is only a conversion that throws a exception, then the TryConvert will be forced to catch the exception. If there is only a conversion that does not generates an exception and can fail, then the Convert will need to check for that and generate the exception, but we will avoid the case where an exception is caugh to return false to then generate another exception.

In this case such versatility makes the interface the best solution, without a comparable delegate solution and surely giving better performance than a TryConvert only delegate.

For those who read my other articles, you can expect an update of the Converters article to use the interface solution, which will support both Convert and TryConvert and the CastedGet will be eliminated as an untyped interface will do the job.

Conclusion

I still question myself if I should use an interface or a delegate when a method is going to receive such to use it only once as interfaces are faster for those cases and delegates have the compiler support to be anonymous.

For normal events I don't question that delegates are better, but for the most part (in general when I register delegates and allow users to find them) I am replacing delegates by interfaces, because the latter allows better typed to untyped support, which is easier to use and also performs better.

But let's finish by a small list of points:

Delegates:

  • Are reference-types, so they allocate an entire object only to reference a method;
  • Are the fastest to call when you know all parameter types at compile-time;
  • Allow the use of anonymous delegates which really simplify creating single-line or very small delegates;
  • Can reference a private method without requiring to create a new type.

Interfaces:

  • Don't allocate new objects, so they are faster to get;
  • Are faster for single-use cases, as only one object will be created instead of two;
  • If well designed allow for generic (untyped) uses that are faster than DynamicInvoke of delegates;
  • If well designed, generic interfaces can be accessed by an untyped interface that has the same signature methods and parameters, only changing the generic type parameters by object;
  • Allow different calling possibilities (like the Convert and TryConvert);
  • Are a little slower to call with the rightly typed parameters;
  • Don't have anonymous compile-time support;
  • Require full types to be created even if a single method is needed.

Sample

The sample application will only execute speed comparisons for the different situations.

Initially all the tests were executing 100 million of iterations, but the DynamicInvoke was so slow that I decided to reduce the test to 10 millions of iterations.

The output of this application on my work computer is:

This application tests the speed of interfaces and delegates in
different situations. Compile it in release and execute it outside
Visual Studio to get the right results.

The following tests do 100 millions of iterations:
Testing delegate speed, the wrong way: 00:00:01.6483403
Testing interface speed, the wrong way: 00:00:00.5369746

Testing delegate speed, the right way: 00:00:00.3757670
Testing interface speed, the right way: 00:00:00.4831114

Testing anonymous delegate speed: 00:00:01.7475340
Testing an interface that does the same: 00:00:01.1950063

The following tests do only 10 millions of iterations:
Testing delegate's DynamicInvoke speed: 00:00:37.0368337
Testing interface's DynamicInvoke speed: 00:00:00.3218726

All the tests are finished. Press ENTER to exit.

Version History

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
Questionis performance relevant? Pin
Mr.PoorEnglish27-Nov-13 10:52
Mr.PoorEnglish27-Nov-13 10:52 
AnswerRe: is performance relevant? Pin
Paulo Zemek27-Nov-13 10:59
mvaPaulo Zemek27-Nov-13 10:59 
GeneralRe: is performance relevant? Pin
Mr.PoorEnglish27-Nov-13 11:27
Mr.PoorEnglish27-Nov-13 11:27 
GeneralRe: is performance relevant? Pin
Paulo Zemek27-Nov-13 11:38
mvaPaulo Zemek27-Nov-13 11:38 
BugC#5 not .NET 4.5 Pin
Mackenzie Zastrow27-Nov-13 7:36
Mackenzie Zastrow27-Nov-13 7:36 
GeneralRe: C#5 not .NET 4.5 Pin
Paulo Zemek27-Nov-13 9:14
mvaPaulo Zemek27-Nov-13 9:14 
QuestionNicely done..one question Pin
FatCatProgrammer4-Aug-13 5:17
FatCatProgrammer4-Aug-13 5:17 
AnswerRe: Nicely done..one question Pin
Paulo Zemek4-Aug-13 7:10
mvaPaulo Zemek4-Aug-13 7:10 
GeneralRe: Nicely done..one question Pin
FatCatProgrammer4-Aug-13 15:09
FatCatProgrammer4-Aug-13 15:09 
QuestionVery good. Pin
Septimus Hedgehog22-Jul-13 21:13
Septimus Hedgehog22-Jul-13 21:13 
AnswerRe: Very good. Pin
Paulo Zemek23-Jul-13 2:00
mvaPaulo Zemek23-Jul-13 2:00 
GeneralMy vote of 5 Pin
CzimerA6-Mar-13 21:17
CzimerA6-Mar-13 21:17 
GeneralRe: My vote of 5 Pin
Paulo Zemek7-Mar-13 3:05
mvaPaulo Zemek7-Mar-13 3:05 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA18-Nov-12 7:34
professionalȘtefan-Mihai MOGA18-Nov-12 7:34 
GeneralRe: My vote of 5 Pin
Paulo Zemek18-Nov-12 9:53
mvaPaulo Zemek18-Nov-12 9:53 
GeneralMy vote of 5 Pin
Pankaj Nikam12-Nov-12 0:04
professionalPankaj Nikam12-Nov-12 0:04 
GeneralRe: My vote of 5 Pin
Paulo Zemek12-Nov-12 9:25
mvaPaulo Zemek12-Nov-12 9:25 
GeneralMy vote of 5 Pin
_AK_11-Nov-12 23:23
_AK_11-Nov-12 23:23 
GeneralRe: My vote of 5 Pin
Paulo Zemek12-Nov-12 9:26
mvaPaulo Zemek12-Nov-12 9:26 
GeneralMy vote of 5 Pin
Monjurul Habib9-Nov-12 8:52
professionalMonjurul Habib9-Nov-12 8:52 
GeneralRe: My vote of 5 Pin
Paulo Zemek9-Nov-12 10:00
mvaPaulo Zemek9-Nov-12 10:00 
GeneralMy vote of 5 Pin
Akram El Assas7-Nov-12 9:37
Akram El Assas7-Nov-12 9:37 
GeneralRe: My vote of 5 Pin
Paulo Zemek7-Nov-12 10:10
mvaPaulo Zemek7-Nov-12 10:10 
GeneralMy vote of 5 Pin
Abhishek Pant6-Nov-12 19:12
professionalAbhishek Pant6-Nov-12 19:12 
GeneralRe: My vote of 5 Pin
Paulo Zemek7-Nov-12 2:03
mvaPaulo Zemek7-Nov-12 2:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.