Anonymous Method in C#: Syntax, Closures, Events and Lambdas (.NET 10)

An anonymous method in C# is a block of code with no name. You write it with the delegate keyword and assign it straight to a delegate type such as Func, Action or one you declare yourself:

Func<decimal, decimal> addSalesTax = delegate (decimal subtotal)
{
    return Math.Round(subtotal * 1.0875m, 2);
};
Console.WriteLine(addSalesTax(42.50m));   // 46.22

Anonymous methods arrived in C# 2.0, and lambdas replaced them in day-to-day code a version later. They still compile on .NET 10, though, and there’s one trick lambdas can’t do. Below, I run both styles against the same small list of coffee orders.

The Sample Coffee Orders We’ll Use

The examples share four online orders from a Sacramento coffee roaster, stored as a List<Order> in C#: Jenna Morales ($42.50), Marcus Reed ($18.75), Olivia Chen ($96.00) and Caleb Turner ($7.25).

Shipping is free at $40, so only Jenna and Olivia qualify, and Caleb’s $7.25 is the tiny order filters love to miss.

Anonymous Method Syntax in C#

It’s the delegate keyword, an optional parameter list and a block body, ending with a semicolon:

DelegateType name = delegate (Type1 p1, Type2 p2) { /* statements */ };

The compiler converts it to a delegate, so the left side must be a delegate type, a delegate parameter or var. If delegates are new to you, start with delegates in C#.

Method 1: Assign an Anonymous Method to Func and Action (Recommended)

This is the form you’ll meet most. Func returns a value and Action doesn’t. Same order list, now with a free-shipping check and a print step:

List<Order> orders = [
    new("Jenna Morales", "Sacramento", 42.50m),
    new("Marcus Reed", "Fresno", 18.75m),
    new("Olivia Chen", "San Diego", 96.00m),
    new("Caleb Turner", "Reno", 7.25m)];

Func<Order, bool> freeShipping = delegate (Order o) { return o.Total >= 40m; };
Action<Order> print = delegate (Order o)
{
    Console.WriteLine($"{o.Customer,-14} {o.City,-11} ${o.Total:0.00}");
};

orders.Where(freeShipping).ToList().ForEach(print);
Console.WriteLine($"Free shipping: {orders.Count(freeShipping)} of {orders.Count}");

record Order(string Customer, string City, decimal Total);

Output:

Jenna Morales  Sacramento  $42.50
Olivia Chen    San Diego   $96.00
Free shipping: 2 of 4
Anonymous method in C# assigned to Func<Order, bool> and Action<Order>, filtering four coffee orders in VS Code
Two anonymous methods: one decides, one prints

How does this code work?

freeShipping takes an Order and returns true at $40 or more, and LINQ’s Where and Count accept it like a named method. print returns nothing, so it’s an Action<Order>. The result: Jenna and Olivia, 2 of 4.

Method 2: Use an Anonymous Method with a Custom Delegate Type

A named delegate type says what the code is for, the same way one of your named methods in C# would. Same sample list, now with a shipping rule:

List<Order> orders = [
    new("Jenna Morales", "Sacramento", 42.50m),
    new("Marcus Reed", "Fresno", 18.75m),
    new("Olivia Chen", "San Diego", 96.00m),
    new("Caleb Turner", "Reno", 7.25m)];

ShippingRule rule = delegate (Order o, decimal freeAt)
{
    return o.Total >= freeAt ? 0m : 6.95m;
};
foreach (var o in orders)
    Console.WriteLine($"{o.Customer,-14} shipping ${rule(o, 40m):0.00}");

var small = orders.FindAll(delegate (Order o) { return o.Total < 10m; });
Console.WriteLine($"Small orders: {small.Count}");

delegate decimal ShippingRule(Order order, decimal freeAt);
record Order(string Customer, string City, decimal Total);

Output:

Jenna Morales  shipping $0.00
Marcus Reed    shipping $6.95
Olivia Chen    shipping $0.00
Caleb Turner   shipping $6.95
Small orders: 1

