Pourquoi une expression lambda doit-elle être convertie lorsqu'elle est fournie sous la forme d'un paramètre délégué délégué?

StackOverflow https://stackoverflow.com/questions/411579

  •  03-07-2019
  •  | 
  •  

Question

Prenez la méthode System.Windows.Forms.Control.Invoke (méthode Delegate)

Pourquoi cela donne-t-il une erreur de temps de compilation:

string str = "woop";
Invoke(() => this.Text = str);
// Error: Cannot convert lambda expression to type 'System.Delegate'
// because it is not a delegate type

Pourtant, cela fonctionne bien:

string str = "woop";
Invoke((Action)(() => this.Text = str));

Quand la méthode attend un délégué simple?

Était-ce utile?

La solution

Une expression lambda peut être convertie en un type de délégué ou en un arbre d’expression - mais elle doit connaître le type de délégué . . Savoir que la signature ne suffit pas. Par exemple, supposons que j'ai:

public delegate void Action1();
public delegate void Action2();

...

Delegate x = () => Console.WriteLine("hi");

Que pensez-vous que le type concret de l'objet référencé par x ? Oui, le compilateur pourrait générer un nouveau type de délégué avec une signature appropriée, mais cela est rarement utile et vous vous retrouvez avec moins de possibilités de vérification d'erreur.

Si vous souhaitez faciliter l'appel de Control.Invoke avec une action , la meilleure chose à faire est d'ajouter une méthode d'extension à Control:

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

Autres conseils

Vous en avez marre de lancer des lambdas encore et encore?

public sealed class Lambda<T>
{
    public static Func<T, T> Cast = x => x;
}

public class Example
{
    public void Run()
    {
        // Declare
        var c = Lambda<Func<int, string>>.Cast;
        // Use
        var f1 = c(x => x.ToString());
        var f2 = c(x => "Hello!");
        var f3 = c(x => (x + x).ToString());
    }
}

Neuf fois sur dix, les gens l’obtiennent parce qu’ils essaient de se positionner sur le fil de l’interface utilisateur. Voici la manière paresseuse:

static void UI(Action action) 
{ 
  System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action); 
}

Maintenant que le texte est saisi, le problème disparaît (réponse de qv Skeet) et nous avons cette syntaxe très succincte:

int foo = 5;
public void SomeMethod()
{
  var bar = "a string";
  UI(() =>
  {
    //lifting is marvellous, anything in scope where the lambda
    //expression is defined is available to the asynch code
    someTextBlock.Text = string.Format("{0} = {1}", foo, bar);        
  });
}

Pour les points bonus, voici un autre conseil. Vous ne feriez pas cela pour les éléments d'interface utilisateur, mais dans les cas où vous aurez besoin de SomeMethod pour le bloquer jusqu'à son achèvement (par exemple, une requête / réponse d'E / S, en attente de la réponse), utilisez un WaitHandle (qv msdn WaitAll, WaitAny, WaitOne).

Notez qu'AutoResetEvent est un dérivé de WaitHandle.

public void BlockingMethod()
{
  AutoResetEvent are = new AutoResetEvent(false);
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    are.Set();
  });      
  are.WaitOne(); //don't exit till asynch stuff finishes
}

Et un dernier conseil car les choses peuvent s’embrouiller: WaitHandles bloque le fil. C'est ce qu'ils sont censés faire. Si vous essayez de rassembler le fil de l'interface utilisateur alors que vous l'avez bloqué , votre application sera bloquée. Dans ce cas (a) une refactorisation sérieuse s'impose, et (b) en tant que piratage temporaire, vous pouvez attendre comme ceci:

  bool wait = true;
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    wait = false;
  });
  while (wait) Thread.Sleep(100);

Peter Wone. vous êtes un homme. Prenant votre concept un peu plus loin, je suis venu avec ces deux fonctions.

private void UIA(Action action) {this.Invoke(action);}
private T UIF<T>(Func<T> func) {return (T)this.Invoke(func);}

Je place ces deux fonctions dans mon application Form et je peux passer des appels comme en arrière-plan

int row = 5;
string ip = UIF<string>(() => this.GetIp(row));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));

