淺談C#中常見的委託(轉)

一提到委託,浮如今咱們腦海中的大概是聽的最多的就是相似C++的函數指針吧,呵呵,至少個人第一個反應是這樣的。數組

關於委託的定義和使用,已經有諸多的人講解過,而且講解細緻入微,尤爲是張子陽的那一篇。我就不用多廢話了。異步

今天我要說的是C#中的三種委託方式:Func委託,Action委託,Predicate委託以及這三種委託的常見使用場景。ide

Func,Action,Predicate全面解析

首先來講明Func委託,經過MSDN咱們能夠了解到,Func委託有以下的5種類型:函數

複製代碼
            (1) *delegate TResult Func<TResult>(); 
(2)*delegate TResult Func<T1,TResult>(T1 arg1);
(3) *delegate TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2);
(4)*delegate TResult Func<T1,T2,T3,TResult>(T1 arg1, T2 arg2, T3 arg3);
(5)*delegate TResult Func<T1,T2,T3,T4,TResult>T1 arg1, T2 arg2, T3 arg3, T4 arg4);
 
 

其中(1)只能委託無參可是有返回值的函數,TResult就是其返回類型。post

而(2)只能委託具備一個傳入參數,有返回值的函數,T1爲一個傳入參數,TResult爲返回類型。this

(3)只能委託具備二個傳入參數,有返回值的函數,T1和T2爲兩個傳入參數,TResult爲返回類型,(4)和(5)以此類推。spa

那麼如何來使用呢? 下面給出一個簡單的幾個例子:線程

          #region Func委託
            
            ///Func<TResult>的用法
            ///這裏TResult表明函數的返回值類型
            ///只能代理返回值爲TResult類型的無參函數
            Func<string> func = delegate()
            {
                return "我是Func<TResult>委託出來的結果";
            };
            Console.WriteLine(func());
            Console.ReadKey();

            ///Func<T,TResult>的用法
            ///這裏的T爲代理的函數的傳入類型,TResult表明函數的返回值類型
            ///只能代理參數爲T類型,返回值爲TResult類型的函數
            Func<string, string> funcOne = delegate(string s)
            {
                return s.ToUpper();
            };
            Console.WriteLine(funcOne("我是Func<T,TResult>委託出來的結果"));
            Console.ReadKey();

            ///Func<T1,T2,TResult>的用法
            ///這裏T1,T2爲代理的函數的傳入類型,TResult表明函數的返回值類型
            ///只能代理參數爲T1,T2類型,返回值爲TResult類型的函數
            Func<string, string, string> funcTwo = delegate(string value1, string value2)
            {
                return value1 + " " + value2;
            };
            Console.WriteLine(funcTwo("我是", "Func<T1,T2,TResult>委託出來的結果"));
            Console.ReadKey();

            #endregion

 

上面代碼中,我用了匿名方法來代替函數,其中delegate()表明無參函數,delegate(string s)表明有一個傳入參數的函數,如下的以此類推。代理

      而後須要說明的就是Action委託,這個委託也是很是經常使用的,尤爲是在涉及到線程和界面交互的時候,配合着lamada表達式使用,很是方便的實現兩者的交互。後面我會提到用法。指針

來看看Action委託的幾種表現形式:

複製代碼
            (1) * delegate void Action(); 無參,無返回值
(2)* delegate void Action<T>(T1 arg1);
(3)* delegate void Action<T1,T2>(T1 arg1, T2 arg2);
(4)* delegate void Action<T1,T2,T3>T1 arg1, T2 arg2, T3 arg3);
(5)* delegate void Action<T1,T2,T3,T4>T1 arg1, T2 arg2, T3 arg3, T4 arg4);
 

從上面能夠看出,總共有5中表現形式,其中(1)既沒有傳入參數,也沒有返回值,那麼它適合代理那些無參,無返回值的函數;(2)有一個傳入參數,無返回值,適合代理有參,無返回值的函數,(3)(4)(5)以此類推。最都容納四個傳入參數。