How does this code work?

The anonymous method must match ShippingRule exactly, so both parameter types are spelled out. The foreach loop calls rule(o, 40m) like a method: Marcus and Caleb pay $6.95.

FindAll takes a Predicate<Order>, and the inline anonymous method converts to it with no variable. Only Caleb is under $10.

Method 3: Capture Outer Variables in an Anonymous Method (Closures)

An anonymous method can read and change the local variables around it. Here’s the same order list with a threshold that changes:

List<Order> orders = [
    new("Jenna Morales", "Sacramento", 42.50m),
    new("Marcus Reed", "Fresno", 18.75m),
    new("Olivia Chen", "San Diego", 96.00m),
    new("Caleb Turner", "Reno", 7.25m)];

decimal freeAt = 40m;
int calls = 0;
Func<Order, bool> qualifies = delegate (Order o)
{
    calls++;                   // writes the outer variable
    return o.Total >= freeAt;  // reads freeAt when it runs
};
Console.WriteLine($"At $40: {orders.Count(qualifies)} orders");
freeAt = 15m;                  // holiday promo lowers the bar
Console.WriteLine($"At $15: {orders.Count(qualifies)} orders");
Console.WriteLine($"Anonymous method ran {calls} times");

record Order(string Customer, string City, decimal Total);

Output:

At $40: 2 orders
At $15: 3 orders
Anonymous method ran 8 times
C# anonymous method capturing outer variables freeAt and calls: 2 orders at $40, 3 orders at $15, ran 8 times
The anonymous method sees the new threshold without being recreated

How does this code work?

freeAt and calls are captured, not copied, so lowering the bar to $15 changes the answer from 2 orders to 3. Each Count walks all four orders, so calls++ runs 8 times.

The compiler keeps captured local variables alive as long as the delegate lives. Important: you can’t capture a ref, out or in parameter (CS1628).

Method 4: Anonymous Methods as Event Handlers (Omit the Parameter List)

Here’s the trick lambdas can’t do. Write delegate { ... } with no parameter list, and it converts to a delegate with any parameters, perfect for handlers that ignore sender and e:

var shop = new OrderQueue();
EventHandler<OrderEventArgs> ping =
    delegate { Console.WriteLine("Ping the barista"); };
shop.OrderPlaced += ping;           // no parameter list above
shop.OrderPlaced += delegate (object? s, OrderEventArgs e)
    { Console.WriteLine($"New order: {e.Order.Customer}"); };
shop.Place(new("Jenna Morales", "Sacramento", 42.50m));
shop.OrderPlaced -= ping;           // needs the same instance
shop.Place(new("Olivia Chen", "San Diego", 96.00m));

class OrderQueue
{
    public event EventHandler<OrderEventArgs>? OrderPlaced;
    public void Place(Order o) =>
        OrderPlaced?.Invoke(this, new(o));
}
class OrderEventArgs(Order o) : EventArgs { public Order Order => o; }
record Order(string Customer, string City, decimal Total);

Output:

Ping the barista
New order: Jenna Morales
New order: Olivia Chen
C# anonymous method as an event handler without a parameter list, subscribed and unsubscribed with += and -=
The parameterless handler runs once, then it’s removed

How does this code work?

ping skips the (object? s, OrderEventArgs e) signature, and the compiler fills it in from EventHandler<OrderEventArgs>. So Jenna’s order pings the barista, and Olivia’s, after -=, is only logged.

The second handler has no variable, so you can’t remove it. My guide to events in C# explains why -= needs the same delegate instance.

