Roslyn: [Proposal] Non-Capturing Lambdas with ==>

Created on 27 May 2016  路  16Comments  路  Source: dotnet/roslyn

A well-known issue in C# programming is inadvertent lambda capture, in which a lambda, which was intended to be statically allocated, mistakenly closes over some local state -- which forces a full display class allocation and delegate allocation. ReSharper evidently has a warning for this case, but it is as often intentional as not.

Some way for the programmer to express their intent -- that a particular lambda _not_ close over local state -- is needed.

https://github.com/dotnet/roslyn/issues/117 is a proposal to bring full-fledged C++-like lambda capture lists to C#. This would solve the problem, but at the cost of extra syntax for all lambdas, and a maintenance burden for lambdas which do capture (proportional to the number of locals captured).

The main distinction that is needed is binary: is this lambda intended to be capturing, or not?

This proposal is to introduce a new token to the language when defining lambdas: the ==> token. The ==> token, when used in a lambda definition in place of =>, indicates "this lambda must not capture local state," and causes a compiler error if there are any open variables in the lambda.

So in the case of

intEnumerable.Where(i ==> i > 0).Select(i ==> i.ToString())

both of the lambdas would be fine, as neither one captures local state.

But this would be an error:

intEnumerable.Where(i ==> i > intEnumerable.Count)

as the lambda is defined with ==> but references a local variable.

(The motivation for "==>" as the token is that it is obviously close to the existing syntax, in fact so close as to be almost completely unobtrusive (and hence widely usable), while hopefully fitting reasonably well into the existing tokenization rules of the language.)

As far as expressing this in the IL, it is possible that this syntax could result in applying an attribute to the generated method for the lambda, as suggested under item 6 of https://github.com/dotnet/roslyn/issues/1898

The implementation intent is that the compiler reliably lifts ==> lambdas into static allocations, with no runtime allocation cost required when invoking them.

0 - Backlog Area-Language Design Feature Request Language-C#

Most helpful comment

@gulshan

The -> operator already exists in C# for pointer dereferencing member access, which is why it couldn't be used as the normal lambda operator. It would create syntax ambiguities.

All 16 comments

While it would involve more syntax I kind of think that such a problem could easily be handled by analyzers. An assembly or class could be annotated with attributes to specify whether capturing lambdas are allowed, and then additional attributes could be applied to specific methods to white/blacklist capturing:

[assembly:  AllowCaptures(false)]

static class Program {
    static void Foo() {
        int max = 5;
        int[] array = Enumerable.Range(0, 10).Where(i => i < max).ToArray();  // analyzer error
    }

    [AllowCaptures(true)]
    static void Bar() {
        int max = 5;
        int[] array = Enumerable.Range(0, 10).Where(i => i < max).ToArray();  // good
    }
}

It's more verbose and the granularity could only be at the method level, but it does achieve the desired goal.

I am not sure method granularity is adequate. Though I agree this would be possible to prototype without parser changes, which is a significant benefit.

I also agree that in many cases, one does want to ensure that _all_ lambdas in a given code region don't capture, so perhaps it would be a good experiment to try -- especially accompanied by reliable static lifting of non-capturing lambdas.

Very few code bases are so allocation sensitive that this warning would be of any use. This feature is too niche for the C# language.

Wasn't there a proposal to allow putting compiletime-only attributes anywhere? This could be implemented through static analysis looking for such attributes.

I think i agree that this is too niche. If your hot path is so allocation sensitive then i think it is reasonable to fallback to more basic language constructs that involve less compiler magic.

I don't see the benefit offered by this. The value will be captured if it is used. If it is used, it is needed. In other words you very likely need to write a different function with different semantics. I also dislike the idea of introducing a new syntax for anonymous methods, and less powerful anonymous methods at that.

I really like how lambdas work in C++ and really want it to work like this.

In C++ I have full control over what gets captured and what doesn't and how I want it either by value or by reference, I'm biased but I'm sure that new comers to the C++ syntax (or capture list) will have no problem to pick it up simply because it's explicit and very clear, not to mention more powerful.

Adding extra character doesn't really tell me anything about what it does but annoys me, it's ugly, at least in my opinion.

@eyalsk See #117

@AlgorithmsAreCool yeah, I know about this, thanks! :)

Just thought to share my opinion about this suggestion.

A workaround that can be used to avoid capturing is to define and return the lambda from at static method and pass all variables to the method.

Then only the passed variables will be captured.

Example (a bit contrived but I have used a similar version to solve over capture)

void Main()
{
    var a = new[] {1,3,5,7,9,11,13,15,17};

    var l = a.Select(x => Test(x));

    l.Select(x => x.Invoke()).Dump();
}

//This method will only capture the v variable.
static Func<bool> Test(int v) => () => v < 10;

I would like a thin arrow -> for non-capturing lambdas, while current => thick arrow continues to be capturing lambda. One more thing I would like have with non-capturing lambdas, type inference (with let keyword). Allowing something like- let sqr = x -> x*x;

I still do not see any reason to have this in the language at all. It adds syntactic complexity and noise to introduce a less expressive form of lambdas. If you do not want to close over any outer lexical bindings, do not use them in the lambda. If you use the value it is needed.

@gulshan

The -> operator already exists in C# for pointer dereferencing member access, which is why it couldn't be used as the normal lambda operator. It would create syntax ambiguities.

I don't understand, an empty capture list indicates a non-capturing lambda [](x, y) => {}, do we really need a separate syntax for that case?

If we had capture lists, then no, we would not.

The overall point of the proposal was to allow the programmer to express that the lambda _should not_ capture, so that any inadvertent capture would be flagged as an error. The assumption is that non-capturing lambdas are an important enough case (due to their ability to be statically allocated) that special support to facilitate their use is warranted.

"Very few code bases are so allocation sensitive that this warning would be of any use. This feature is too niche for the C# language."

Perhaps so. In the code bases I personally work with (systems code, including games and browser components), performance demands and allocation-sensitivity are both very high. The C# language's reach is broadening, and the point of this feature is to make this scenario _less_ niche.

I'm pushing for non-capturing lambdas and this is why: http://stackoverflow.com/q/43217853/1236397

Since C# 6, people shouldn't be forced to push all static lambdas for expression building now into the class making clutter. I vote for either some non-capturing syntax, or the "static" modifier, which is basically much the same thing.

I'll go ahead and close this discussion. Feel free to continue on csharplang.
Here's one issue that's related: https://github.com/dotnet/csharplang/issues/275 (there may be others too).
Thanks

Was this page helpful?
0 / 5 - 0 ratings