Peut-être un peu paresseux, mais je n'ai pas à configurer les fonctions effectuées par le travailleur ce qui est très pratique dans des cas comme celui-ci

private void Ping_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
  int count = this.dg.Rows.Count;
  System.Threading.Tasks.Parallel.For(0, count, i => 
  {
    string ip = UIF<string>(() => this.GetIp(i));
    bool r = GoPingIt(ip);
    UIA(() => this.SetPing(i, r));
  });
  UIA(() => SetAllControlsEnabled(true));
}

Essentiellement, obtenez des adresses IP à partir d’une interface graphique DataGridView, envoyez-leur une requête ping, définissez les icônes résultantes sur vert ou rouge et réactivez les boutons du formulaire. Oui, il s'agit d'un "parallèle.pour". dans un backgroundworker. Oui, c’est BEAUCOUP d’invoquer une surcharge, mais c’est négligeable pour les listes courtes et un code beaucoup plus compact.

J'ai essayé de créer cela à partir de la réponse de @Andrey Naumov . Peut-être est-ce une légère amélioration.

public sealed class Lambda<S>
{
    public static Func<S, T> CreateFunc<T>(Func<S, T> func)
    {
        return func;
    }

    public static Expression<Func<S, T>> CreateExpression<T>(Expression<Func<S, T>> expression)
    {
        return expression;
    }

    public Func<S, T> Func<T>(Func<S, T> func)
    {
        return func;
    }

    public Expression<Func<S, T>> Expression<T>(Expression<Func<S, T>> expression)
    {
        return expression;
    }
}

Où le paramètre type S est le paramètre formel (paramètre d'entrée minimum requis pour déduire le reste des types). Maintenant, vous pouvez l'appeler comme suit:

var l = new Lambda<int>();
var d1 = l.Func(x => x.ToString());
var e1 = l.Expression(x => "Hello!");
var d2 = l.Func(x => x + x);

//or if you have only one lambda, consider a static overload
var e2 = Lambda<int>.CreateExpression(x => "Hello!");

Vous pouvez avoir des surcharges supplémentaires pour Action et Expression de la même manière dans la même classe. Pour les autres types de délégués et d'expression intégrés, vous devrez écrire des classes distinctes, telles que Lambda , Lambda < S, T > , Lambda < S, T, U > etc.

L'avantage de ce que je vois par rapport à l'approche initiale:

  1. Une spécification de type de moins (seul le paramètre formel doit être spécifié).

  2. Ce qui vous donne la liberté de l'utiliser contre n'importe quel Func < int, T > , pas seulement lorsque T est dit, chaîne , comme indiqué dans les exemples.

  3. Prend immédiatement en charge les expressions. Dans l'approche précédente, vous devrez spécifier à nouveau des types, tels que:

    var e = Lambda<Expression<Func<int, string>>>.Cast(x => "Hello!");
    
    //or in case 'Cast' is an instance member on non-generic 'Lambda' class:
    var e = lambda.Cast<Expression<Func<int, string>>>(x => "Hello!");
    

    pour les expressions.

  4. L’extension de la classe pour d’autres types de délégués (et d’expression) est également fastidieuse, comme ci-dessus.

    var e = Lambda<Action<int>>.Cast(x => x.ToString());
    
    //or for Expression<Action<T>> if 'Cast' is an instance member on non-generic 'Lambda' class:
    var e = lambda.Cast<Expression<Action<int>>>(x => x.ToString());
    

Dans mon approche, vous ne devez déclarer les types qu’une seule fois (un de moins pour les Func ).

Une autre façon de mettre en œuvre la réponse d'Andrey est comme ne pas aller complètement générique

public sealed class Lambda<T>
{
    public static Func<Func<T, object>, Func<T, object>> Func = x => x;
    public static Func<Expression<Func<T, object>>, Expression<Func<T, object>>> Expression = x => x;
}

Donc, les choses se réduisent à:

var l = Lambda<int>.Expression;
var e1 = l(x => x.ToString());
var e2 = l(x => "Hello!");
var e3 = l(x => x + x);

C’est encore moins de dactylographie, mais vous perdez un certain type de sécurité et , cela n’en vaut pas la peine.

Peu en retard pour la fête, mais vous pouvez aussi lancer comme ça

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top