Por que uma expressão lambda ser lançado quando fornecido como um parâmetro simples Delegado

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

  •  03-07-2019
  •  | 
  •  

Pergunta

Leve o método System.Windows.Forms.Control.Invoke (método Delegado)

Por que isso dará um erro de tempo de compilação:

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

No entanto, isso funciona bem:

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

Quando o método espera um Delegado simples?

Foi útil?

Solução

Uma expressão lambda pode ser convertido em um tipo de delegado ou de uma árvore de expressão - mas tem de saber que delegado tipo. Basta saber a assinatura não é suficiente. Por exemplo, suponha que eu tenho:

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

...

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

O que você espera que o tipo concreto do objeto referido por x ser? Sim, o compilador pode gerar um novo tipo de delegado com uma assinatura apropriada, mas isso é raramente útil e você acabar com menos oportunidade para a verificação de erros.

Se você quiser torná-lo fácil de Control.Invoke chamada com uma Action a melhor coisa a fazer é adicionar um método de extensão para controle:

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

Outras dicas

Cansado de lançar lambdas mais e mais?

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());
    }
}

décimos Nove das vezes as pessoas ficam isso porque eles estão tentando marechal para o segmento. Aqui é a maneira preguiçosa:

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

Agora que é digitado, o problema desaparece (qv anwer de Skeet) e nós temos essa sintaxe muito sucinta:

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);        
  });
}

Para pontos de bônus aqui é outra dica. Você não faria isso para coisas UI, mas nos casos em que você precisa SomeMethod para bloquear até que ela seja concluída (por exemplo, pedido / resposta I / O, à espera da resposta) use um WaitHandle (qv MSDN WaitAll, WaitAny, WaitOne).

Note que AutoResetEvent é um derivado 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
}

E uma ponta final, porque as coisas podem ficar presos: WaitHandles parar a linha. Isto é o que é suposto fazer. Se você tentar organizar para o segmento interface do usuário enquanto você tê-lo parado , o aplicativo irá travar. Neste caso (a) algum refactoring grave é em ordem, e (b) como um hack temporária você pode esperar como esta:

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

Peter Wone. você é da man. Tomando um pouco mais o seu conceito, eu vim com essas duas funções.

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

Eu coloco essas duas funções em meu aplicativo Form, e eu posso fazer chamadas a partir de trabalhadores de fundo como este

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

Talvez um pouco preguiçoso, mas eu não tenho a funções de trabalho feito de configuração, que vem em super útil em casos como este

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));
}

Essencialmente, obter alguns endereços IP de um gui DataGridView, pingue-los, definir os ícones resultantes de botões verdes ou vermelhas, e reativar no formulário. Sim, é um "Parallel.For" em um backgroundworker. Sim, é um monte de invocar sobrecarga, mas sua insignificante para listas curtas, e muito código mais compacto.

Eu tentei construir esta em cima resposta @Andrey Naumov 's. Pode ser esta é uma ligeira melhoria.

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;
    }
}

Onde tipo de parâmetro S é o parâmetro formal (o parâmetro de entrada, que é mínimo exigido para descansar inferir dos tipos). Agora você pode chamá-lo assim:

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!");

Você pode ter sobrecargas adicionais para Action<S> e Expression<Action<S>> semelhante na mesma classe. Para outros construído em tipos de delegado e de expressão, você terá que escrever classes separadas como Lambda, Lambda<S, T>, Lambda<S, T, U> etc.

A vantagem deste vejo sobre a abordagem original:

  1. Menos uma especificação do tipo (apenas o parâmetro formal precisa ser especificado).

  2. O que lhe dá a liberdade para usá-lo contra qualquer Func<int, T>, não apenas quando T é digamos, string, como mostrado nos exemplos.

  3. suporta expressões imediatamente. Na abordagem mais cedo você terá que especificar os tipos de novo, como:

    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!");
    

    para expressões.

  4. Extensão da classe para a outra delegado (e de expressão) os tipos é semelhante pesado, como descrito acima.

    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());
    

Na minha abordagem você tem que declarar tipos apenas uma vez (que também um menos para Funcs).


Uma outra maneira de implementar a resposta de Andrey é como não vai totalmente genérico

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;
}

Assim que as coisas reduzir a:

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

Isso é ainda menos digitação, mas você perde certa segurança de tipos, e imo, isso não vale a pena.

Um pouco tarde para a festa, mas você também pode lançar como este

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});
 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top