views:

302

answers:

3
+2  Q: 

C# lambda ref out

I'm trying to do this, but it doesn't work. Some suggestions?

int test_i = 0;
DoSomethingThatTakesAgesAndNeedsToUpdateUiWhenFinished(test_i);
test_i <- still is 0 and not 3!!!

public void DoSomethingThatTakesAgesAndNeedsToUpdateUiWhenFinished(int i)
{
    DisableUi();
    m_commandExecutor.ExecuteWithContinuation(
                () =>
                    {
                        // this is the long-running bit
                        ConnectToServer();
                        i = 3; <-------------------------- 
                        // This is the continuation that will be run
                        // on the UI thread
                        return () =>
                                    {
                                        EnableUi();
                                    };
                    });
}

Why I can't set test_i to 3? I also tried ref and out, but it doesn't work.

What can I do to fix it?

EDIT

I've tried this, but ouside of this method dataSet still is empty.

public static void Select(DataGridView dataGridView, ref DataSet dataSet, params object[] parameters)
  {
     var _dataSet = dataSet;
     AsyncCommandExecutor commandExecutor = new AsyncCommandExecutor(System.Threading.SynchronizationContext.Current);
     commandExecutor.ExecuteWithContinuation(
     () =>
     {
        // this is the long-running bit
        _dataSet = getDataFromDb(parameters);

        // This is the continuation that will be run on the UI thread
        return () =>
        {
           dataGridView.DataSource = _dataSet.Tables[0].DefaultView;
        };
     });
     dataSet = _dataSet;
  }
+6  A: 

The i variable in the lambda expression refers to the parameter i of the method. As a workaround, you can make it to refer to a global variable (dirty solution).

By the way, you can't capture ref and out variables in lambdas, but you can have them as parameters. You need to change the signature of your delegate and the implementation of the method receiving the delegate, which might not be suitable:

(out int i) => { i = 10; }
Mehrdad Afshari
+6  A: 

When passing the variable using the ref keyword, you can't use it inside a lambda expression. Try using a local variable inside the lambda and assign the ref variable outside it, if possible (somewhat simplified example):

private static void Main(string[] args)
{
    int i = 0;
    DoSomethingThatTakesAgesAndNeedsToUpdateUiWhenFinished(ref i);
    Console.WriteLine(i);
}


public static void DoSomethingThatTakesAgesAndNeedsToUpdateUiWhenFinished(ref int i)
{
    int temp = i;
    Thread t = new Thread(() =>
    {
        temp = 3; // assign the captured, local variable    
    });
    t.Start();
    t.Join();

    i = temp; // assign the ref parameter
}

Update
In response to the updated answer: your problem is that _dataSet inside the lambda expression is not the same variable as dataSet outside the lambda expression. What you could do is the following:

class DataSetContainer
{
    public DataSet DataSet { get; set; }
}

Now we have a reference type with a property that we can safely modify inside the lambda expression:

public static void Select(DataGridView dataGridView,
                          DataSetContainer dataSetContainer, 
                          params object[] parameters)
{
    AsyncCommandExecutor commandExecutor = new AsyncCommandExecutor(System.Threading.SynchronizationContext.Current);
    commandExecutor.ExecuteWithContinuation(
    () =>
    {
        // this is the long-running bit
        dataSetContainer.DataSet = getDataFromDb(parameters);

        // This is the continuation that will be run on the UI thread
       return () =>
       {
           dataGridView.DataSource = _dataSet.Tables[0].DefaultView;
       };
    });

}

}

In the above code, the lambda expression will update the DataSet property of the DataSetContainer instance that is passed to the Select method. Since you are not modifying the passed argument itself but only a member of that instance there is no need for the ref keyword, and we also get around the closure issue.

Update 2
And now when I switched on my brain, I realize that the Select method makes an asynchronous call. It is quite likely as the code looks that the last line is the Select method will be executed long before _dataSet is being assigned, and as a result it will be null. To get around this you probably want to look into using some sort of signaling mechanism (such as ManualResetEvent or AutoResetEvent) to know when the assignment is done.

Fredrik Mörk
Is it possible to use it like in my example but with a dataSet and not int i?
Jooj
@Jooj: if you pass a reference type, you should be able to modify its member properties/fields inside the lambda expression.
Fredrik Mörk
Please look at my last post.
Jooj
Thank you Fredrik Mörk. Your updated code works great.
Jooj
A: 

I've tried this, but ouside of this method dataSet still is empty.

public static void Select(DataGridView dataGridView, ref DataSet dataSet, params object[] parameters)
  {
     var _dataSet = dataSet;
     AsyncCommandExecutor commandExecutor = new AsyncCommandExecutor(System.Threading.SynchronizationContext.Current);
     commandExecutor.ExecuteWithContinuation(
     () =>
     {
        // this is the long-running bit
        _dataSet = getDataFromDb(parameters);

        // This is the continuation that will be run on the UI thread
        return () =>
        {
           dataGridView.DataSource = _dataSet.Tables[0].DefaultView;
        };
     });
     dataSet = _dataSet;
  }
Jooj
you should add any additional information as edits to your question. I have done just done this for you, just for future reference.
Nathan Koop
ok I will do it next time, thanks!
Jooj
@Jooj, feel free to delete this answer, it only adds to the clutter and may confuse some people
Nathan Koop