那麼如何使用呢?下面有一些簡單的例子:

#region Action的用法
            ///Action<T>的用法
            ///這裏的T爲代理函數的傳入類型,無返回值
            Action<string[]> action = delegate(string[] x)
            {
                var result = from p in x
                             where p.Contains("s")
                             select p;
                foreach (string s in result.ToList())
                {
                    Console.WriteLine(s);
                }
            };
            string[] str={ "charlies","nancy","alex","jimmy","selina"};
            action(str);
            Console.ReadKey();

            #endregion

 

上面的例子是經過傳入的String類型的數組,找出其中包含有字符s的項,而後輸出到控制檯。

最後一個就是Predicate委託,這個的形式比較少一些,就是一個傳入參數,返回值爲bool類型,具體示例以下:

#region Action的用法
            ///Action<T>的用法
            ///這裏的T爲代理函數的傳入類型,無返回值
            Action<string[]> action = delegate(string[] x)
            {
                var result = from p in x
                             where p.Contains("s")
                             select p;
                foreach (string s in result.ToList())
                {
                    Console.WriteLine(s);
                }
            };
            string[] str={ "charlies","nancy","alex","jimmy","selina"};
            action(str);
            Console.ReadKey();

            #endregion

 

上面的代碼其實也是判斷String數組中有沒有包含s的項,有的話就在控制檯打印出  They contain.沒有的話就打印出They don't contain.

總結一下這三個的特色就是:

Func能夠接受0個至4個傳入參數,必須具備返回值
Action能夠接受0個至4個傳入參數,無返回值
Predicate只能接受一個傳入參數,返回值爲bool類型

下面附上所有實現代碼:

複製代碼
using System; using System.Collections.Generic; using System.Linq; using System.Text;
namespace DelegateIntegrateConsoleApp { class Program { static void Main(string[] args) { #region Func委託 ///Func<TResult>的用法 ///這裏TResult表明函數的返回值類型 ///只能代理返回值爲TResult類型的無參函數 Func<string> func = delegate() { return "我是Func<TResult>委託出來的結果"; }; Console.WriteLine(func()); Console.ReadKey();
///Func<T,TResult>的用法 ///這裏的T爲代理的函數的傳入類型,TResult表明函數的返回值類型 ///只能代理參數爲T類型,返回值爲TResult類型的函數 Func<string, string> funcOne = delegate(string s) { return s.ToUpper(); }; Console.WriteLine(funcOne("我是Func<T,TResult>委託出來的結果")); Console.ReadKey();
///Func<T1,T2,TResult>的用法 ///這裏T1,T2爲代理的函數的傳入類型,TResult表明函數的返回值類型 ///只能代理參數爲T1,T2類型,返回值爲TResult類型的函數 Func<string, string, string> funcTwo = delegate(string value1, string value2) { return value1 + " " + value2; }; Console.WriteLine(funcTwo("我是", "Func<T1,T2,TResult>委託出來的結果")); Console.ReadKey();
/*************餘下的相似上面的這種操做,最多能夠接受四個傳入參數*************** *delegate TResult Func<TResult>(); *delegate TResult Func<T1,TResult>(T1 arg1); *delegate TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2); *delegate TResult Func<T1,T2,T3,TResult>(T1 arg1, T2 arg2, T3 arg3); *delegate TResult Func<T1,T2,T3,T4,TResult>T1 arg1, T2 arg2, T3 arg3, T4 arg4); */
#endregion
#region Action的用法 ///Action<T>的用法 ///這裏的T爲代理函數的傳入類型,無返回值 Action<string[]> action = delegate(string[] x) { var result = from p in x where p.Contains("s") select p; foreach (string s in result.ToList()) { Console.WriteLine(s); } }; string[] str={ "charlies","nancy","alex","jimmy","selina"}; action(str); Console.ReadKey();
/***************餘下的相似上面的這種操做,最多能夠接受四個傳入參數********** * delegate void Action(); 無參,無返回值 * delegate void Action<T>(T1 arg1); * delegate void Action<T1,T2>(T1 arg1, T2 arg2); * delegate void Action<T1,T2,T3>T1 arg1, T2 arg2, T3 arg3); * delegate void Action<T1,T2,T3,T4>T1 arg1, T2 arg2, T3 arg3, T4 arg4); */
#endregion
#region Predicate ///bool Predicate<T>的用法 ///輸入一個T類型的參數,返回值爲bool類型 Predicate<string[]> predicate = delegate(string[] x) { var result = from p in x where p.Contains("s") select p; if (result.ToList().Count > 0) { return true; } else { return false; } }; string[] _value = { "charlies", "nancy", "alex", "jimmy", "selina" }; if (predicate(_value)) { Console.WriteLine("They contain."); } else { Console.WriteLine("They don't contain."); } Console.ReadKey();
#endregion
} } }
複製代碼

