谈谈C#里面的Delegate

来源:互联网 发布:java电商平台源码 编辑:程序博客网 时间:2024/04/28 02:54

Delegate.NET中其实是个非常重要的概念,在.NET中使用到Delegate的地方有很多,比如将函数作为参数传递给其他函数,事件处理,以及异步方法的实现。

从本质上说,我们可以将Delegate理解成一个安全的函数指针或者函数指针数组的的概念。

 

之所以说Delegate是函数的指针,是因为通过创建一个Delegate类型之后,就可以通过这个Delegate来获得函数(方法)签名相同的其他函数的入口地址,然后像函数指针一样可以通过Delegate去调用所指向的函数。

例:

class Program 
    

        
public delegate int FP_Delegate(int x, int y); 

        
static int Max(int a, int b) 
        

            
return a > b ? a : b; 
        }
 

        
static int Min(int a, int b) 
        

            
return a < b ? a : b; 
        }
 

        
static void Main(string[] args) 
        

            FP_Delegate fpoint_max 
= new FP_Delegate(Max); 
            FP_Delegate fpoint_min 
= new FP_Delegate(Min); 

            
int max = fpoint_max(34); 
            
int min = fpoint_min(34); 

            Console.WriteLine(max);     
            Console.WriteLine(min);    
            Console.ReadLine(); 
        }
 
    }
 

之所以说是安全的,因为一个Delegate无法指向与其方法签名不同的函数。我们还用上面的例子,会发现如果指向了方法签名不同的函数在编译的时候会有警告。

public delegate int FP_Delegate(int x, int y); 

static int Abs(int a) 



return System.Math.Abs(a); 

}
 

FP_Delegate fpoint_abs 
= new FP_Delegate(Abs);  //No overload for 'Abs' matches delegate 'Program.FP_Delegate' 

 之所以也有可能是函数指针的数组,是因为Delegate是支持多播(mutlicase)的,即一个delegate的实例可以同时调用多个与之关联的函数。

class Program 
    

        
public delegate void FP_Delegate(int x, int y); 

        
static void Max(int a, int b) 
        

            Console.WriteLine( a 
> b ? a : b); 
        }
 

        
static void Min(int a, int b) 
        

             Console.WriteLine( a 
< b ? a : b); 
        }
 

        
static void Main(string[] args) 
        

            FP_Delegate fpoint_max 
= new FP_Delegate(Max); 
            FP_Delegate fpoint_min 
= new FP_Delegate(Min); 

            FP_Delegate fpoint 
= null
            fpoint 
+= new FP_Delegate(Max); 
            fpoint 
+= new FP_Delegate(Min); 
            
//4,3被先后输入出 
      fpoint(34);                 
            Console.ReadLine(); 
        }
 
    }
 

以上的例子基本说明了什么是.NET中的Delegate。下面的内容将继续讨论Delegate在实际开发中的应用。

应用1:作为方法的参数传递

有时为了增加方法的灵活性,Delegate不失为是一种好的方法,下面的例子可能有些牵强,不过可以充份说明Delegate作为方法的参数时的高度的灵活性。

class Program 

    


        
public delegate int FP_Delegate(int x, int y); 

  

        
static int Max(int a, int b) 

        


            
return a > b ? a : b; 

        }
 

  

        
static int Min(int a, int b) 

        


            
return a < b ? a : b; 

        }
 

  

        
static int MaxOrMin(int a, int b, FP_Delegate fpint) 

        


            
return fpint(a, b); 

        }
 

  

        
static void Main(string[] args) 

        



            FP_Delegate fpoint_max 
= new FP_Delegate(Max); 

            FP_Delegate fpoint_min 
= new FP_Delegate(Min); 

  

            
//根据传出的Delegate对象不同,内部调用了不同的方法 

            
int max = MaxOrMin(34, fpoint_max); 

            
int min = MaxOrMin(34, fpoint_min); 

  

            Console.WriteLine(max);     
//输出4 

            Console.WriteLine(min);     
//输出3 

            Console.ReadLine(); 

  

        }
 

    }
 

.NET中,一个类型的事件是由类型本身定义并公开的,但是作为事件的处理程序则是由用户在客户端使用类型的实例时自己编写的。这样就面临一个问题,用户所编写的事件处理程序最终是由对象内部去调用执行的,可是创建好的对象实例怎么才能知道与他共同存在的众多客户端方法中,哪些方法才是为这个类型定义的事件的处理程序呢。

其实道理非常简单,因为用户编写的事件处理程序最终是要被类型内部的代码调用,所以只要从类型的内部能够得到客户端函数的指针就可以解决了,毫无疑问Delegate是这个问题的最好解决办法。所以说的再直白些,就是类型中的事件其实就是一个个的Delegate对象实例,等待着你在客户端为他们指定可以调用的方法,如果明白了上述的道理那么只要看一下下面的例子,就会明白.NET事件处理的基本机制了。

声明:下面的例子出自《C# Essentials,这是我见过的非常优秀的描述事件的例子。

