📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

What are anonymous methods in C# (the delegate keyword syntax)? How do they differ from lambdas, what can they still do that lambdas cannot, can they be static, and would you use them in new code?

C# 2 anonymous methods: syntax, captured variables, omitting the parameter list, static anonymous methods, the lack of expression-tree conversion, and how they compare with lambdas.

Intermediate Theory Intermediate anonymous-methods closures csharp delegates lambdas

🎯 Key points

  • Anonymous method syntax: `delegate (int x) { return x * 2; }` (C# 2)
  • They compile like lambdas: to a method, plus a closure class if they capture variables
  • Unique feature: the parameter list can be omitted (`delegate { ... }`) to convert to any delegate type whose parameters you ignore, as long as it has no out parameters
  • They cannot be converted to expression trees and must have a block body
  • C# 9 allows `static delegate (...) { }` to forbid captures; discards are allowed for parameters
  • Lambdas superseded them: shorter, expression bodies, natural types, attributes, default parameters
  • In new code use lambdas; recognise anonymous methods when maintaining older code

⏱ 30-second answer

Anonymous methods are the C# 2 syntax for inline delegates: delegate (int x) { return x * 2; }. They compile the same way as lambdas, into a method plus a closure class if they capture variables. Lambdas replaced them in C# 3 because they are shorter, can have expression bodies and can become expression trees, which anonymous methods cannot. The one thing anonymous methods still do is omit the parameter list entirely, delegate { ... }, to ignore all parameters. In new code I use lambdas, with discards for unused parameters.

🗣 Full answer (senior)

How I would answer this in an interview

Anonymous methods (C# 2.0) are inline, unnamed methods written with the delegate keyword: delegate (int x) { return x * 2; }. Lambdas (C# 3.0) replaced them for almost every purpose. Both compile to the same thing (a compiler-generated method, plus a closure class if they capture). I'd explain the differences, the one trick anonymous methods still have, static support, and whether to use them today.

1. Syntax comparison

// Anonymous method (C# 2)
Func<int, int> doubleIt = delegate (int x) { return x * 2; };
button.Click += delegate (object? s, EventArgs e) { Save(); };

// Lambda (C# 3+)
Func<int, int> doubleIt2 = x => x * 2;
button.Click += (s, e) => Save();

2. Differences

Anonymous method Lambda
Syntax delegate (params) { body } (params) => expr or => { body }
Expression body No (block only) Yes
Omitting the parameter list Yes: delegate { ... } converts to any delegate type whose parameters you don't use No (must declare parameters or discards)
Parameter type inference No — types must be written if parameters are declared Yes
Expression trees (Expression<Func<...>>) No Yes — essential for LINQ providers/EF Core
Natural type (var f = ...) No Yes (C# 10)
Attributes, explicit return type, default parameters No Yes (C# 10/12)
async Yes (async delegate { ... }) Yes
static Yes (C# 9) Yes (C# 9)
Discard parameters Yes (C# 9: delegate (int _, int _) { }) Yes
Captures (closures) Same mechanism Same mechanism

3. What anonymous methods can still do that lambdas can't

The parameterless anonymous method converts to a delegate of any parameter list (as long as there are no out parameters), because you omit the list entirely:

EventHandler<OrderPlacedEventArgs> h = delegate { _counter.Increment(); };   // ignores (sender, e)
Action<int, string, Guid> ignoreAll = delegate { Console.WriteLine("called"); };

With lambdas you must write the parameters (or discards): (_, _) => _counter.Increment(). Since C# 9 lambda discards make this almost as concise, so the advantage is marginal. It's mainly handy when the parameter list is long or awkward to spell.

(Historically there was also a quirk: a parameterless anonymous method could be convertible to multiple delegate types, sometimes causing overload ambiguity.)

4. Can they be static?

Yes. Since C# 9, both static lambdas and static anonymous methods are allowed:

Func<int, int> square = static delegate (int x) { return x * x; };

static forbids capturing locals, parameters or this, guaranteeing no closure allocation and no accidental captures.

5. Would I use them in new code?

Almost never. Lambdas are shorter, support type inference, expression bodies, expression trees, natural types, attributes and default parameters, and every C# developer reads them fluently. I'd only consider delegate { ... } in rare cases where ignoring a long parameter list improves readability — and even then (_, _) => is usually clear enough. In legacy code, I'd leave existing anonymous methods alone unless touching that code anyway; converting is a safe, IDE-assisted refactoring.

6. A production example

In a legacy WinForms codebase migrated to .NET 8, a code-analysis pass flagged hundreds of anonymous methods. Rather than a risky mass rewrite, we let the IDE "convert to lambda" fixer run file by file during normal feature work, and added an .editorconfig rule preferring lambdas. One conversion revealed a bug: an anonymous method captured a loop variable in a for loop — same capture semantics as lambdas — which we fixed by copying to a local.

🔍 What the interviewer is checking

Checks depth of language knowledge and ability to work with legacy code.

Green flags: knows the syntax and that it compiles like a lambda; names the omitted-parameter-list feature; knows they cannot be expression trees; prefers lambdas in new code.

Red flags: confuses anonymous methods with anonymous types; thinks they do not capture variables.

📚 Learn in depth

1. Compilation

Both anonymous methods and lambdas compile to:

  • a static method on the containing class if they capture nothing and the compiler chooses that strategy,
  • or (more commonly) an instance method on a singleton <>c class with a cached delegate in a static field, if they capture nothing,
  • or a method on a display class (closure) if they capture locals or parameters, with a new display class instance per scope entry, plus a delegate per evaluation,
  • or an instance method on the containing class if they capture only this.

The IL is the same whichever syntax you used.

2. Conversion rules for parameterless anonymous methods

delegate { ... } (no parameter list) is compatible with any delegate type where:

  • the delegate has no out parameters,
  • the return type matches the body's return statements (or void).

This made them useful in C# 2 for events and callbacks where parameters were irrelevant. With overloaded methods taking different delegate types, a parameterless anonymous method can be ambiguous — the compiler may report CS0121.

3. Why lambdas support expression trees and anonymous methods don't

When a lambda targets Expression<TDelegate>, the compiler emits code that builds an expression tree (calls to Expression.Lambda, Expression.Call, etc.) instead of compiling a method. The C# design team only implemented this for lambdas with expression bodies (and limited statement forms). Anonymous methods always have block bodies with statements — not representable in LINQ expression trees, which are expression-oriented.

4. Lambda features added over time

Version Feature
C# 3 Lambdas, expression trees
C# 5 async lambdas
C# 9 static lambdas/anonymous methods, discard parameters
C# 10 Natural types, attributes on lambdas, explicit return types
C# 12 Default parameter values, params in lambdas
C# 14 Parameter modifiers (ref, out, scoped) without explicit types

Anonymous methods received only static and discards — they're effectively frozen.

5. Capture semantics (shared)

Both capture variables, not values. A for loop variable captured by either syntax is shared across iterations; foreach variables are fresh per iteration since C# 5. Closures over this keep the object alive as long as the delegate lives.

Try it yourself

  1. Write the same callback as an anonymous method and a lambda and compare IL in SharpLab.
  2. Assign delegate { } to Action<int, string> and to EventHandler.
  3. Try converting an anonymous method to Expression<Func<int, int>> and read the error.
  4. Add static to an anonymous method that captures a local and read CS8820.

💻 Code

namespace Delegates.AnonymousMethods;

public static class Program
{
    public static void Main()
    {
        // Classic anonymous method
        Func<int, int> doubleIt = delegate (int x) { return x * 2; };

        // Parameter list omitted: converts to any delegate without out parameters
        Action noArgs = delegate { Console.WriteLine("no args"); };
        Action<int, string> ignoresBoth = delegate { Console.WriteLine("ignored both parameters"); };
        EventHandler onTick = delegate { Console.WriteLine("tick"); };

        // Captures like a lambda
        var threshold = 10;
        Predicate<int> isBig = delegate (int x) { return x > threshold; };
        threshold = 3; // closure sees the variable, not a copy
        Console.WriteLine(isBig(5)); // True

        // Static anonymous method (C# 9): capturing would be a compile error
        Func<int, int> square = static delegate (int x) { return x * x; };

        // Modern equivalents
        Func<int, int> doubleLambda = x => x * 2;
        EventHandler onTickLambda = (_, _) => Console.WriteLine("tick (lambda)");

        // Expression<Func<int, int>> expr = delegate (int x) { return x; }; // CS1946: not convertible to expression tree

        noArgs();
        ignoresBoth(1, "a");
        onTick(null, EventArgs.Empty);
        onTickLambda(null, EventArgs.Empty);
        Console.WriteLine(doubleIt(4) + doubleLambda(4) + square(3));
    }
}

⚠ Common mistakes

1. Writing anonymous methods in new code

What happens: delegate (x) { ... } style used out of habit. Why it is wrong: more verbose, no inference or expression trees. Do instead: use lambdas.

2. Mass-rewriting legacy code without tests

What happens: converting all anonymous methods in one big change. Why it is wrong: churn and risk for no behaviour change; may hide subtle capture issues. Do instead: convert opportunistically with IDE fixes.

3. Assuming different capture semantics

What happens: believing anonymous methods capture by value. Why it is wrong: both capture variables; loop-variable bugs apply equally. Do instead: copy loop variables to locals before capturing in for loops.

4. Expecting expression trees from anonymous methods

What happens: passing an anonymous method to EF Core's Where. Why it is wrong: can't convert to Expression<Func<...>>. Do instead: use expression-bodied lambdas for queryable APIs.

5. Overload ambiguity with parameterless anonymous methods

What happens: delegate { } passed to overloaded methods. Why it is wrong: CS0121 ambiguity. Do instead: use a lambda with explicit parameters.

6. Capturing this in long-lived subscriptions

What happens: anonymous methods subscribed to static events. Why it is wrong: keeps the object alive and can't be unsubscribed without a stored reference. Do instead: store the delegate and unsubscribe, or avoid capturing.

7. Not using static for non-capturing callbacks

What happens: callbacks accidentally capture locals in hot paths. Why it is wrong: closure allocations. Do instead: mark them static.

8. Async anonymous methods as async void

What happens: async delegate { ... } assigned to Action. Why it is wrong: exceptions crash the process; completion isn't observable. Do instead: target Func<Task>.

🏛 Senior insight: trade-offs

This is a history-and-maintenance question. Seniors explain that anonymous methods and lambdas share the same compilation model, name the one remaining unique feature, and show they can modernise legacy code safely. I treat migrating to lambdas as a low-risk cleanup that also opens the door to static lambdas and expression trees.

🔗 Follow-ups the interviewer may ask

  1. When: Candidate says they would use lambdas in new code. Why: Lambdas replaced anonymous methods; tests modern lambda features.
    How to answer

    A lambda is an anonymous function, either an expression like x => x * 2 or a statement block. It has no type until it is converted, either to a delegate, which is compiled code, or to an Expression<Func<...>>, which is a data structure EF Core can turn into SQL. Since C# 10 a lambda can have a natural type, so var f = (int x) => x * 2 is a Func<int,int>. When it captures variables, the compiler hoists them into a closure class, which allocates. static lambdas forbid capture. Newer additions are attributes and return types (C# 10), default parameters (C# 12), and C# 14 parameter modifiers like out without writing types.

  2. When: Candidate compares anonymous functions with named helpers. Why: Local functions are the other alternative to inline delegates.
    How to answer

    A local function is a named method declared inside another method. Unlike a lambda it can be recursive, generic, an iterator or async, and called before it is declared. The big practical difference is allocation: if a local function captures variables and is only called directly, the compiler passes them in a struct by reference, so there is no heap allocation, while a capturing lambda always allocates a closure object and a delegate. A static local function cannot capture anything, which guarantees no hidden allocations. I use them for helpers used in one method and for the eager-validation pattern in iterators and async methods.

More in Delegates and Events

Last updated 30 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me