जब एक सादे प्रतिनिधि पैरामीटर के रूप में आपूर्ति की जाती है तो लैम्ब्डा अभिव्यक्ति क्यों डाली जानी चाहिए


124

विधि ले लो। प्रणाली। Windows.Forms.Control.Invoke (प्रतिनिधि विधि)

यह संकलन समय त्रुटि क्यों देता है:

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

फिर भी यह ठीक काम करता है:

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

जब विधि एक सादे प्रतिनिधि की उम्मीद करती है?

जवाबों:


125

एक लैम्ब्डा अभिव्यक्ति या तो एक प्रतिनिधि प्रकार या एक अभिव्यक्ति पेड़ में परिवर्तित हो सकती है - लेकिन यह जानना होगा कि कौन सा प्रतिनिधि प्रकार। केवल हस्ताक्षर जानना पर्याप्त नहीं है। उदाहरण के लिए, मान लीजिए कि मेरे पास:

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

...

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

आप जिस वस्तु को संदर्भित करते हैं, उसके ठोस प्रकार की आप क्या अपेक्षा करेंगे x? हां, संकलक एक उचित हस्ताक्षर के साथ एक नया प्रतिनिधि प्रकार उत्पन्न कर सकता है , लेकिन यह शायद ही कभी उपयोगी है और आप त्रुटि जाँच के कम अवसर के साथ समाप्त होते हैं।

यदि आप सबसे आसान चीज के Control.Invokeसाथ कॉल करना आसान बनाना चाहते हैं, तो Actionनियंत्रण के लिए एक एक्सटेंशन विधि जोड़ें:

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

1
धन्यवाद - मैंने सवाल को अपडेट किया क्योंकि मुझे लगता है कि अप्रकाशित उपयोग करने के लिए गलत शब्द था।
xyz

1
यह एक बहुत ही सुरुचिपूर्ण और परिपक्व समाधान है। मैं शायद इसे "InvokeAction" कहूंगा ताकि नाम से पता चले कि हम वास्तव में क्या कर रहे हैं (सामान्य प्रतिनिधि के बजाय) लेकिन यह निश्चित रूप से मेरे लिए काम करता है :)
Matthias Hryniszak

7
मैं असहमत हूं कि यह "शायद ही उपयोगी और ..." है। कॉलिंग के मामले में / लैम्ब्डा के साथ शुरू / इनवॉइस आप निश्चित रूप से परवाह नहीं करते हैं यदि प्रतिनिधि प्रकार ऑटो-जनरेट किया गया है, तो हम बस कॉल करना चाहते हैं। किस स्थिति में एक विधि जो एक डेलिगेट (आधार प्रकार) को स्वीकार करती है, वह ध्यान रखेगी कि ठोस प्रकार क्या है? इसके अलावा, विस्तार विधि का उद्देश्य क्या है? यह कुछ भी आसान नहीं है।
15

5
आह! मैंने एक्सटेंशन विधि को जोड़ा और कोशिश की Invoke(()=>DoStuff)और फिर भी त्रुटि हुई। समस्या यह थी कि मैं निहित 'यह' का उपयोग करता था। एक नियंत्रण सदस्य के भीतर से काम करने के लिए आपको स्पष्ट होना होगा this.Invoke(()=>DoStuff):।
Tergiver

2
इसे पढ़ने वाले किसी और के लिए, मुझे लगता है कि प्रश्न और उत्तर C #: InvokeRequired कोड पैटर्न को स्वचालित करना बहुत सहायक है।
एरिक फिलिप्स

34

बार-बार कास्टिंग लैंबडस से थक गए?

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
यह जेनेरिक का एक सुंदर उपयोग है।
पीटर वॉन

2
मुझे मानना ​​पड़ेगा, मुझे यह पता लगाने में थोड़ी देर लगी कि यह क्यों काम किया। प्रतिभाशाली। बहुत बुरा मैं अभी इसके लिए कोई फायदा नहीं है।
विलियम

1
क्या आप इसके उपयोग की व्याख्या कर सकते हैं? यह समझने के लिए मेरे लिए मुश्किल है? बहुत बहुत धन्यवाद।
शाहकलेश

इसका यह कभी पढ़ने के लिए अकेले कहने दो, लेकिन मुझे लगता है कि मैं जॉन स्कीट के इस जवाब को पसंद करता हूं!
पोग्रिंडिस

