Düz bir Delegate parametresi olarak sağlandığında neden bir lambda ifadesi dönüştürülmeli?


124

System.Windows.Forms.Control.Invoke yöntemini alın (Temsilci yöntemi)

Bu neden bir derleme zamanı hatası veriyor:

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

Yine de bu iyi çalışıyor:

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

Yöntem sade bir Delege beklediğinde?

Yanıtlar:


125

Bir lambda ifadesi, bir temsilci türüne veya bir ifade ağacına dönüştürülebilir - ancak hangi temsilci türünü bilmesi gerekir . Sadece imzayı bilmek yeterli değil. Örneğin, sahip olduğumu varsayalım:

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

...

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

Bahsedilen nesnenin somut türünün ne olmasını beklersiniz x? Evet, derleyici olabilir uygun bir imza ile yeni bir temsilci türü oluşturmak, ama bu nadiren yararlıdır ve hata kontrolü için daha az fırsat ile bitirmek.

Eğer çağrıya kolay yapmak istiyorsanız Control.Invokebir ile Actionyapılacak en kolay şey Kontrole bir uzantısı yöntemini eklemektir:

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

1
Teşekkürler - Soruyu güncelledim çünkü bence kullanılmamış olan yanlış terim.
xyz

1
Bu çok zarif ve olgun bir çözüm. Muhtemelen buna "InvokeAction" adını verirdim, böylece ad aslında neyi çağırdığımızı gösteriyor (genel bir temsilci yerine) ama kesinlikle benim için çalışıyor :)
Matthias Hryniszak

7
Bunun "nadiren yararlı ve ..." olduğuna katılmıyorum. Begin / Invoke'u bir lambda ile çağırma durumunda, delege türünün otomatik olarak oluşturulup oluşturulmaması kesinlikle umursamaz, biz sadece çağrıyı yaptırmak istiyoruz. Temsilciyi (temel tür) kabul eden bir yöntem hangi durumda somut türün ne olduğuna dikkat eder? Ayrıca, uzatma yönteminin amacı nedir? Hiçbir şeyi kolaylaştırmaz.
Tergiver

5
Ah! Uzantı yöntemini ekledim ve denedim Invoke(()=>DoStuff)ve hala hatayı alıyorum. Sorun, örtük 'bu' kelimesini kullanmamdı. Eğer açık olmak zorunda Denetim üyesi içinden işe almak için: this.Invoke(()=>DoStuff).
Tergiver

2
Bunu okuyan başka biri için, C # sorusunun ve yanıtlarının : InvokeRequired kod modelini otomatikleştirmenin çok yararlı olduğunu düşünüyorum.
Erik Philips 13

34

Lambdaları defalarca dökmekten bıktınız mı?

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

3
Jenerik ilaçların güzel bir kullanımı.
Peter Wone

2
Kabul etmeliyim, neden işe yaradığını anlamam biraz zaman aldı. Parlak. Ne yazık ki şu anda ona ihtiyacım yok.
William

1
Lütfen bunun kullanımını açıklar mısınız? Bunu anlamak benim için zor mu? Çok teşekkürler.
Shahkalpesh

Bunu söylemek şöyle dursun, okumak için ama sanırım bu cevabı Jon Skeet'e tercih ederim!
Pogrindis

@shahkalpesh çok karmaşık değil. Bu şekilde görün, Lambda<T>sınıfın Castgeçirilen ( Func<T, T>) olanı döndüren bir kimlik dönüştürme yöntemi vardır . Şimdi Lambda<T>olarak ilan edilir Lambda<Func<int, string>>bir geçirirseniz hangi araçlar Func<int, string>için Castyöntemin, döndürür Func<int, string>, çünkü geri TBu durumda içinde Func<int, string>.
nawfal

12

İnsanların onda dokuzu, UI iş parçacığına yerleştirmeye çalıştıkları için bunu alıyor. İşte tembel yol:

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

Artık yazıldığı için sorun ortadan kalkar (qv Skeet'in cevabı) ve bu çok kısa sözdizimimiz var:

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

Bonus puanlar için işte başka bir ipucu. Bunu UI işleri için yapmazsınız, ancak tamamlanıncaya kadar (örneğin istek / yanıt G / Ç, yanıt beklemek) SomeMethod'a ihtiyaç duyduğunuz durumlarda bir WaitHandle (qv msdn WaitAll, WaitAny, WaitOne) kullanın.

AutoResetEvent'in bir WaitHandle türevi olduğunu unutmayın.

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
}

Ve son bir ipucu, çünkü işler karışabilir: WaitHandles ipliği geciktirir. Yapmaları gereken bu. Durdurulmuş haldeyken UI iş parçacığına yerleştirmeye çalışırsanız , uygulamanız askıda kalacaktır. Bu durumda (a) bazı ciddi yeniden düzenleme işlemlerinin yapılması gerekir ve (b) geçici bir hack olarak şu şekilde bekleyebilirsiniz:

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

