I am standing for a "little" syntax problem and cannt figure out how to correctly write what I desire.
I have the following method:
public void DoSomeMagic(string foo, ref string bar)
{
//DoSomeMagic...
}
Now I would like to offload this code inside a Task.Run I would normally write the following:
public async void Button_Click(object sender, EventArgs args)
{
string foo = "Hello Foo";
string bar = "Hello Bar";
await Task.Run(() => DoSomeMagic(foo, ref bar));
}
This doesn't compile telling me: "Cannot use 'ref' or 'out' parameter 'bar' inside an anonymous method body"
So I thought why even do the () => since I am just calling the method and I could reduce it to this:
Task.Run(DoSomeMagic(foo, ref bar));
This again doesn't compile telling me: Cannot resolve method 'Run(void)', candidates are: Run(Action) and Run(Func)
So again no problem Visual Studio your demand is my command.
And changed the code to this:
Task.Run((Action)DoSomeMagic(foo, ref bar));
Again doesn't compile telling me: Cannot cast expression of type 'void' to type 'Action',
Okay this starts getting tricky...
I than tried to instead of returning void I will just try int and cast to Func giving me the error: Cannot cast expression of type 'int' to type 'Func'
Saw that one coming but I thought lets give it a try:
So I tried the following approach:
public Action CallDoSomeMagic(string foo, ref string bar)
{
//DoSomeMagic...
return new Action(() => DoSomeMagic(foo, ref bar));
}
Task.Run(CallDoSomeMagic);
But this again gives me the message "Cannot use 'ref' or 'out' parameter 'bar' inside an anonymous method body"
Since my headache is increasing with every try, I thought you guys can help me out. Is it even possible?
As the message says: you can't do that.
You could take a copy of the parameter value, and capture that, for example:
public Action CallDoSomeMagic(string foo, ref string bar)
{
var snapshot = bar;
return new Action(() => DoSomeMagic(foo, ref snapshot));
}
But note that updates to snapshot
are not visible outside the caller via bar
.
The reasons for this are two-fold:
Firstly, captured variables used on a lambda become fields on a compiler-generated context class. This works fine for non-ref/out parameters, as they already have a copy semantic. So in the case of my snapshot
example, this is actually:
var ctx = new CompilerGeneratedContextClassWithHorribleName();
ctx.foo = foo;
ctx.snapshot = bar;
return new Action(ctx.CompilerGeneratedMethod);
where CompilerGeneratedMethod
is:
DoSomeMagic(foo, ref snapshot);
this isn't possible for refs, as you'd essentially need the field to be a reference to a string-reference, which is... messy.
But more importantly: consider the lifetime of the caller. This could be:
void SomeMethod() {
string s = "abc";
CallDoSomeMagic("def", ref s);
}
Note in particular that the code needs to work even if the delegate is invoked much later - as indeed we might expect it to in your case since it involves Task
and async
. Now: if SomeMethod
has exited: where is that reference to a string-reference pointing? Hint: it is just an arbitrary location on the stack, now out of scope.
Just to give a simpler workaround: instead of passing ref string bar
, consider passing SomeType obj
, where obj.Bar
is the string
you want; i.e.
public Action CallDoSomeMagic(string foo, SomeType obj)
{
return new Action(() => DoSomeMagic(foo, obj));
}
public void DoSomeMagic(string foo, SomeType obj)
{
// read and write obj.Bar here
}
Note you could also move foo
to obj.Foo
if you wanted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With