For example,
public int DoSomething(in SomeType something){
int local(){
return something.anInt;
}
return local();
}
Why does the compiler issue an error that the something variable cannot be used in the local function?
For example,
public int DoSomething(in SomeType something){
int local(){
return something.anInt;
}
return local();
}
Why does the compiler issue an error that the something variable cannot be used in the local function?
The documentation on local functions states the following
Note that when a local function captures variables in the enclosing scope, the local function is implemented as a delegate type.
And looking at lambdas:
Capture of outer variables and variable scope in lambda expressions
A lambda expression can't directly capture an
in
,ref
, orout
parameter from the enclosing method.
The reason is simple: it's not possible to lift these parameters into a class, due to ref
escaping problems. And that is what would be necessary to do in order to capture it.
public Func<int> DoSomething(in SomeType something){
int local(){
return something.anInt;
}
return local;
}
Suppose this function is called like this:
public Func<int> Mystery()
{
SomeType ghost = new SomeType();
return DoSomething(ghost);
}
public void Scary()
{
var later = Mystery();
Thread.Sleep(5000);
later(); // oops
}
The Mystery
function creates a ghost
and passes it as an in
parameter to DoSomething
, which means that it is passed as a read-only reference to the ghost
variable.
The DoSomething
function captures this reference into the local function local
, and then returns that function as a Func<int>
delegate.
When the Mystery
function returns, the ghost
variable no longer exists. The Scary
function then uses the delegate to call the local
function, and local
will try to read the anInt
property from a nonexistent variable. Oops.
The "You may not capture reference parameters (in
, out
, ref
) in delegates" rule prevents this problem.
You can work around this problem by making a copy of the in
parameter and capturing the copy:
public Func<int> DoSomething2(in SomeType something){
var copy = something;
int local(){
return copy.anInt;
}
return local;
}
Note that the returned delegate operates on the copy
, not on the original ghost
. It means that the delegate will always have a valid copy
to get anInt
from. However, it means that any future changes to ghost
will have no effect on the copy
.
public int Mystery()
{
SomeType ghost = new SomeType() { anInt = 42 };
var later = DoSomething2(ghost);
ghost = new SomeType() { anInt = -1 };
return later(); // returns 42, not -1
}
ref
escaping problems" is a bit hand-wavy for my taste. What exactly is the connection between delegates and lambdas here? Is the restriction on lambdas itself arbitrary, or what underlies that technically? –
Entopic ref
escaping is well documented: you cannot have a ref
field in a class, it's only allowed as a local variable. The first quote I gave shows that local functions become lambdas if there are captured variables –
Babarababassu return local;
means that the compiler must do escape analysis: "Is it possible for local
to be called after this function returns?" Escape analysis is hard. For example, would list.Mystery(e => e.value == local())
be safe? You don't know, because you don't know what Mystery
does. You would have to create more and more complex rules to allow this if Mystery
is Select
or Where
, but not other methods, and when you're done it's not clear that you made things any better. Simple rules are simple to explain and to understand. –
Panacea ref
and in
parameters when no delegate is generated, but it's rather messy to define, and I think it unlikely that they would do so –
Babarababassu ref
and out
behavior... –
Shouldst in
. in
is not intended to provide ref
semantics like the other two modifiers, so capturing it like "regular" (not in
) variable shouldn't be a problem (technically), except if we don't see something. Which would be good to be explained in the docs/specs. –
Shouldst in
is supposed to provide reference semantics to the called function, so that it can read the original location if there were changes to it on another thread. That is not possible to do if it's lifted to a field –
Babarababassu in
or ref
or out
variables from the enclosing scope is already explained, it's simply impossible. –
Babarababassu © 2022 - 2025 — McMap. All rights reserved.