Metoda anonimowa w wywołaniu Invoke

132

Mamy trochę problemów ze składnią, w której chcemy anonimowo wywołać delegata w Control.Invoke.

Wypróbowaliśmy wiele różnych podejść, ale bezskutecznie.

Na przykład:

myControl.Invoke(delegate() { MyMethod(this, new MyEventArgs(someParameter)); }); 

gdzie someParameter jest lokalna dla tej metody

Powyższe spowoduje błąd kompilatora:

Nie można przekonwertować metody anonimowej na typ „System.Delegate”, ponieważ nie jest to typ delegata

Duncan
źródło

Odpowiedzi:

222

Ponieważ Invoke/ BeginInvokeaccepts Delegate(zamiast delegata o określonym typie), musisz powiedzieć kompilatorowi, jaki typ delegata ma utworzyć; MethodInvoker(2.0) lub Action(3.5) to typowe wybory (zauważ, że mają ten sam podpis); tak:

control.Invoke((MethodInvoker) delegate {this.Text = "Hi";});

Jeśli chcesz przekazać parametry, sposobem na to są „przechwycone zmienne”:

string message = "Hi";
control.Invoke((MethodInvoker) delegate {this.Text = message;});

(uwaga: musisz być nieco ostrożny, jeśli używasz przechwytywania asynchronicznego , ale synchronizacja jest w porządku - tj. powyższe jest w porządku)

Inną opcją jest napisanie metody rozszerzającej:

public static void Invoke(this Control control, Action action)
{
    control.Invoke((Delegate)action);
}

następnie:

this.Invoke(delegate { this.Text = "hi"; });
// or since we are using C# 3.0
this.Invoke(() => { this.Text = "hi"; });

Możesz oczywiście zrobić to samo z BeginInvoke:

public static void BeginInvoke(this Control control, Action action)
{
    control.BeginInvoke((Delegate)action);
}

Jeśli nie możesz użyć języka C # 3.0, możesz zrobić to samo ze zwykłą metodą wystąpienia, prawdopodobnie w Formklasie bazowej.

Marc Gravell
źródło
Jak mogę przekazać parametry do pierwszego rozwiązania w tej odpowiedzi? Miałem na myśli to rozwiązanie: delegat control.Invoke ((MethodInvoker) {this.Text = "Cześć";});
uzay95
1
Dlaczego metoda rozszerzenia jest wywoływana bez konieczności wykonywania jawnego rzutowania na akcję?
P.Brian.Mackey
Ponieważ kompilator może to wywnioskować na podstawie użycia.
RoboJ1M
1
To to samo, co możliwość zrobienia Form.Load += Loader()zamiast staregoForm.Load += new EventHandler(Loader())
RoboJ1M
49

Właściwie nie musisz używać słowa kluczowego delegate. Po prostu podaj lambdę jako parametr:

control.Invoke((MethodInvoker)(() => {this.Text = "Hi"; }));
Vokinneberg
źródło
16
myControl.Invoke(new MethodInvoker(delegate() {...}))
François
źródło
13

Musisz utworzyć typ pełnomocnika. Słowo kluczowe „delegat” w tworzeniu anonimowych metod jest nieco mylące. Nie tworzysz anonimowego delegata, ale anonimową metodę. Utworzonej metody można użyć w delegacie. Lubię to:

myControl.Invoke(new MethodInvoker(delegate() { (MyMethod(this, new MyEventArgs(someParameter)); }));
Jelon
źródło
8

Ze względu na kompletność można to również osiągnąć za pomocą kombinacji Metoda działania / metoda anonimowa:

//Process is a method, invoked as a method group
Dispatcher.Current.BeginInvoke((Action) Process);
//or use an anonymous method
Dispatcher.Current.BeginInvoke((Action)delegate => {
  SomeFunc();
  SomeOtherFunc();
});
mhamrah
źródło
Invoke((Action) Process);to najlepsza odpowiedź, dziękuję!
Jinjinov
5

Miałem problemy z innymi sugestiami, ponieważ czasami chcę zwrócić wartości z moich metod. Jeśli spróbujesz użyć MethodInvoker ze zwracanymi wartościami, nie wydaje się to podoba. Więc rozwiązanie, którego używam, jest takie (bardzo miło słyszę, jak to zrobić bardziej zwięźle - używam c # .net 2.0):

    // Create delegates for the different return types needed.
    private delegate void VoidDelegate();
    private delegate Boolean ReturnBooleanDelegate();
    private delegate Hashtable ReturnHashtableDelegate();

    // Now use the delegates and the delegate() keyword to create 
    // an anonymous method as required

    // Here a case where there's no value returned:
    public void SetTitle(string title)
    {
        myWindow.Invoke(new VoidDelegate(delegate()
        {
            myWindow.Text = title;
        }));
    }

    // Here's an example of a value being returned
    public Hashtable CurrentlyLoadedDocs()
    {
        return (Hashtable)myWindow.Invoke(new ReturnHashtableDelegate(delegate()
        {
            return myWindow.CurrentlyLoadedDocs;
        }));
    }
Rory
źródło
1

Lubię używać Action zamiast MethodInvoker, jest krótszy i wygląda czysto.

Invoke((Action)(() => {
    DoSomething();
}));

// OR

Invoke((Action)delegate {
    DoSomething();
});

Na przykład.

// Thread-safe update on a form control
public void DisplayResult(string text){
    if (txtResult.InvokeRequired){
        txtResult.Invoke((Action)delegate {
            DisplayResult(text);
        });
        return;
    }

    txtResult.Text += text + "\r\n";
}
Du D.
źródło
0

Nigdy nie rozumiałem, dlaczego ma to znaczenie dla kompilatora, ale to wystarczy.

public static class ControlExtensions
{
    public static void Invoke(this Control control, Action action)
    {
        control.Invoke(action);
    }
}

Bonus: dodaj obsługę błędów, ponieważ jest prawdopodobne, że jeśli używasz Control.Invokez wątku w tle, aktualizujesz stan tekstu / postępu / włączenia kontrolki i nie obchodzi Cię, czy formant jest już usunięty.

public static class ControlExtensions
{
    public static void Invoke(this Control control, Action action)
    {
        try
        {
            if (!control.IsDisposed) control.Invoke(action);
        }
        catch (ObjectDisposedException) { }
    }
}
Jürgen Steinblock
źródło