C# Invoke() delegate with string array as an argument (winforms)
Asked Answered
L

2

6

I am attempting to use this.Invoke() from a separate thread to access controls on my form. I am Invoking a delegate pointing to a method with a string[] as an argument.

A few lines regarding my delegate declaration:

public delegate void delVoidStringArray(string[] s);
public delVoidStringArray _dLoadUserSelect = null;
_dLoadUserSelect = LoadUsers;

Invoking the delegate from a separate thread:

Invoke(_dLoadUserSelect, sUsernames);

And the method called to work with the controls on the form

private void LoadUsers(string[] users)
{
   //Load the list of users into a ListBox
   lstUsers.Items.AddRange(users);

   //Load the state of a CheckBox on the form
   chkUserAlways.Checked = Properties.Settings.Default.PreferDefaultUser;
}

This normally works with the rest of my delegates with various arguments (string, Control, Form, and no arguments), but whenever I call this Invoke() line, I get an error: "Parameter count mismatch."

I think what's happening is that my string array is being boxed into an object array and the delegate is trying to pass these strings as separate arguments to the method. So if the string array had "Bob" "Sally" and "Joe", it is attempting to call LoadUsers as

LoadUsers("Bob", "Sally", "Joe");

which obviously doesn't match the signature.

Does this sound like something that might happen? How could I work around this issue?

Liquidator answered 23/5, 2013 at 23:11 Comment(1)
The params keyword on the 2nd argument of Invoke() makes it ambiguous. It can't tell whether you called it with a single array argument or multiple strings. It assumes multiple strings.Commissariat
R
6

Assuming sUsernames is a string[] then yes, you need to call it with

Invoke(_dLoadUserSelect, new object[] { sUsernames });

.Net arrays are covariant, so this assignment is valid:

string[] sUsernames = new[] { "a", "b", "c" };
object[] objs = sUsernames;

and when calling a method with params arguments, the array is passed directly instead of being passed as the first element in an argument array. You need to manually create the argument array for Invoke to get the behaviour you expect.

Roo answered 23/5, 2013 at 23:17 Comment(0)
I
0

The following change will do the trick (the method needs to reside within the Form class):

internal void LoadUsers(params string[] users)
{
    System.Action act = () =>
    {
        //Load the list of users into a ListBox
        lstUsers.Items.AddRange(users);

        //Load the state of a CheckBox on the form
        chkUserAlways.Checked = Properties.Settings.Default.PreferDefaultUser;
    });
    this.Invoke(act);
}

If being called from outside of the form, the method LoadUsers needs to be at least internal, not private.

Because I have encapsulated it in an Action act, I can now invoke it via this.Invoke(act);. Now you can call LoadUsers safely from within a long running thread or task context, like

private void ShowUsers_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    { // long running task (e.g. database query running 20 seconds)
      Thread.Sleep(20000); // wait 20 seconds
      // populate the user's list
      string[] sUsernames = new[] { "Bob", "Sally", "Joe" };
      LoadUsers(sUsernames);
      // or, passed as params: LoadUsers("Bob", "Sally", "Joe");
    });
}

In this example, immediately running the actions in the click event as a Task prevents the form from freezing and showing "Not responding...", because the event just kicks of the task and exits immediately while the task keeps running separately.


N.B.:

  • The Action act is technically used as a delegate here, but with the Lamba syntax it is much easier to declare (and understand). And it saves you some implementation effort, because you don't need to declare a delegate type first and then use it.
  • Using params is optional, but it simplifies calling LoadUsers if you pass the parameters directly. You can still pass an array if you want.
  • If you only need to update one control, say only lstUsers, you can as well invoke it on the control like lstUsers.Invoke(act);. Here this isn't possible, but it is worth mentioning.
Inclinometer answered 16/10, 2020 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.