Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Use static keyword for lambdas to disallow capturing locals and parameters (VS 16.8, .NET 5) #275

Open
2 of 5 tasks
mattwar opened this issue Mar 15, 2017 · 71 comments
Assignees
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion
Milestone

Comments

@mattwar
Copy link
Contributor

mattwar commented Mar 15, 2017

Allow Func<int, string> = static i => i.ToString();.

  • Proposal added (here)
  • Discussed in LDM 2019-10-21
  • Decision in LDM
  • Finalized (done, rejected, inactive)
  • Spec'ed

To avoid accidentally capturing any local state when supplying lambda functions for method arguments, prefix the lambda declaration with the static keyword. This makes the lambda function just like a static method, no capturing locals and no access to this or base.

int y = 10;
someMethod(x => x + y); // captures 'y', causing unintended allocation.

with this proposal you could the static keyword to make this an error.

int y = 10;
someMethod(static x => x + y); // error!
const int y = 10;
someMethod(static x => x + y); // okay :-)

LDM history:

  • 10/21/2019
@mattwar
Copy link
Contributor Author

mattwar commented Mar 15, 2017

Related issues/proposals:
Lambda Capture lists: dotnet/roslyn#117
Non-Capture Lambdas with ==> syntax: dotnet/roslyn#11620

@alrz
Copy link
Member

alrz commented Mar 15, 2017

I'd really prefer capture lists overs this, that'd be something like this []() => {}. what advantages we'd have over capture lists?

@mattwar
Copy link
Contributor Author

mattwar commented Mar 15, 2017

@alzr just considering an alternative to capture lists in case we chose not to do capture lists.

@MgSam
Copy link

MgSam commented Mar 15, 2017

I like this better than capture lists, which I think are way too heavyweight.

@DavidArno
Copy link

I don't see the point to this, but it's got a bunch of up-votes. So rather than just down-vote, I'll assume I'm ignorant of the issue. Why is this (and/or capture lists) needed? Is accidental closure capture a problem for people?

@iam3yal
Copy link
Contributor

iam3yal commented Mar 16, 2017

@MgSam Too heavyweight for whom? people want to have control over what gets captured and sometimes how it gets captured and this locks the language into a specific behaviour which seems too limited.

People can always have this as a syntactic sugar to capture lists but doing this first would be a shame.

I have a bad feeling about capture lists, they won't make it. 😆

@jnm2
Copy link
Contributor

jnm2 commented Mar 16, 2017

I'm in the same place as @DavidArno. If there is a special case where you need to disrupt the syntax to be absolutely sure that you aren't capturing anything, is it so bad to refactor it to a static method?

@yaakov-h
Copy link
Member

Does this need to be a language feature or is this something you could reasonably achieve with an Analyzer?

@alrz
Copy link
Member

alrz commented Mar 16, 2017

As another alternative: https://github.com/dotnet/roslyn/issues/6671 (see the example).

@MgSam
Copy link

MgSam commented Mar 16, 2017

@eyalsk

people want to have control over what gets captured and sometimes how it gets captured and this locks the language into a specific behaviour which seems too limited.

"People" is kind of vague and arbitrary, no? I've never had a need for such a feature and even here among C# enthusiasts demand seems tepid at best. While on the other hand there have occasionally been times when I want to ensure that a lambda (or now a local function!) is not closing over anything.

Lambda capture lists add a bunch of new syntax for a narrow use case. And you'll undoubtedly have some programmers overuse them every single time they write a lambda, needlessly complicating code. Whereas this proposal reuses an existing keyword in a lightweight, thoughtful, and natural way.

Plus, anytime a feature suggestion is ripped straight from C++, I'm immediately suspicious. :P

@DavidArno
Copy link

DavidArno commented Mar 16, 2017

@MgSam,

While on the other hand there have occasionally been times when I want to ensure that a lambda (or now a local function!) is not closing over anything.

This is what I'm not getting here. If I "want to ensure that a lambda (or now a local function!) is not closing over anything" then I make it a private method. The only reason I see for creating local functions is because I want to close over something. And regarding lambdas, again I'm struggling to see how I'd "accidentally" create a closure and - even if I did - unless I make a habit of cluttering my lambdas with eg the static keyword, I'd still end up making that mistake.