3
İnsanların sadece kişisel olarak çekici bulmadıkları için bir cevaba oy verme yanağına sahip olmalarını büyüleyici buluyorum. E? Er yanlış ve sen bunu biliyorsun, o zaman yanlış açıklamalar yapıyorlar. Bunu yapamazsanız, olumsuz oy için hiçbir dayanağınız yoktur. Epey yanlışsa, "Baloney. Bkz. [Doğru yanıt]" veya belki "Önerilen bir çözüm değil, [daha iyi şeyler] bakın" gibi bir şey söyleyin
Peter Wone

1
Evet, ben Frankenthreadstress'im; ama yine de neden reddedildiğine dair hiçbir fikrim yok; gerçek kodu kullanmamış olsam da, bunun UI çapraz iş parçacığı çağırmalarına hızlı bir giriş olduğunu düşündüm ve gerçekten bu kadar takdir etmediğim bazı şeyler var, kesinlikle yukarı ve ötesine gitmek için +1. :) Demek istediğim, delege çağrıları yapmak için güzel ve hızlı bir yöntem verdiniz; Beklenmesi gereken aramalar için bir seçenek verirsiniz; ve UI Thread Hell'de sıkışmış birinin biraz kontrol alması için güzel ve hızlı bir yolla takip ediyorsunuz. Güzel cevap, ben de + <3 diyeceğim. :)
shelleybutterfly

System.Windows.Threading.Dispatcher.CurrentDispatcherCURRENT iş parçacığının dağıtıcısını döndürür - yani bu yöntemi UI iş parçacığı olmayan bir iş parçacığından çağırırsanız, kod UI iş parçacığında çalıştırılmayacaktır.
BrainSlugs83

@ BrainSlugs83 iyi bir nokta, muhtemelen bir uygulamanın UI iş parçacığı dağıtıcısına bir referans yakalaması ve onu küresel olarak erişilebilir bir yere koyması en iyisidir. Birinin bunu farketmesinin bu kadar uzun sürmesine şaşırdım!
Peter Wone

4

Peter Wone. sen da erkeksin. Konseptinizi biraz daha ileri götürerek bu iki işlevi buldum.

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

Bu iki işlevi Form uygulamama yerleştiriyorum ve arka plandaki çalışanlardan şu şekilde arama yapabiliyorum

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

Belki biraz tembel, ancak bunun gibi durumlarda çok kullanışlı olan çalışanların yaptığı işlevleri kurmam gerekmiyor

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

Esasen, bir gui DataGridView'dan bazı ip adresleri alın, ping atın, elde edilen simgeleri yeşil veya kırmızı olarak ayarlayın ve formdaki düğmeleri yeniden etkinleştirin. Evet, arka planda çalışan bir "parallel.for" dur. Evet, çok fazla ek yüktür, ancak kısa listeler için ihmal edilebilir ve çok daha kompakt koddur.


1

Bunu @Andrey Naumov'un cevabı üzerine inşa etmeye çalıştım . Bu ufak bir gelişme olabilir.

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

Tür parametresi S, biçimsel parametredir (türlerin geri kalanını çıkarmak için gereken minimum giriş parametresi). Şimdi şöyle diyebilirsiniz:

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

Aynı sınıf için Action<S>ve Expression<Action<S>>benzer şekilde ek aşırı yüklemelere sahip olabilirsiniz . İçin diğer temsilci ve anlatım türlerinin inşa sen gibi ayrı sınıflar yazmak zorunda kalacak Lambda, Lambda<S, T>, Lambda<S, T, U>vb

Bunun avantajı, orijinal yaklaşıma göre:

  1. Bir adet daha az tip belirtimi (yalnızca biçimsel parametrenin belirtilmesi gerekir).

  2. Bu size, örneklerde gösterildiği gibi , Func<int, T>sadece ne zaman Tsöylense değil, herhangi birine karşı kullanma özgürlüğü verir string.

  3. İfadeleri hemen destekler. Önceki yaklaşımda, türleri yeniden belirtmeniz gerekecektir, örneğin:

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

    ifadeler için.

  4. Sınıfı diğer temsilci (ve ifade) türleri için genişletmek, yukarıdaki gibi benzer şekilde hantaldır.

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

Benim yaklaşımıma göre türleri yalnızca bir kez beyan etmelisiniz (bu, Funcs için çok az ).


Andrey'in cevabını uygulamanın bir başka yolu da, tamamen jenerik olmamak gibidir.

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

Yani işler şu şekilde azalır:

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

Bu daha da az yazı yazmak, ancak belirli güvenlik türlerini kaybedersiniz ve imo, buna değmez.


1

Partiye biraz geç ama böyle de oynayabilirsin

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});


0

XUnit ve Fluent Assertions ile oynamak, bu inline özelliğini gerçekten harika bulduğum bir şekilde kullanmak mümkün oldu.

Önce

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    Action action = () => {
        using (var c = DbProviderFactories.GetFactory("MySql.Data.MySqlClient").CreateConnection())
        {
            c.ConnectionString = "<xxx>";
            c.Open();
        }
    };

    action.Should().Throw<Exception>().WithMessage("xxx");
}

Sonra

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    ((Action)(() => {
        using (var c = DbProviderFactories.GetFactory("<provider>").CreateConnection())
        {
            c.ConnectionString = "<connection>";
            c.Open();
        }
    })).Should().Throw<Exception>().WithMessage("Unable to find the requested .Net Framework Data Provider.  It may not be installed.");
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.