18

The other day in one of my utilities, ReSharper hinted me about the piece of code below stating that lambda defining the delegate ThreadStart can be turned into a local function:

public void Start(ThreadPriority threadPriority = ThreadPriority.Lowest)
{
    if (!Enabled)
    {
        _threadCancellationRequested = false;

        ThreadStart threadStart = () => NotificationTimer (ref _interval, ref _ignoreDurationThreshold, ref _threadCancellationRequested);

        Thread = new Thread(threadStart) {Priority = ThreadPriority.Lowest};
        Thread.Start();
    }
}

And hence transformed into:

public void Start(ThreadPriority threadPriority = ThreadPriority.Lowest)
{
    if (!Enabled)
    {
        _threadCancellationRequested = false;

        void ThreadStart() => NotificationTimer(ref _interval, ref _ignoreDurationThreshold, ref _threadCancellationRequested);

        Thread = new Thread(ThreadStart) {Priority = ThreadPriority.Lowest};
        Thread.Start();
    }
}

What are the benefits of the latter over the former, is it only about performance?

I've already checked the resources below but in my example the benefits are not that obvious:

Natalie Perret
  • 8,013
  • 12
  • 66
  • 129
  • I would say close because it is opinion based. But it is a really interesting question. :-) – Al Kepp Oct 09 '17 at 22:46
  • 1
    local functions support recursion in one line, with lambdas you have to do a little trick to do that, they also don't create garbage like lambdas do when capturing closures. – M.kazem Akhgary Oct 09 '17 at 22:46
  • 4
    @AlKepp this is **not** opinion based, the question asks about benefits of local functions, else why would they even introduced local function. question would be opinion based if OP were asking which you prefer. clearly it doesn't. – M.kazem Akhgary Oct 09 '17 at 22:51
  • 1
    @M.kazemAkhgary Oh I didn't think about that, makes sense, that can be a big pro. – Natalie Perret Oct 09 '17 at 22:55

1 Answers1

15

The 1st website you linked mentions some benefits of local functions:
- A lambda causes allocation.
- There's no elegant way of writing a recursive lambda.
- They can't use yield return and probably some other things.

One useful use-case is iterators:

Wrong way:

public static IEnumerable<T> SomeExtensionMethod<T>(this IEnumerable<T> source) {
    //Since this method uses deferred execution,
    //this exception will not be thrown until enumeration starts.
    if (source == null)
        throw new ArgumentNullException();
    yield return something;
}

Right way:

public static IEnumerable<T> SomeExtensionMethod<T>(this IEnumerable<T> source) {
    if (source == null)
        throw new ArgumentNullException();
    return Iterator();

    IEnumerable<T> Iterator() {
        yield return something;
    }
}
Dennis_E
  • 8,751
  • 23
  • 29
  • 2
    iterators as local functions is huge benefit of local functions over lambdas +1 – M.kazem Akhgary Oct 09 '17 at 23:01
  • @Dennis_E: agreed this is a nice improvement! – Natalie Perret Oct 09 '17 at 23:06
  • *no `ref`, `out`, `params`, optional parameters* Can you provide reference for this? Because I am able to implement delegates with `ref`, `out`, `params` and optional parameters with lambda. – user4003407 Oct 10 '17 at 05:30
  • 1
    @PetSerAl The first site he linked mentioned it. I just didn't think about it. You're right of course. But you need to declare a delegate first, so they can't be used out-of-the-box. None of the standard delegates in .NET (like `Func` or `Action`) have `ref` or `out` parameters. – Dennis_E Oct 10 '17 at 07:51
  • @Dennis_E Can you explain why it's the right way. I just want to make sure I understand as they are many APIs in .NET that do this and I've seen stuff like that internally for private code. Was it 'bad' code due to a technical limitation of the past and if so, we should strive to correct it? – Kevin Avignon Oct 23 '20 at 11:11
  • 1
    @KevinAvignon As said in the code comment, the 1st example will not throw the exception until you start enumerating. We want the exception to be thrown right away, which the 2nd example does. Before C#7, we just made a private method. So you had the public `SomeExtensionMethod` that does the check and throw and a private method (usually called `SomeExceptionMethodImpl`) that had the `yield return`. But that private method would also be visible to the rest of the class. It is only used in one place, so by making it a local function, it can only be used by `SomeExtensionMethod`. – Dennis_E Oct 23 '20 at 16:13