在WinForm和WPF中,利用Func,Action,Predicate進行線程UI交互

下面這部分主要講解如何在WinForm中利用這些委託進行線程和界面的交互。

首先對於Func來講,因爲其必須具備返回值,因此咱們能夠利用以下代碼來實現線程和界面的交互:

#region 利用Func實現線程和界面交互
        private void AlternationUsingFunc(object text)
        {
            //無參數,可是返回值爲bool類型
            this.Invoke(new Func<bool>(delegate()
            {
                button1.Text = text.ToString();
                return true; //返回值
            }));
        }

        private void AlternationUsingFuncThread()
        {
            WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingFunc);
            ThreadPool.QueueUserWorkItem(waitCallBack, "Func的使用");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            AlternationUsingFuncThread();
        }
        #endregion

 

其中

this.Invoke(new Func<bool>(delegate()
            {
                button1.Text = text.ToString();
                return true; //返回值
            }));

 

這段代碼中利用了Func<TResult>這種類型,也就是沒有傳入參數,可是有一個bool類型的返回值,而後將這個函數利用加入到線程池中,最後運行,這裏咱們成功的設置了button1的text爲「Func的使用」。

而後,對於Action來講,因爲其能夠無參,無返回值,那麼它的交互方式最爲簡便,同時也是使用最多的,先看有參的調用方式:

#region 利用Action實現線程和界面交互
        private void AlternationUsingAction(object text)
        {
            //須要一個T類型的參數,無返回值
            this.Invoke(new Action<object>(delegate(object myText)
            {
                myText = text;
                button2.Text = text.ToString();
            }),text);
        }

        private void AlternationUsingActionThread()
        {
            WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingAction);
            ThreadPool.QueueUserWorkItem(waitCallBack,"Action的使用");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            AlternationUsingActionThread();
        }
        #endregion

 

在上面的代碼示例中,咱們使用了帶有一個傳入參數的Action委託,固然了,匿名類型delegate(object myText)匿名代理了具備一個傳入參數的函數。

其實簡單點來講,能夠像以下方式使用:

this.Invoke((Action)(()=>             {                 button2.Text = text.ToString();             }));

這樣就顯得很是的方便。

最後一個固然是Predicate委託,和上面相似,只是寫起來麻煩一些,它須要一個傳入參數,而且返回一個bool類型:

 #region 利用Predicate實現線程和界面的交互
        private void AlternationUsingPrecidate(object text)
        {
            //須要一個T類型的參數,返回bool類型
            this.Invoke(new Predicate<object>(delegate(object myText)  
            {
                myText = text;
                button3.Text = myText.ToString();
                return true;   //返回值
            }),text);
        }

        private void AlternationUsingPrecidateThread()
        {
            WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingPrecidate);
            ThreadPool.QueueUserWorkItem(waitCallBack,"Predicate的使用");
        }

        private void button3_Click(object sender, EventArgs e)
        {
            AlternationUsingPrecidateThread();
        }
        #endregion

 

 

