Delegates provide additional level of abstraction, because you can abstract out some action expressed in the code and pass is as a parameter in some other method, like any other parameter.
This is called "first-class citizen".
OOP virtual methods and late binding does something like that (and
this
is also a method parameter, hidden on), but delegated has no limitations: delegate instances can accept any method in its invocation list; if the method has matching signature it can be virtual or not, static or instance method, of the same class or any other (which is the main difference between a delegate and a virtual method).
To show the main purpose of first-class citizen conception, imaging you have a complex data traversal algorithm. You want to select only a sub-set part of data members by some predicate and perform some action on each. The traversal algorithm should be universal, agnostic to both action and predicate. Here is how:
void TraverseData(
MyDataContainer data,
Func<DataElement> predicate,
Action<DataElement> dataProcessor) {
DateElement element =
if (predicate(element))
dataProcessor(element);
}
[EDIT — fixed reference]
See also my other answer:
What is the Extra Advantage of Delegate[
^]
[EDIT]
For advanced use of delegates, see my new article on the topic:
Dynamic Method Dispatcher[
^]
—SA