If we didn't yet have lambdas, then there could be a case for them not allowing closures by default, but this proposal seems a classic case of swinging the stable doors shut as the horse disappears over the horizon...

@MgSam
Copy link

MgSam commented Mar 16, 2017

@DavidArno
You seem to be arguing two contradictory things:

  • Lambdas / local functions are useless without the ability to capture something.
  • Lambdas should have been designed as non-capturing by default. (and by point 1 of your argument, would have therefore been useless)

If I "want to ensure that a lambda (or now a local function!) is not closing over anything" then I make it a private method.

I very highly doubt that you do this. The vast majority of the time when you are using LINQ, you are probably not calling private methods with it, but rather creating lambdas.

It is true that for a chain of LINQ methods prefixing them all with static would be tiresome and noisy. Perhaps that's a reason for instead taking an attribute based approach to this that can either be applied locally to a given lambda or to an entire method / class to ensure no captures anywhere.

@DavidArno
Copy link

DavidArno commented Mar 16, 2017

@MgSam,

Fair point: I didn't express myself very well there 😁

I'll retry, in a hopefully less muddled way:

  1. The only use I can see for local functions is for closures. Ergo I see no point to protecting local functions against closures.
  2. If I create a closure with a lambda, it's because I want to. Sure, I might accidentally do this, but to protect against this I'd have to put static everywhere and consciously remove it when I want a closure.
  3. If we were creating lambdas now, I can just about see a benefit to making them not support closures by default and needing to explicitly declare them as such if I needed a closure. But they exist and create closures by default. Therefore this proposal comes too late.

Now if this were combined with capture lists, then it becomes a more useful feature. Then I could have an analyzer report a "bare" lambda as an error and I get to explicitly say whether it's a closure or not:

someMethod(x => x + y); // analyzer error. Bare closure lambdas are now bad.
someMethod(static x => x + y); // compiler error, y can't be used in a non-closing lambda
someMethod([y] x => x + y); // all is well, compiler and analyzer happy.

However, if the proposal to allow attributes to appear anywhere were implemented, this could be the best solution in my view as I could introduce a [closure] attribute that an analyzer would need to allow closures in lambdas, which then creates an opt-in "pit of success" solution:

someMethod(x => x + y); // analyzer error. Bare closure lambdas are now bad.
someMethod([closure]x => x + y); // analyzer happy.

Update added the link to the attributes anywhere proposal, thanks to @eyalsk and @alrz .

@iam3yal
Copy link
Contributor

iam3yal commented Mar 16, 2017

@MgSam

"People" is kind of vague and arbitrary, no? I've never had a need for such a feature and even here among C# enthusiasts demand seems tepid at best. While on the other hand there have occasionally been times when I want to ensure that a lambda (or now a local function!) is not closing over anything.

Well, you didn't mention anyone so I thought that you referred to just any C# dev out there. :)

Lambda capture lists add a bunch of new syntax for a narrow use case. And you'll undoubtedly have some programmers overuse them every single time they write a lambda, needlessly complicating code. Whereas this proposal reuses an existing keyword in a lightweight, thoughtful, and natural way.

You're right but then you wouldn't have to do it for every lambda and in my opinion this proposal/feature isn't mutually exclusive with capture lists simply because this feature can be a syntactic sugar to capture lists.

Just to give an example where this feature wouldn't really help, today we can do something like this:

public static void ContainingMethod()
{
	int i = 42;

	{int iCopy = i;
	Action a = () =>
	{
		Console.WriteLine(iCopy);
	};}
}

But I think that the following reads a lot better:

public static void ContainingMethod()
{
    int i = 42; // variable to be captured by value
    ...
    Action a = [int iCopy = i]() => {
        Console.WriteLine(iCopy); 
    };
}

Example taken from the capture lists proposal.

Plus, anytime a feature suggestion is ripped straight from C++, I'm immediately suspicious. :P