具體的註釋我已經寫在代碼中了,最後運行,能成功的將button3的Text置爲「Predicate的使用.」

下面是所有實現代碼:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace DelegateIntegrateWinFormApp
{
 public partial class mainFrm : Form
 {
 public mainFrm()
 {
 InitializeComponent();
 }

private void mainFrm_Load(object sender, EventArgs e)
 {
 /****************************注意例子中的使用方法****************
 * delegate TResult Func<TResult>(); 無參,可是返回值爲TResult類型
* delegate void Action<T>(T1 arg1); 有一個參數arg1,可是無返回值
* delegate bool Predicate<T>(T arg); 有一個參數arg,返回bool類型
* **************************************************************/
 }

#region 利用Func實現線程和界面交互
private void AlternationUsingFunc(object text)
 {
 //無參數,可是返回值爲bool類型
this.Invoke(new Func<bool>(delegate()
 {
 button1.Text = text.ToString();
 return true; //返回值
}));
 }

private void AlternationUsingFuncThread()
 {
 WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingFunc);
 ThreadPool.QueueUserWorkItem(waitCallBack, "Func的使用");
 }

private void button1_Click(object sender, EventArgs e)
 {
 AlternationUsingFuncThread();
 }
 #endregion

 


 #region 利用Action實現線程和界面交互
private void AlternationUsingAction(object text)
 {
 //須要一個T類型的參數,無返回值
this.Invoke(new Action<object>(delegate(object myText)
 {
 myText = text;
 button2.Text = text.ToString();
 }),text);
 }

private void AlternationUsingActionThread()
 {
 WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingAction);
 ThreadPool.QueueUserWorkItem(waitCallBack,"Action的使用");
 }

private void button2_Click(object sender, EventArgs e)
 {
 AlternationUsingActionThread();
 }
 #endregion

#region 利用Predicate實現線程和界面的交互
private void AlternationUsingPrecidate(object text)
 {
 //須要一個T類型的參數,返回bool類型
this.Invoke(new Predicate<object>(delegate(object myText) 
 {
 myText = text;
 button3.Text = myText.ToString();
 return true; //返回值
}),text);
 }

private void AlternationUsingPrecidateThread()
 {
 WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingPrecidate);
 ThreadPool.QueueUserWorkItem(waitCallBack,"Predicate的使用");
 }

private void button3_Click(object sender, EventArgs e)
 {
 AlternationUsingPrecidateThread();
 }
 #endregion

 


 }
}

 

那麼,如今對於WPF來講,該如何來使用呢?其實在WPF中,和winform中相似,只是在WPF中要實現線程和界面的交互,咱們須要用Dispatcher來實現,也就是形如Control.Dispatcher.Invoke()的方式,因爲與Winform實現方式無多大差異,這裏我就直接附上所有代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;

namespace WpfApplication1
{
 /// <summary>
 /// Interaction logic for Window1.xaml
 /// </summary>
 public partial class Window1 : Window
 {
 public Window1()
 {
 InitializeComponent();
 }

private void Window_Loaded(object sender, RoutedEventArgs e)
 {
 /****************************注意例子中的使用方法****************
 * delegate TResult Func<TResult>(); 無參,可是返回值爲TResult類型
* delegate void Action(); 無參,無返回值
* delegate bool Predicate<T>(T arg); 有一個參數arg,返回bool類型
* 須要注意,與WinForm中不一樣的是,WPF中須要利用Control.Dispatcher.Invoke來實現,其餘相似.
 * **************************************************************/
 }

#region 利用Func實現線程和界面交互
private void AlternationUsingFunc(object text)
 {
 //無參數,可是返回值爲bool類型
button1.Dispatcher.Invoke(new Func<bool>(delegate()
 {
 button1.Content = text.ToString();
 return true; //返回值
}));
 }

private void AlternationUsingFuncThread()
 {
 WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingFunc);
 ThreadPool.QueueUserWorkItem(waitCallBack, "Func的使用");
 }

private void button1_Click(object sender, RoutedEventArgs e)
 {
 AlternationUsingFuncThread();
 }
 #endregion

#region 利用Action實現線程和界面交互
private void AlternationUsingAction(object text)
 {
 //無參數,無返回值
//button2.Dispatcher.Invoke(new Action(delegate()
 //{
 // button2.Content = text.ToString();
 //}));
 //或者
button2.Dispatcher.Invoke((Action)(()=>
 {
 button2.Content = text.ToString();
 }));
 }

private void AlternationUsingActionThread()
 {
 WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingAction);
 ThreadPool.QueueUserWorkItem(waitCallBack, "Action的使用");
 }