Method 5: Static Anonymous Methods and Discards (C# 9)

Mark an anonymous method static and it can’t capture locals or this, so you never allocate a closure by accident. Discards _ mark unused parameters:

const decimal TaxRate = 0.0875m;          // a constant is fine in a static method
Func<decimal, decimal> tax = static delegate (decimal subtotal)
{
    return Math.Round(subtotal * TaxRate, 2);
};
Func<decimal, decimal, decimal> flatFee =
    delegate (decimal _, decimal _) { return 6.95m; };   // discards (C# 9)

Console.WriteLine($"Tax on $96.00: ${tax(96.00m):0.00}");
Console.WriteLine($"Flat fee: ${flatFee(96.00m, 2m):0.00}");

Output:

Tax on $96.00: $8.40
Flat fee: $6.95

How does this code work?

TaxRate is a C# constant, so the static method may use it: $8.40 on Olivia’s order. Make it a plain local and the build fails with CS8820.

Anonymous Method vs Lambda Expression: Which Should You Use?

Lambdas (C# 3.0) do everything an anonymous method does with less typing, and Microsoft Learn recommends them everywhere except the parameterless form. Same order list, both styles:

using System.Linq.Expressions;

List<Order> orders = [
    new("Jenna Morales", "Sacramento", 42.50m),
    new("Marcus Reed", "Fresno", 18.75m),
    new("Olivia Chen", "San Diego", 96.00m),
    new("Caleb Turner", "Reno", 7.25m)];

Func<Order, bool> oldWay = delegate (Order o) { return o.Total >= 40m; };
Func<Order, bool> newWay = o => o.Total >= 40m;
Console.WriteLine($"Old: {orders.Count(oldWay)}, new: {orders.Count(newWay)}");

var natural = delegate (Order o) { return o.Total >= 40m; };
Console.WriteLine($"Natural type: {natural.GetType().Name}");

Expression<Func<Order, bool>> tree = o => o.Total >= 40m;
Console.WriteLine($"Expression tree: {tree}");

record Order(string Customer, string City, decimal Total);

Output:

Old: 2, new: 2
Natural type: Func`2
Expression tree: o => (o.Total >= 40)
C# anonymous method vs lambda expression: both count 2 orders, natural type Func`2, and an expression tree built from a lambda
Same filter, same answer; only the lambda becomes an expression tree

How does this code work?

Both delegates count 2 orders. Since C# 10, a typed anonymous method also gets a natural type, so var gives Func`2. Only the lambda becomes an expression tree; the anonymous version fails with CS1946.

FeatureAnonymous methodLambda
Expression bodyNo, block onlyYes
Inferred parameter typesNoYes
Omit the parameter listYesNo
Expression trees (EF Core)No (CS1946)Yes
static, discards, capturesYesYes

Pro Tip: write lambdas in new code and convert old delegate (...) blocks when you touch the file. C# 14 even lets lambdas use ref or out without types (what’s new in C# 14).

Things to Keep in Mind

  • break, continue and goto can’t leave the body (CS1632). Use return.
  • Outer ref/out/in parameters can’t be captured (CS1628). Copy the value to a local first.
  • Need an IQueryable filter for EF Core? Use a lambda; anonymous methods can’t be expression trees.
  • Keep handlers in a variable if you’ll unsubscribe. A new delegate { } is a different instance.

Want more ways to shape methods and their results? Try these next:

Frequently Asked Questions

What is an anonymous method in C#?

An unnamed block of code created with the delegate keyword and assigned to a delegate type, for example Func<decimal, decimal> tax = delegate (decimal s) { return s * 0.0875m; };. It was added in C# 2.0.

What is the difference between an anonymous method and a lambda expression?

Lambdas are shorter, infer parameter types, allow expression bodies and can become expression trees. Anonymous methods can omit the parameter list, which lambdas can’t.

Are anonymous methods obsolete in C#?

No, they compile on .NET 10. The delegate operator page on Microsoft Learn recommends lambdas except for the parameterless form.

Can an anonymous method return a value?

Yes. Use return in the block and assign it to a delegate with a return type, such as Func<Order, bool>.

Can I use an anonymous method as an event handler?

Yes. shop.OrderPlaced += delegate { ... }; fits any event signature. Store it in a variable if you’ll remove it with -=.