-2

I was looking through the code of AutoMapper ASP.NET Core dependency injection, and in the AddAutoMapperClasses, I saw that they declared a lambda expression after the last return:

private static IServiceCollection AddAutoMapperClasses(...)
{
    // ... code
    foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes().Where(type => type.IsClass && !type.IsAbstract && IsAmType(type))))
    // ... more code

    return services;
    bool IsAmType(Type type) => Array.Exists(AmTypes, openType => ...);
}

I've never seen something like this before, and I was wondering how and why did it worked for the compiler, since IsAmType was not declared before being called in the foreach loop.

Also is this good practice?

3
  • 6
    Local functions: learn.microsoft.com/en-us/dotnet/csharp/programming-guide/… Commented Jun 26 at 9:51
  • 4
    FWIW your bool IsAmType isn't a lambda, it's a local function Commented Jun 26 at 9:52
  • 1
    Additionally, it is very common to declare local functions at the end of the enclosing method. In particular, if a local function uses a local variable defined outside the local function, then the local function MUST be defined AFTER the definition of the local variable. Commented Jun 26 at 9:56

1 Answer 1

1

This works because IsAmType is not a lambda, but rather it is a local function. And that works because local functions are "just" syntax sugar and are "expanded" (in C# it's called "lowering") to a (compiler generated) "regular" method on your class. So, something like this

public class Program
{    
    private static int Foo(int[] toSum)
    {
        var sum = 0;
        foreach (var element in toSum.Where(ShouldSelect))
            sum += element;

        return sum;
        
        bool ShouldSelect(int num) => num % 2 == 0;
    }
}

Gets lowered to something like this (ignoring that foreach is also syntax sugar that gets lowered) which then gets successfully compiled.

public class Program
{    
    private static int Foo(int[] toSum)
    {
        var sum = 0;
        foreach (var element in toSum.Where(SomeCompileGeneratedNameForShouldSelect))
            sum += element;

        return sum;
    }

    internal static bool SomeCompileGeneratedNameForShouldSelect(int num)
    {
        return num % 2 == 0;
    }
}

If IsAmType actually were a lambda (an anonymous function), you'd (expectedly) get a compiler error, i.e. this

public class Program
{    
    private static int Foo(int[] toSum)
    {
        var sum = 0;
        foreach (var element in toSum.Where(bar))
            sum += element;

        return sum;
        
        Predicate<int> bar = num => num % 2 == 0;
    }
}

Doesn't compile with the error message:

error CS0841: Cannot use local variable 'bar' before it is declared

You can see what these examples (roughly) get lowered to here


It is very common for local functions to be defined at the end of the scope, and if local functions capture a variable (local functions can capture variables much like anonymous functions capture variables) from the surrounding context they actually have to be defined after those variables it captures are defined.

Whether or not this (and local functions) are good practice entirely depends on the context and is largely opinion based, as such I won't go into detail on that.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.