private void button2_Click(object sender, RoutedEventArgs e)
 {
 AlternationUsingActionThread();
 }
 #endregion

#region 利用Predicate實現線程和界面的交互
private void AlternationUsingPrecidate(object text)
 {
 //須要一個T類型的參數,返回bool類型
this.button3.Dispatcher.Invoke(new Predicate<object>(delegate(object myText)
 {
 myText = text;
 button3.Content = myText.ToString();
 return true; //返回值
}), text);
 }

private void AlternationUsingPrecidateThread()
 {
 WaitCallback waitCallBack = new WaitCallback(this.AlternationUsingPrecidate);
 ThreadPool.QueueUserWorkItem(waitCallBack, "Predicate的使用");
 }

private void button3_Click(object sender, RoutedEventArgs e)
 {
 AlternationUsingPrecidateThread();
 }
 #endregion


 }
}

 

逐個點擊界面上的按鈕,咱們能夠看到成功實現了線程和UI的交互:

固然,上面咱們只是說到了在WinForm中和WPF中如何來使用的狀況,代碼比較簡單,也沒有具體的應用場景,下面咱們將結合中WPF來模擬一個具體的應用場景:

實現異步和線程同步

如今假設我有一個txt文檔,名稱爲newEXO.txt,裏面大概有5w行記錄,文件大小爲30MB左右;同時我手邊還有一個oldEXO.txt裏面也有5w數據,可是其中有一些記錄和newEXO.txt中的不一樣,我如今須要對比兩個txt文檔,找出不一樣的記錄,並對不一樣的記錄進行上色操做。

那麼如今這裏很明確了,咱們須要兩個函數,一個是讀取記錄的函數ChangeText(),一個是上色的函數ChangeColor()。

可是在實際操做中,發現若是我直接利用wpf讀取數據的話,逐行讀取,耗時10s左右,也就是用戶界面會被阻塞10s,而後纔會顯示給用戶,這個體驗性是至關很差的,因此擬採用異步方式來導入數據。

同時,考慮到差別比較也會比較耗時,因此也準備採用異步方式來進行對比。

那麼問題來了,兩個均採用異步方式進行,不免會發生數據未導入完成就開始進行差別比較的可能,因此這裏還涉及到一個線程同步的問題。

如今,這裏有三個操做,異步的數據導入,異步的差別比較並上色,線程同步。

首先咱們看異步導入,你也能夠本身實現一個異步類,經過委託的BeginInvoke方法和EndInvoke方法來實現

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ThreadSynchorous
{
    public  class AsyncInvoke
    {
        public void BeginAsync(Func<bool> MyFunction)
        {
            Func<bool> func = new Func<bool>(MyFunction);
            IAsyncResult iar = func.BeginInvoke(new AsyncCallback(EndAsync), func);
        }

        public void EndAsync(IAsyncResult iar)
        {
            Func<bool> func = (Func<bool>)iar.AsyncState;
            func.EndInvoke(iar);
        }
    }
}

 

因爲Action委託的使用方式最爲便捷,這裏我採用Action委託方式來進行,固然了,:

private void ChangeText()
        {
            this.button1.Dispatcher.Invoke((Action)(()=>
            {
                string filename = @"C:\newEXO.txt";
                using (StreamReader sr = new StreamReader(filename, Encoding.Default))
                {
                    string result;
                    while ((result = sr.ReadLine()) != null)
                    {
                        //here perform action
                    }
                }
                //label1.Dispatcher.Invoke((new Action(delegate()
                label1.Dispatcher.Invoke((Action)(()=>
                {
                    label1.Content += "Loading finish!(Thread.CurrentThreadName=" + Thread.CurrentThread.ManagedThreadId.ToString() + ") ";
                }));
            }));
        }

 

首先是當點擊button1按鈕的時候,就啓動ChangeText()函數,也即數據導入函數,而後label1會在加載完畢的時候,給出提示信息。

下面再看看ChangeColor()函數:

private void ChangeColor()
        {
            this.button1.Dispatcher.Invoke((Action)(()=>
            {
                this.button1.Background = Brushes.Red;

                //here perform large amount of data action and color the result

                label1.Dispatcher.Invoke((Action)(()=>
                {
                    label1.Content += "Coloring finish!(Thread.CurrentThreadName=" + Thread.CurrentThread.ManagedThreadId + ") ";
                }));

            }));
        }

 

能夠看到也是當button1點擊的時候,會觸發ChangeColor函數。因爲兩者操做比較耗時,爲了防止用戶界面阻塞,咱們放到線程池中:

  ThreadPool.QueueUserWorkItem(o => ChangeText());
ThreadPool.QueueUserWorkItem(o => ChangeColor());

從上面能夠看出,當點擊按鈕button1的時候,兩個函數同時被引起,也就是點擊按鈕的時候進行以下操做:

private void button1_Click(object sender, RoutedEventArgs e)
        {
            ThreadPool.QueueUserWorkItem(o => ChangeText());
            ThreadPool.QueueUserWorkItem(o => ChangeColor());
            label1.Content += " \r\n-------------------------\r\n";
        }

 

看到了什麼?

看到了線程運行的混亂,咱們本想讓數據先加載,而後比較得出差別着色,惋惜上面的結果中卻與想象中的相差甚遠.

這裏的ChangeText()函數和ChangeColor()函數確定不會像想象的那樣順序執行,那麼代碼就有問題了,因此爲了不這個問題,咱們必須進行線程同步,如何來進行呢? 方法不少,這裏我採用EventWaitHandle方式來進行。

EventWaitHandle的Reset方式用來重置信號量,告訴其餘運行的進程,大家須要被阻塞;Set方式用來釋放信號量,告訴其餘運行的進程,大家的阻塞已經被解除,能夠繼續運行了。

可是其餘進行經過什麼來知道本身是否能夠解除阻塞狀態呢? 那就是利用WaitOne方式來判斷:

也就是按照以下的代碼模式來:

EventWaitHandle waitMeHandle = new EventWaitHandle(false,EventResetMode.ManualReset);
private void ChangeText()
{
     waitMeHandle.Reset();  //即將進入下列執行過程,其餘須要阻塞
  //....
     waitMeHandle.Set(); //釋放
}
private void ChangeColor()
{
      waitMeHandle.WaitOne(); //等待,直到接收到Set信號,才能運行
     //  ...
}

 

固然上面我舉出的例子只是一個Sample,我寫過這個軟件,利用的是BeginInvoke和EndInvoke方式實現的異步調用,有興趣能夠參見個人這篇文章中提到的軟件:

下面是軟件截圖:

另外在寫這篇文章的時候,我在StackOverFlow上面有過提問,就是關於當前的Thread的ThreadId爲何一致的問題, 應該說兩個函數放到了ThreadPool中,結果出來的ThreadId應該不同纔對呀.

其實,正確的答案是個人打印出ThreadId的信息都放在了label1.Dispatcher.Invoke這句話中,而這個Lable1屬於界面UI,也就是前臺線程,因此ThreadId會是同樣的,若是你直接在進入函數的時候,輸出ThreadId,你就會發現兩個函數運行在不一樣的線程上了.

本文中涉及到的源碼,能夠從這裏下載

相關文章
相關標籤/搜索