Delegate is a type in C# that references a block of code. This block of code can be named or anonymous method or lambdas (in C# 3.0). They are like function pointers in C++. Though they are not purely function pointers as they are 'type safe'. Return value and parameters of code block are defined in the delegate declaration.
e.g. public delegate void Print(string message);
This delegate is named as execute which can refer to any method (or code block) which will take one string argument and returns void.
Delegates allows us to use a block of code as data object. As delegate is just a data type like others, it can be passed around to methods and called whenever required. They be default extends System.MulticastDelegate, which means a delegate can refer to more than one methods or code blocks.
Delegate objects can refer to both static and instance methods. Also, Delegates can refer to anonymous methods as follows.
public delegate void Print(string message);
Print print = delegate(string message){
Console.writeline("Message : " + message);
}
In C# 3.0, the same can be written much cleanly using lambdas as -
public delegate void Print(string message);
Print print = message => Console.writeline("Message : " + message);
C# Delegates which uses anonymous methods and lambdas are essentially closures. However, they are not exactly same as closures in pure functional languages like ML. Anonymous methods or lambdas do not capture the variable values when they are definied. The variables used in the anonymous methods are used in the moved to heap and shared between anonymous methods and outer scope. The value of outer variables are modified if they are changed by the delegate. So the changes made by the delegate in the lexical environment propogates back to parent scope.
Delegates with anonymous methods and lambdas are not 'true' closures if we consider the closures in pure functional languages like ML as the lexical environment of delegate is not closed and changes made by delegate are visible in the outer scope. But there are ways to get around this. In C#, we have a name-variable binding instead of name-value binding(which is used in pure functional languages where variables are immutable).