@ शशाकल्पेश बहुत जटिल नहीं है। इसे इस तरह से देखें, Lambda<T>कक्षा में एक पहचान रूपांतरण पद्धति है Cast, जिसे कहा जाता है जो कुछ भी पारित होता है ( Func<T, T>)। अब Lambda<T>घोषित किया जाता है के रूप में Lambda<Func<int, string>>जो साधन यदि आप एक से पारित Func<int, string>करने के लिए Castविधि है, यह रिटर्न Func<int, string>वापस, के बाद से Tइस मामले में है Func<int, string>
नवफाल

12

समय के नौ दसवें लोग इसे प्राप्त करते हैं क्योंकि वे यूआई थ्रेड पर मार्शल करने की कोशिश कर रहे हैं। यहाँ आलसी तरीका है:

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

अब जब यह टाइप किया जाता है, तो समस्या दूर हो जाती है (qv Skeet's anwer) और हमारे पास यह बहुत ही सरल वाक्य रचना है:

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

बोनस अंक के लिए यहां एक और टिप है। आप UI सामान के लिए ऐसा नहीं करेंगे, लेकिन ऐसे मामलों में जब आपको पूरा होने तक कुछ Method को ब्लॉक करने की आवश्यकता होती है (उदाहरण के लिए अनुरोध / प्रतिक्रिया I / O, प्रतिक्रिया की प्रतीक्षा में) एक WaitHandle (qv msdn WaitAll, WaitAny, WaitOne) का उपयोग करें।

ध्यान दें कि AutoResetEvent एक 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
}

और एक अंतिम टिप क्योंकि चीजें उलझ सकती हैं: WaitHandles धागे को स्टाल करता है। यह वही है जो वे करने वाले हैं। यदि आप इसे रोकते समय UI थ्रेड पर मार्शल करने का प्रयास करते हैं , तो आपका ऐप लटका रहेगा। इस मामले में (ए) कुछ गंभीर रिफैक्टरिंग क्रम में है, और (बी) एक अस्थायी हैक के रूप में आप इस तरह इंतजार कर सकते हैं:

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

3
मुझे यह आकर्षक लगता है कि लोगों के पास केवल एक जवाब देने के लिए गाल होता है क्योंकि वे व्यक्तिगत रूप से इसे आकर्षक नहीं पाते हैं। अगर यह गलत है और आप यह जानते हैं, तो कहें कि इसमें गलत क्या है। यदि आप ऐसा नहीं कर सकते हैं, तो आपके पास चढ़ाव के लिए कोई आधार नहीं है। यदि यह समय-समय पर गलत है, तो "Baloney। देखें [सही प्रतिक्रिया]" या शायद "कोई अनुशंसित समाधान नहीं, देखें [बेहतर सामान]"
पीटर वॉन

1
हां, मैं फ्रेंकहंडस्ट्रेस हूं; लेकिन वैसे भी मुझे नहीं पता कि इसे क्यों वोट दिया गया था; हालाँकि मैंने वास्तविक कोड का उपयोग नहीं किया है, मुझे लगा कि यह UI क्रॉस-थ्रेड इनवोक में एक अच्छा त्वरित परिचय था, और इसमें कुछ चीजें हैं जो मैंने वास्तव में कुदोस के बारे में नहीं सोचा था, निश्चित रूप से ऊपर और परे जाने के लिए +1। :) मेरा मतलब है, आपने प्रतिनिधि आमंत्रित करने के लिए एक अच्छी त्वरित विधि दी; आप कॉल के लिए एक विकल्प देते हैं जिस पर इंतजार किया जाना चाहिए; और आप इसे उन लोगों के लिए एक अच्छा त्वरित तरीका मानते हैं जो यूआई थ्रेड हेल में थोड़े से नियंत्रण को पाने के लिए फंस गए हैं। ठीक उत्तर, मैं कहने जा रहा हूँ + <3 भी। :)
शेलीबटरफ्लाई

System.Windows.Threading.Dispatcher.CurrentDispatcherCURRENT थ्रेड के डिस्पैचर को लौटाएगा - यानी यदि आप किसी थ्रेड से वह विधि कहते हैं जो UI थ्रेड नहीं है, तो UI थ्रेड पर कोड नहीं चलाया जाएगा।
BrainSlugs83

