1. Introduction
When you're programming, sometimes you need to describe a simple action that's used only once and won't be useful anywhere else. Creating a separate method for it is like hammering a nail with a plane model toolkit. It's much easier to grab something handy and solve the task "on the spot".
Anonymous method is a temporary piece of code that doesn't need a name because it's used right where it's created. It:
- Is declared inside another method.
- Has no name.
- Is usually used once — for example, passed to a delegate or subscribed to an event.
Nowadays lambda expressions (the => operator) are used more often, but understanding anonymous methods helps to get how delegates and the event model in C# work.
When are anonymous methods convenient?
- You need to quickly pass a piece of logic as a parameter, e.g., for sorting, filtering, events, etc.
- There's no point in cluttering the class with separate methods for one-off logic.
- When you want the code to be compact and readable: all the "small business logic" in one place.
A bit of history
Anonymous methods first appeared in C# version 2.0. Before that using delegates was clumsy: you had to declare a separate named method even if its logic was used in one place. With anonymous methods things got simpler, and with lambdas this idea reached a new level of conciseness.
2. Classic: delegates and a usual "named" method
Before meeting anonymous methods, let's recall the standard way to pass behavior via delegates. Suppose we have a "Book database" app where we want to filter books by different conditions.
// Define the delegate
public delegate bool BookFilter(Book book);
// Book class
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
Previously we wrote like this:
// Separate filter function
public static bool IsClassic(Book b)
{
return b.Year < 1970;
}
// Somewhere in the application code
BookFilter filter = IsClassic;
Creating a separate method every time isn't always convenient. What if there are dozens of filters?
3. Anonymous method: minimalism, friendly with delegates
An anonymous method allows you to define the filter code right where you need it:
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
That's it! No extra methods, everything in place. delegate acts as a declaration of an unnamed function.
General syntax
delegate([arguments])
{
// method body
};
Example embedded in a program:
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
public delegate bool BookFilter(Book book);
class Program
{
static void Main()
{
Book[] books = {
new Book { Title = "Master and Margarita", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
// Anonymous filter for classics
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
foreach (Book book in books)
{
if (filter(book)) // invoke the anonymous function!
Console.WriteLine($"{book.Title} — classic!");
}
}
}
Output:
Master and Margarita — classic!
4. Anonymous method with different delegates
Anonymous methods work fine with all delegates. For example, the standard Action and Func<T, TResult>, which we'll cover in detail later.
Action<string> sayHello = delegate(string name)
{
Console.WriteLine($"Hello, {name}!");
};
sayHello("World"); // Hello, World!
Short illustration: how a "one-off" anonymous method works
The easiest way to imagine an anonymous method is like a temporary worker: shows up for a short job, does it — and disappears. We bind the worker and the job via a delegate.
Func<int, int, int> sum = delegate (int a, int b) {
return a + b;
};
Console.WriteLine(sum(5, 7)); // 12
Use cases: sorting, searching, collection processing
Suppose we have a list of books that we want to sort by publication year. Declaring a separate method for a one-off comparison is excessive.
var books = new List<Book>
{
new Book { Title = "Master and Margarita", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
books.Sort(delegate(Book a, Book b)
{
return a.Year.CompareTo(b.Year);
});
foreach (var book in books)
Console.WriteLine($"{book.Title} ({book.Year})");
5. Useful nuances
Is an anonymous method the same as a lambda expression?
In modern C# lambda expressions (=>) are used more often, and usually there's little difference, but there are some fundamental differences:
- Lambdas are shorter and more expressive.
- Lambdas have unambiguous variable capture rules (we'll learn this a bit later).
- Anonymous methods appeared earlier and are sometimes found in legacy codebases.
Here's how our example would look with a lambda:
BookFilter filter = b => b.Year < 1970;
Still, knowing both old and new syntax is useful for interviews, reading other people's code and for a deep understanding of delegates.
Anonymous methods with and without parameters
If the delegate takes nothing, you can write it in one line:
Action printHello = delegate { Console.WriteLine("Hello!"); };
printHello(); // Hello!
If it takes parameters — you can still write it in one line:
Action<int> printSquare = delegate (int x) { Console.WriteLine(x * x); };
printSquare(6); // 36
Anonymous method and Lambda expression
| Anonymous method | Lambda expression | |
|---|---|---|
| Syntax | |
|
| Variable capture | Yes | Yes |
| Popularity | Rarely used | Standard |
| Return value | Can/required | Can/required |
| Multiline | Possible | Possible |
6. Anonymous methods — small details and usage nuances
Capturing local variables
Anonymous methods can use variables from the surrounding method — this mechanism is called a "closure". For example:
int minYear = 1970;
BookFilter filter = delegate(Book book)
{
return book.Year < minYear;
};
Console.WriteLine(filter(new Book { Title = "Test", Year = 1960 })); // True
If you later change minYear, the filter will use the new value!
You can omit parameters
If parameters aren't needed:
Action sayHi = delegate { Console.WriteLine("Hi!"); };
Passing null
If a delegate hasn't been assigned a method (for example, an anonymous one), its value is null, and attempting to invoke it will throw a NullReferenceException. Be careful.
Multiline
An anonymous method can contain a full block of code, conditions, loops and even other calls:
Action manyThings = delegate
{
Console.WriteLine("We started!");
for (int i = 0; i < 3; i++)
Console.WriteLine(i);
Console.WriteLine("We finished!");
};
manyThings();
7. Common mistakes and quirks
Sometimes developers confuse variable scope: capturing loop variables or nested-method variables can lead to surprising results.
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(delegate { Console.WriteLine(i); });
}
foreach (var action in actions) action(); // 3 3 3 — surprise!
The thing is an anonymous method "sees" the variable i — when the loop finished, i became 3. All methods print the same value. For correct behavior it's better to capture the variable like this:
for (int i = 0; i < 3; i++)
{
int current = i;
actions.Add(delegate { Console.WriteLine(current); });
}
GO TO FULL VERSION