public delegate void MoveEventHandler(object source, MoveEventArgs e); 

    
public class MoveEventArgs:EventArgs 

    


        
public int newPosition; 

        
public bool cancel; 

        
public MoveEventArgs(int newPosition) 

        


            
this.newPosition = newPosition; 

        }
 

  

    }
 

    
public class Slider 

    


        
int position; 

        
public event MoveEventHandler Move; 

  

        
public int Position 

        


            
get return position; } 

            
set

                
if (position != value) 

                


                    
if (Move != null

                    


                        MoveEventArgs args 
= new MoveEventArgs(value); 

                        Move(
this, args); 

  

                        
if (args.cancel) 

                        


                            
return

                        }
 

                        position 
= value; 

                    }
 

                }
 

            }
 

        }
 

    }
 

static void Main(string[] args) 



            Slider slider 
= new Slider(); 

            slider.Move 
+= new MoveEventHandler(slider_Move); 

            slider.Position 
= 20

            slider.Position 
= 60

}
 

static void slider_Move(object source, MoveEventArgs e) 



      
if (e.newPosition < 50

      


            Console.WriteLine(
"OK"); 

      }
 

      
else 

      


            e.cancel 
= true

            Console.WriteLine(
"Can't go that high!"); 

       }
 

}
 

Delegate默认调用是采用同步调用的方式,可是如果从用户的角度出发,当需要调用一个执行时间比较长的方法,而我们又不希望主程线处于等待的状态,这时可能异步调用方法更适合我们,一般来讲异常调用的方式有下列三种。

l         第一种如果这个方法提供异步调用方法的(BeginXXX, EndXXX)给我们使用是最好的了,但是如果没有提供我们只能考虑后面的两种方法。(关于如何实现异步调用,我们在后面会谈到)

l         第二种就是直接使用多线程的方法,开启另一个新的线程完来调用这个方法。

l         第三种就是使用Delegate本身提供给我们的异步调用方法BeginInvokeEndInvoke

这里我们主要说明如何使用Delegate为我们能供的实现异步调用方法。

BeginInvoke方法用于启动异步调用。它与您需要异步执行的方法具有相同的参数,只不过还有两个额外的参数,在本例中我们还使用不到这两个参数,所以先将这两个参数置为null

BeginInvoke 立即返回,不等待异步调用完成。但是我们可以通过返回值IAsynResult来监测BeginInvoke的执行状况。

EndInvoke 方法用于检索异步调用结果。调用 BeginInvoke 后可随时调用 EndInvoke 方法;如果异步调用未完成,EndInvoke 将一直阻塞到异步调用完成。

下面还是通过一个例子来说明,假如我们要从局域网上访问服务器端的一个文件,但是根据网络的情况不同。取得FileStream的时间也变的很难预测。但是在调用OpenFile方法的时候我们通过BeginInvoke来开启另一个线程,此时主线程会继续向下执行,我们可以利用主线程来做其他的处理,最后通过调用BeginInvoke来取得FileOpen方法返回的FileStream对象。

 

String strFilePath = "/FileServer/Download/100M.txt"

FileOpenDelegate fileOpenDelegate 
= new FileOpenDelegate(File.Open); 

System.IAsyncResult ar 
= fileOpenDelegate.BeginInvoke(strFilePath, System.IO.FileMode.Open, nullnull); 

//其他的处理…… 

FileStream fs 
= fileOpenDelegate.EndInvoke(ar); 

StreamReader sr 
= new StreamReader(fs); 

Console.WriteLine(sr.ReadToEnd()); 

当我们使用EndInvoke来得OpenFile的返回值时,我们将BeginInvoke的返回值-- IAsynResult对象作为参数传入EndInvoke,这个IAsynResult的作用是通过轮询的方式去获得执行BeginInvoke的线程是否已经返回,如果返回的情况下,才允许执行EndInvoke,也就是说如果在我们调用EndInvoke时,BeginInvoke的执行并没有结束的情况下,我们依然是要等待的,看来这种方式并不是最好的方法,我们看一下是否还有可以改进的余地。

 

改进方案1:主动获取BeginInvoke的执行状态

通过BeginInvoke的返回值IAsynResult

通过得到IAsynResult.AsyncWaitHandle方法会得到WaitHandle对象,然后再调用WaitHandla.WaitOne方法来无限期阻塞当前进程,直到WaitOne方法得到来自另一个线程的表示异步操作完成时生成的信号。

 

System.IAsyncResult ar = fileOpenDelegate.BeginInvoke(strFilePath, System.IO.FileMode.Open, nullnull); 

ar.AsyncWaitHandle.WaitOne(); 

FileStream fs 
= fileOpenDelegate.EndInvoke(ar); 

或者采用轮询的方式检查另一个线程是否已经执行完成。

 

System.IAsyncResult ar = fileOpenDelegate.BeginInvoke(strFilePath, System.IO.FileMode.Open, nullnull); 

ar.AsyncWaitHandle.WaitOne(); 

FileStream fs 
= fileOpenDelegate.EndInvoke(ar); 