I don't know, I mean I know that many C++ idioms don't fit in C# but I can't see why this wouldn't make sense here, I mean I'm not suggesting the same rules but syntactically in my opinion, it fits nicely.

@iam3yal
Copy link
Contributor

iam3yal commented Mar 17, 2017

@DavidArno

there was a proposal (which I now can't find) to allow attributes to appear anywhere.

I'm not sure whether you found it but just to let you know, @alrz linked it above you. 😉

@DavidArno
Copy link

@eyalsk,

Thanks. Should have known that @alrz would have a link to it as his GitHub-search-fu is unsurpassed in my experience 😁 Comment updated.

@HaloFour
Copy link
Contributor

An alternative which could be accomplished today with normal attributes and analyzers would be to mark on the method which variables may be enclosed:

[AllowClosure("x", "func")]
public void MyMethod() {
    int x = 123;
    int y = 456;
    Func<int, int> addX = arg => arg + x; // fine, closure encloses "x" which is explicitly allowed
    Func<int, int> addY = arg => arg + y; // error, closure encloses "y" which is not allowed
    Func<int> func = () => x + y; // fine, closure is named "func" which is explicitly allowed to enclose
}

It's certainly more clumsy than a language feature or support for call-site argument attributes, but it would get the job done.

@iam3yal
Copy link
Contributor

iam3yal commented Mar 17, 2017

@HaloFour Yeah I guess this can work as a trade-off for nothing in the language or lack of solution but dunno why shouldn't we get proper solution? rhetorical 😄

@tannergooding
Copy link
Member

This may conflict or overlap with the championed static delegate proposal https://github.com/dotnet/csharplang/blob/master/proposals/static-delegates.md.

It might conflict if we wanted to allow lambdas for static delegates. It might overlap since those will already not capture some information (won't capture this).

@mattwar
Copy link
Contributor Author

mattwar commented Mar 17, 2017

@eyalsk you should always assume that all proposals start tied to a brick and dropped to the bottom of the ocean. The hardest part of getting any feature added to the language is convincing others its a good idea, it has low cost and high benefit for most users, and its high enough on the pile of good ideas to ever get implemented.

@iam3yal
Copy link
Contributor

iam3yal commented Mar 17, 2017

@mattwar Sure, I don't think otherwise, I know my arguments are not convincing so it's all good, I'm with @DavidArno and others on this though, I'd prefer to enhance existing features in the language such as attributes and then introduce it through "Attributes everywhere" than having this feature. :)

@rjamesnw
Copy link

rjamesnw commented Apr 4, 2017

I'm pushing for non-capturing/static 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.

@AlgorithmsAreCool
Copy link

I like this better than capture lists. The vast majority of the time i am am either wanting closures, or not. And if i am not wanting to allocate this is a simple way of enforcing that.

Capture lists are technically superior but at the cost of much more complex syntax that will only be used in very niche scenarios. I think this proposal is a judicious compromise.

@benaadams
Copy link
Member

benaadams commented Oct 22, 2017

If there is a special case where you need to disrupt the syntax to be absolutely sure that you aren't capturing anything, is it so bad to refactor it to a static method?

Because a method group allocates so you have to create a lambda for your static function anyway.

static int Add(int x, int y) => x + y;

static int Use(int x, int y)
{
    SomeMethod(Add);                  // Allocates delegate per call
    SomeMethod((x, y) = > Add(x, y)); // Caches delegate, doesn't allocate
}

So you end up in a weird pattern of having to use either a static function plus a lambda or a static lambda at class level

readonly static Func<int, int, int> Add = (int x, int y) => x + y;

static int Use(int x, int y)
{
    SomeMethod(Add);                  //  Delegate statically allocated once
}

@svick
Copy link
Contributor

svick commented May 21, 2020

@yugabe With static lambdas, I can specify which lambdas should be non-capturing and which are allowed to capture. How would that work with an analyzer?

@yugabe
Copy link

yugabe commented May 21, 2020

@svick if there was an analyzer, there was a rule (or rules) that would report on diagnostics. This analyzer would report on all lambdas in scope which were closing over anything. In my mind, this report would be disabled (or "silent") by default. You would have to opt-in to use this kind of diagnostic, either by flipping a switch in the IDE (I wouldn't prefer this, it would use discrepancy between team members) or using an .editorconfig file. By using an .editorconfig, you can scope where you would like the diagnostic reported as errors (or warnings, suggestions, etc.) in a folder-hierarchy. If you put it in your project root, it will report capturing lambdas as errors. Optimally you can suppress this message locally. Also, you can enhance the rule in any way, maybe by putting an [IgnoreClosure] or [EnforceClosure] attribute on the lambda (which is not yet supported though, AFAIK, but on the roadmap).

If going down this route, you can get compile-time checking in a controlled environment for closures without having to modify the language. The static keyword on an anonymous method/lambda instance is weird if you think about it. If you are not closing over anything, it won't matter whether you are putting or omitting the static keyword, the built assembly will be exactly the same. So it is just a compile-time check to ensure that you won't close over anything.

Making lambdas static would only induce a compile-time check to ensure nothing was closed over. I see it a lot easier to support this via an analyzer instead of a language feature. At least this is my understanding. If I'm wrong about this or I misunderstood something, please feel free to correct me, anyone.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented May 21, 2020

By using an .editorconfig, you can scope where you would like the diagnostic reported as errors (or warnings, suggestions, etc.) in a folder-hierarchy. If you put it in your project root, it will report capturing lambdas as errors. Optimally you can suppress this message locally.

But i don't want this. Because i'm not setting this rule for a scope. I'm choosing it for particular lambdas.

Also, you can enhance the rule in any way, maybe by putting an [IgnoreClosure] or [EnforceClosure] attribute on the lambda.

That's what static is. :)

If going down this route, you can get compile-time checking in a controlled environment for closures without having to modify the language.

You need a way to do attributes on lambdas with your proposal. That's a change to the language.

So it is just a compile-time check to ensure that you won't close over anything.

Yes. And we have precedence for that. For example we literally created static classes primarily for the use case of ensuring people didn't accidentally put instance methods in those classes. This was because the BCL had actually screwed up and done this in V1 and really wanted a simple way to annotate classes as not supporting that at all.

We also have the exactly analogous feature with static local functions, where static exists (and was added) solely for the case of preventing captures. This proposal just expands that out to lambdas, not just local functions. Indeed, that's how it's informally spec'ed at the moment. We would use the same logic and semantics as static-local-functions and apply them to lambdas if it had this modifier.

I see it a lot easier to support this via an analyzer instead of a language feature.

How would you write this as an analyzer today?

@svick
Copy link
Contributor

svick commented May 21, 2020

@yugabe

If you put it in your project root, it will report capturing lambdas as errors.

That would cause a lot of noise for all the lambdas that do and should capture variables.

Optimally you can suppress this message locally.

If you're talking about something like #pragma warning disable 8422 /* code */ #pragma warning restore 8422, then I think that's way too verbose for something like this.

Also, you can enhance the rule in any way, maybe by putting an [IgnoreClosure] or [EnforceClosure] attribute on the lambda (which is not yet supported though, AFAIK, but on the roadmap).

AFAICT, no such thing is on the roadmap. "Permit attributes on local functions" (#1888) is coming, but that's a very different feature. The closest I can think of to this is "Attributes everywhere" (#2478), but that's not being pursued.

The static keyword on an anonymous method/lambda instance is weird if you think about it. If you are not closing over anything, it won't matter whether you are putting or omitting the static keyword, the built assembly will be exactly the same. So it is just a compile-time check to ensure that you won't close over anything.

Making lambdas static would only induce a compile-time check to ensure nothing was closed over. I see it a lot easier to support this via an analyzer instead of a language feature.

The same arguments would apply to static local functions, which are already supported.

@iam3yal
Copy link
Contributor

iam3yal commented May 21, 2020

@yugabe Right and I disagree with what you've said, analyzers doesn't solve all of the things, mainly two things that this feature guarantees and I'm not going to repeat what other people said but if it bothers you I can remove them.

p.s. Extension methods and/or analyzers are not silver bullets, some people think they are.

@yugabe
Copy link

yugabe commented May 21, 2020

@eyalsk no, it's all right, I just wanted to know if there was any specific reason.

P.S.: Same goes for language features, I suppose :)

@svick

If you put it in your project root, it will report capturing lambdas as errors.

That would cause a lot of noise for all the lambdas that do and should capture variables.

You can control .editorconfig in a folder hierarchy. I was on the assumption that some assemblies need this more than others, and in those you would like to enforce not capturing. If that is too broad, you can put it in a folder, and it will only apply to items in that folder.

By suppressing, I was thinking about the two approaches, yes, the #pragma warning disable and explicit attributes. I was looking for the attribute-related issues, but couldn't find them, so thank you for posting. I was under the impression that the #1888 would solve this, but it seems I was wrong.


Generally I am not against the feature as a whole, just that I don't find it clear when someone should use it and when someone shouldn't (application and/or library developers, mainly). I mean, of course, you want to avoid allocations, but that's something the framework and language should try to default to, maybe by not trying to allocate when passing method groups as delegate parameters, but I'm not sure that is possible; or even if it is, I don't know whether it would be a big breaking change or what other aspects of the language/framework it affects.

The other one is the intent of the developer. If the developer intends to capture variables, they do. If they "unintentionally" capture variables, why would they intentionally put the static keyword on the lambda? I don't think it makes much sense.

int y = 10;
someMethod(x => x + y); // captures 'y', causing unintended allocation.

with this proposal you could the static keyword to make this an error.

int y = 10;
someMethod(static x => x + y); // error!

Even using the static keyword has the problem I outlined above, it would not actually be a static method/variable. A related issue on the roslyn repo ([Proposal] Non-Capturing Lambdas with ==> #11620) had the ==> syntax for non-capturing lambdas, which could be better suited than the static keyword in this scenario, I think. But if method groups were not allocating when passed as delegate parameters, it would be easy to avoid allocation by creating a local static method.

@yugabe
Copy link

yugabe commented May 21, 2020

@CyrusNajmabadi sorry, I just saw your reply. I guess you are right, this is quite analogous to static local methods (and as I stated above, I was under the impression that attributes were coming to lambdas too). The only difference is when I declare a local method, there is a code refactoring that suggests I should make it static if I don't capture any local state. Here, the same would be the best, but then I would have a million messages saying I should make lambdas static, which I'd rather avoid.

The other question still unanswered: is there going to be a symmetric static local lambdas for Expressions too? Or this only relates to methods? If it only works on methods, it will be even more confusing to users (many people don't really know the difference between a lambda method and a lambda expression still).

@svick
Copy link
Contributor

svick commented May 21, 2020

@yugabe

I was on the assumption that some assemblies need this more than others, and in those you would like to enforce not capturing. If that is too broad, you can put it in a folder, and it will only apply to items in that folder.

I think that you want this on a per-lambda basis. Anything above that is too broad.


maybe by not trying to allocate when passing method groups as delegate parameters

The issue about that is dotnet/roslyn#5835, though it hasn't seen any activity in quite a while.


Even using the static keyword has the problem I outlined above, it would not actually be a static method/variable.

static is already used this way for static local functions. I don't think having a very different syntax for the same thing on lambdas makes sense.


The only difference is when I declare a local method, there is a code refactoring that suggests I should make it static if I don't capture any local state. Here, the same would be the best, but then I would have a million messages saying I should make lambdas static, which I'd rather avoid.

You can have a refactoring that doesn't produce any messages, so I don't think that's a reason against this feature.


is there going to be a symmetric static local lambdas for Expressions too?

The proposal says:

A static lambda can be used in an expression tree.

I think this means that that answer is yes.

@yugabe
Copy link

yugabe commented May 21, 2020

@svick thanks, that does it for me then, I'm on board.

@jcouv jcouv changed the title Proposal: Use static keyword for lambdas to disallow capturing locals and parameters Proposal: Use static keyword for lambdas to disallow capturing locals and parameters (VS 16.8, .NET 5) Sep 1, 2020
@MadsTorgersen MadsTorgersen modified the milestones: 9.0 candidate, 9.0 Sep 9, 2020
@333fred 333fred added the Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification label Oct 16, 2020
@foxesknow
Copy link

foxesknow commented Nov 25, 2020

I'd rather see the function being called decide that it doesn't want to be given a lambda that has captured anything.

For example, on ConcurrentDictionary we could define AddOrUpdate like this:

public TValue AddOrUpdate<TArg> 
(
    TKey key, 
    static Func<TKey,TArg,TValue> addValueFactory, 
    static Func<TKey,TValue,TArg,TValue> updateValueFactory, 
    TArg factoryArgument
);

Now there's no way the caller can accidentally capture the context and cause a memory allocation. They would need to pass any state they require in the the factoryArgument parameter.

// This will work
int value = 10;
dictionary.AddOrUpdate
(
    "Bob",
    (key, arg) => arg,
    (key, existing, arg) => existing + arg,
    value
);

// This will not work
int value = 10;
dictionary.AddOrUpdate
(
    "Bob",
    (key, arg) => arg,
    (key, existing, arg) => existing + value,  // NOT ALLOWED : value is captured
    value
)

This is particularly useful in high performance code where you may be queuing lambdas up to be processed on work queues. You don't want to have the caller accidentally do an allocation in order to create the lambda and the captured context, but you've got no way of enforcing it. Yes, someone can add static when declaring the lambda, but they can easily work around this by just removing the static

You could also allow the caller to specify static on the lambda, but I think the real power is in allowing the callee to indicate that it does not want to take a lambda that has captures the calling context.

@CyrusNajmabadi
Copy link
Member

You don't want to have the caller accidentally do an allocation in order to create the lambda and the captured context, but you've got no way of enforcing it.

That's not true. This is exactly what analyzers are for. Writing one that checks that a particular api is only passed non allocating lambdas would be trivial.

@foxesknow
Copy link

foxesknow commented Nov 25, 2020

You don't want to have the caller accidentally do an allocation in order to create the lambda and the captured context, but you've got no way of enforcing it.

That's not true. This is exactly what analyzers are for. Writing one that checks that a particular api is only passed non allocating lambdas would be trivial.

Are you seriously telling me that I've got a requirement to not allocate memory then I need to write an analyzer to enforce this? What's to stop someone disabling the analyzer?

Likewise, if the answer is to write an analyzer then why do we need the ability to declare the lambda as static in the first place? Surely you could just write an analyzer to check that the call to the particular api is only passed a non-allocating lambda as (apparently) that would be trivial to do...

NOTE: Writing an analyzer is not trivial!

@333fred
Copy link
Member

333fred commented Nov 25, 2020

Regardless of the merits of a different feature shape, this feature has already been implemented. If you have a new request, please open a new discussion.

@CyrusNajmabadi
Copy link
Member

Are you seriously telling me that I've got a requirement to not allocate memory then I need to write an analyzer to enforce this?

Yes. I'm seriously telling you that.

Likewise, if the answer is to write an analyzer then why do we need the ability to declare the lambda as static in the first place?

Because there is no way to annotate a lambda to say that it shouldn't allocate. It's trivial to annotate a method to do that.

Surely you could just write an analyzer to check that the call to the particular api

Yes. That's what i said. static lambdas addresses hte other end. Where you have an API where you don't care if there is allocation or not, but you don't want a particular callsite to not allocate.

--

So, to sum up:

  1. static lambdas are for specific all site enforcement.
  2. analyzer is for API site enforcement.

@foxesknow
Copy link

foxesknow commented Nov 25, 2020

static lambdas are for specific all site enforcement

No they're not, as you can just remove the static at the site to remove the enforcement! It's nothing more than an indication at the call site which not a requirement.

@foxesknow
Copy link

Regardless of the merits of a different feature shape, this feature has already been implemented. If you have a new request, please open a new discussion.

That's a fair point.

I would open a request, but it seems these days the people in charge of C# are more interested in adding pointless features to the language, such as:

  • Top level programs
  • Expression bodies members
  • using statements in method bodies
  • etc

@333fred
Copy link
Member

333fred commented Nov 25, 2020

I would open a request, but it seems these days the people in charge of C# are more interested in adding pointless features to the language

I mean, you're talking to 2 members of the ldm right now, so we're not going to agree with that assessment. It also does not change the fact that this feature, static lambdas, shipped at the beginning of the month. If you are interested in proposing a new feature, please open a new discussion.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Nov 25, 2020

No they're not, as you can just remove the static at the site to remove the enforcement!

Yes. If you remove the thing enfocing something, it is no longer enforced. I can't help you there. The purpose of those was to be able to be explicit that you want callsite enforcement, and you'd then get that.

It's nothing more than an indication at the call site which not a requirement.

All indications are not requirements if you allow those indications to be removed.

@jnm2
Copy link
Contributor

jnm2 commented Nov 25, 2020

I'd rather see the function being called decide that it doesn't want to be given a lambda that has captured anything.

I wonder if https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers would fit your use case. Beyond that, the language doesn't provide a facility for a method to restrict the set of things that callers can do in the lambda that is passed in the way that you're asking. This would be a separate proposal.

@theunrepentantgeek
Copy link

@foxesknow wrote:

I would open a request, but it seems these days the people in charge of C# are more interested in adding pointless features to the language ._.. elided ..._

If you don't participate, you guarantee that your voice won't be heard. Open the issue, contribute to the discussion. Don't expect everyone to agree with you.

@Luiz-Monad
Copy link

Luiz-Monad commented Jan 2, 2021

@MgSam Too heavyweight for whom? people want to have control over what gets captured and sometimes how it gets captured and this locks the language into a specific behavior which seems too limited.

People can always have this as a syntactic sugar to capture lists but doing this first would be a shame.

I have a bad feeling about capture lists, they won't make it. 😆

As long as we are not getting that horrible C++ syntax for capturing locals with [x](){}, we are fine.
The idea looks like a good idea, but the syntax is horrible.
When I use C++ I end up mostly of the time never capturing anything, as its best for everything to be passed by value on lambdas, it stays more true to the meaning of a lambda in functional programming.
Or when you need to capture something, having to pay attention anyway to what the lambda does, so you go back and put all the variables to be captured in the capture-list, you may as well break the lambda if you have too many variables, or use a struct and pass its entire content as a single var.
So I end up doing either []() {} or [=](){} most of the time, as I also use a lot of auto, I end up don't bothering with specifying each and every single variable, and that's precisely how static will work in C# when you omit it, it will capture all it can capture in a single giant heap object, you either want to capture anything or nothing, specifying every single variable is too heavy.
How many variables do you even have in your scope ? perhaps too much (that's usually a problem with C++ programs, but C# code usually have more small methods with less variables in the scope, but that's anecdotal) .

Also, as someone who extensively uses F# too, I like the idea of static, most lambdas should be pure functions and not capture anything from the scope to the closure. If you want to capture variable, you just don't put static on it.
And there's this thing called curry/uncurry, if you really need to capture some locals, but not others, you do something like this:

people want to have control over what gets captured and sometimes how it gets captured and this locks the language into a specific behavior which seems too limited.

const int y = 10;
someMethod( static x => x + y );  // okay :-)
int z = 9;
someMethod( x => (static (w, a) => w + a + y)(x, z) ); // also okay, and captures the z only, we don't really need capture lists.
//it could even be
someMethod( (const x) => (static w => w + x + y)(z) );  // with this pattern you can precisely say what you want to capture
int k = 8;
someMethod( (const x) => (static (k, z) => k + z + x + y)(k, z) );   //even better, hide outer scope to avoid misuse

If you are already paying the cost of a heap allocation, you may as well pay an extra call. And that's not even needed, as the compiler could (in theory) easily inline (static (w, a) => w + a + y as the Invoke method in the DisplayClass, I'm pretty sure the F# compiler does that extensively for performance reasons.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion
Projects
None yet
Development

No branches or pull requests