@ BrainSlugs83 अच्छा बिंदु, शायद सबसे अच्छी बात यह है कि ऐप के लिए यूआई थ्रेड डिस्पैचर पर एक संदर्भ को कैप्चर करना और इसे विश्व स्तर पर सुलभ होना चाहिए। मुझे आश्चर्य है कि किसी को यह नोटिस करने में इतना समय लगा!
पीटर

4

पीटर वॉन। तुम दा आदमी हो आपकी अवधारणा को थोड़ा आगे बढ़ाते हुए, मैं इन दोनों कार्यों के साथ आया।

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

मैं इन दो कार्यों को अपने फॉर्म ऐप में रखता हूं, और मैं इस तरह के पृष्ठभूमि के कार्यकर्ताओं से कॉल कर सकता हूं

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

शायद थोड़ा आलसी है, लेकिन मुझे काम करने वाले कर्मचारियों को सेटअप करने की ज़रूरत नहीं है, जो इस तरह के मामलों में सुपर काम में आता है

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

अनिवार्य रूप से, एक gui DataGridView से कुछ आईपी पते प्राप्त करें, उन्हें पिंग करें, परिणामस्वरूप आइकन को हरे या लाल, और रीनेबल बटन को फ़ॉर्म पर सेट करें। हां, यह एक बैकग्राउंडर में "समानांतर" है। हाँ, यह ओवरहेड ओवरहेटिंग का एक बहुत कुछ है, लेकिन इसकी छोटी सूचियों के लिए नगण्य है, और बहुत अधिक कॉम्पैक्ट कोड है।


1

मैंने @Andrey Naumov के उत्तर पर इसे बनाने की कोशिश की । हो सकता है कि यह थोड़ा सुधार हो।

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

जहां प्रकार पैरामीटर Sऔपचारिक पैरामीटर है (इनपुट पैरामीटर, जो बाकी प्रकारों के अनुमान के लिए न्यूनतम आवश्यक है)। अब आप इसे कॉल कर सकते हैं जैसे:

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

आपके पास एक ही वर्ग के लिए Action<S>और Expression<Action<S>>इसी तरह अतिरिक्त अधिभार हो सकते हैं । के लिए अन्य प्रतिनिधि और अभिव्यक्ति प्रकार में बनाया गया है, तो आप की तरह अलग वर्ग लिखने के लिए होगा Lambda, Lambda<S, T>, Lambda<S, T, U>आदि

इसका लाभ मैं मूल दृष्टिकोण पर देखता हूं:

  1. एक कम प्रकार के विनिर्देश (केवल औपचारिक पैरामीटर को निर्दिष्ट करने की आवश्यकता है)।

  2. जो आपको किसी भी के खिलाफ इसका उपयोग करने की स्वतंत्रता देता है Func<int, T>, न कि केवल जब Tकहा जाता है string, जैसा कि उदाहरणों में दिखाया गया है।

  3. सीधे भावों का समर्थन करता है। पहले के दृष्टिकोण में आपको फिर से प्रकार निर्दिष्ट करने होंगे, जैसे:

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

    अभिव्यक्ति के लिए।

  4. अन्य प्रतिनिधि (और अभिव्यक्ति) प्रकारों के लिए कक्षा का विस्तार करना ऊपर की तरह बोझिल है।

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

मेरे दृष्टिकोण में आपको केवल एक बार ही प्रकार की घोषणा करनी होगी (वह भी Funcएस के लिए कम )।


एंड्री के जवाब को लागू करने का एक और तरीका पूरी तरह से सामान्य नहीं है

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

तो चीजें कम हो जाती हैं:

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

यह भी कम टाइपिंग है, लेकिन आप कुछ प्रकार की सुरक्षा खो देते हैं, और imo, यह इसके लायक नहीं है।


1

पार्टी करने में थोड़ा लेट हो गए लेकिन आप इस तरह भी कास्ट कर सकते हैं

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


0

XUnit और धाराप्रवाह दावे के साथ खेलना इस तरह से इनलाइन क्षमता का उपयोग करना संभव था, जो मुझे वास्तव में अच्छा लगता है।

इससे पहले

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

उपरांत

[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.");
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.