while (ar.IsCompleted == false



       System.Threading.Thread.Sleep(
10); 

}
 System.AsyncCallback 

以上的两种方案其实最终都会阻塞主线程。所以需要使用回调方法可以解决以上的问题。

改进方案2:通过回调函数来执行EndInvoke

使用回调方法,即调用BeginInvoke时传入一个用于调用回调方法的System.AsynCallBack的委托对象,然后当BeginInvoke执行结果后会自动调用这个委托对象所指定的方法。在回调方法的里面我们可以调用EndInvoke方法,从而将结果得到。

 

public static FileOpenDelegate fileOpenDelegate; 

static void Main(string[] args) 



    String strFilePath 
= "/FileServer/Download/100M.txt"

    fileOpenDelegate 
= new FileOpenDelegate(File.Open);    

    System.IAsyncResult ar 
= fileOpenDelegate.BeginInvoke(strFilePath, System.IO.FileMode.Open, new AsyncCallback(callback), null); 

    Console.ReadLine(); 

}
 

static void callback(System.IAsyncResult ar) 



     FileStream fs 
= fileOpenDelegate.EndInvoke(ar); 

StreamReader sr 
= new StreamReader(fs); 

     Console.WriteLine(sr.ReadToEnd()); 

}
 

.NET Framework Class Library中很多类方法都在提供了同步方法之外又提供了相应的异步调用方法。比如Stocket类在与远程主机连接时,可以时候同步方法Stocket.Connect,也可以使用Stocket.BeginConnectStocket.EndConnect。还有FileStream类在读写文件时除了提供同步方法FileStream.ReadFileStream.Write以外,也提供了相应的异步调用方法FileStream.BeginReadFileStream.BeginWrite以及FileStream.EndReadFileStream.EndWrite方法。

即然这些类型中已经提供了如此方法的异步方法了,我们怎么进行使用呢?其实调用异步方法,和我说前面说明的使用Delegate.BeginInvokeDelegate.EndInvoke的方法完全相同。都是在调用BeginXXX方法时,除了将其对应的同步方法的参数传入后,如果需要执行完成后调用回调方法的话,就为其倒数第二个参数创建一个System.AsnyCallBack的对象实例,并将其传入。而如果您需要在回调方法中使用当前对象(即正在调用异步方法的对象)本身的话,那么请将当前对象作为最后一个参数的实参传入。然后通过回调方法中类形为System.IAsynResult对象的AsyncState属性将传入的对象取得。

我们现在使用一个.NET Framework为我们提供的异步方法。在FileStream读取完成文件之后,再将文件的信息以及内容输入到控制台上面。

 

static Byte[] bytes = null

static void Main(string[] args) 



String strFilePath 
= "C:/500M.txt"

        FileStream fs 
= new FileStream(strFilePath, System.IO.FileMode.Open); 

        bytes 
= new Byte[fs.Length - 1]; 

        System.IAsyncResult ar 
= fs.BeginRead(bytes, 0, (int)fs.Length - 1new AsyncCallback(callback), fs); 

        Console.ReadLine(); 

}
 

static void callback(System.IAsyncResult ar) 



        FileStream fs 
= (FileStream)ar.AsyncState; 

        
int result = fs.EndRead(ar); 

        
if (result > 0

        


            Console.WriteLine(System.Text.Encoding.ASCII.GetString(bytes, 
0, bytes.Length - 1)); 

        }
 

fs.Close(); 

}
 

通过上面对异步方法的使用,我们可以感觉到直接使用异步方法,比起我们对一个同步方法建立Delegate然后在代理上调用BeginInvokeEndInvoke要方便多了,但是我们要怎么在我们自己的类型中去实现异步方法呢,其实作法非常简单,就是在类中预先声明一个指向同步方法的Delegate,然后在声明的BeginXXX方法中调用这个DelegateBeginInvoke方法就可以了,然后将BeginInvoke所需要的参数作为BeginXXX的参数原封不动的传入就可以了。在EndXXX方法中只要调用一下Delegate.EndInvoke就可以了。下面让我们来看一个完整的例子。

public class DBConnection 

    


        
public delegate int ConnectDelegate(int timeout); 

        
private DBConnection.ConnectDelegate cntDelegate = null

        
public DBConnection() 

        


            cntDelegate 
+= new ConnectDelegate(this.Connect); 

        }
 

  

        
public int Connect(int timeout) 

        


            Console.WriteLine(
"Begin Connect"); 

            System.Threading.Thread.Sleep(
new TimeSpan(00, timeout)); 

            Console.WriteLine(
"End Connection"); 

            
return 0

        }
 

  

        
public System.IAsyncResult BeginConnect(int timeOut,  AsyncCallback callback, Object state) 

        


            System.IAsyncResult ar 
= cntDelegate.BeginInvoke(timeOut, callback, state); 

            
return ar; 

        }
 

  

        
public int EndConnect(IAsyncResult asyncResult) 

        


            
int result = (int)cntDelegate.EndInvoke(asyncResult); 

            
return result; 

        }
 

    }