委托和事件

回调(call back)函数是windows编程的一个重要部分,回调函数实际上就是方法调用的指针,也称为函数指针,是一个非常强大的编程特性。在.NET中以委托的形式实现了函数指针的概念,委托是类型安全的。本文主要描述C#中委托和事件的原理和实现。

一、委托

1.1 在C#中使用委托

在C#中,最好将委托看作是对象的一种新类型。使用委托和类一样,需要先定义,然后实例化。定义委托的语法如下:
delegate void VoidPreration(uint x);
这里定义了一个委托VoidPreration,并指定该委托的每个实例都包含一个方法的细节,该方法带有一个uint参数,并返回void。委托的类型安全性非常重要,定义委托时,必须给出它所代表的方法的全部细节。
委托的实例化语法如下:
public void SomeMethod(uint x)
{
  
//.
}

static void Main()
{  
  VoidPreration voidPreration 
= new VoidPreration(SomeMethod);//实例化委托
  voidPreration();//委托实例的调用
}

注意:给定委托的实例时,可以表示任何类型的任何对象上的实例方法或静态方法——只要方法的特征匹配于委托的特征即可。

1.2 委托实例

下面给出两个使用委托的实例
//在这个示例中,定义一个类MathsOperation,它有两个静态方法,对double类型的值执行两个操作,然后使用该委托调用这些方法。
using System;
namespace SimepleDelegate
{
  
//类 MathsOperations 
  public class MathsOperations
  
{
    
public static double MultiplyByTwo(double value)
    
{
      
return value*2;
    }

    
public static double Square(double value)
    
{
      
return value*value;
    }

  }


  
//定义委托
  delegate double DoubleOp(double x);
  
//测试
  public class MainEntryPoint
  
{
    
static void Main()
    
{
      DoubleOp[] operations 
= 
          
{
             
new DoubleOp(MathsOperations.MultiplyByTwo),
             
new DoubleOp(MathsOperations.Square)
          }

      
private void ProcessAndDisplayNumber(DoubleOp action, double value)
      
{
        
double result = action(value);
        ConsoleWriteLine(
"value is {0}, result of operation is {1}",value,result);
      }

      
for(int i = 0; i<operations.Length; i++)
      
{
        Console.WriteLine(
"using operation[{0}]:",i);
        ProcessAndDisplayNumber(operation[i],
2.0);
        Console.WriteLine();
      }

    }

  }

}

结果为:
using operations[0]:
value 
is 2,result of operation is 4

using operation[1];
value 
is 2,result of operation is 4

//BubbleSorter示例
//类BubbleSorter执行一个静态方法Sort(),这个方法的第一个参数时一个对象数组,把该数组按照升序重新排列。
//由于排序的数组可以是任意对象,所以这里需要定义一个委托
delegate bool CompareOp(object lhs, object rhs);
//类BubbleSorter
public class BubbleSorter
{
  
public static void Sort(object[] sortArray, CompareOp gtMethod)
  
{
    
for(int i = 0; i<sortArray.Length; i++)
    
{
      
for(int j = i+1; j<sortArray.Length;j++)
      
{
        
if(gtMethos(object[j],object[i]))
        
{
          
object temp = object[i];
          
object[i] = object[j];
          
object[j] = temp;
        }

      }

    }

  }

}


//定义Employee类,建立要排序的数组
public class Employee
{
  
private string name;
  
private decimal salary;
  
public Employee(string name, decimal salary)
  
{
    
this.name =  name;
    
this.salary = salary;
  }

  
public override string ToString()
  
{
    
return string.Format(name + ", {0,C}",salary);
  }

  
//比较大小的方法
  public static bool RhsIsGreater(object lhs, object rhs)
  
{
    Employee empLhs 
= (Employee)lhs;
    Employee empRhs 
= (Employee)rhs;
    
return (empRhs.salary >empLhs.salary)? true:false;
  }

}


//测试
public class MainEntryPoint
{
  Employee[] employees 
= 
       
{
         
new Employee("wang",2000),
         
new Employee("chen",1000),
         
new Employee("zhu",3000),
         
new Employee("li",2500)
       }
;
  CompareOp employeeCompareOp 
= new CompareOp(Employee.RhsIsGreater);//实例化委托
  BubbleSorter.Sort(employees,employeeCompareOp);//委托调用
  for(int i = 0; i<employees.Length; i++)
  
{
    Console.WriteLine(employees[i].ToString());
  }

}


//运行结果

chen, $
1,000.00
wang, $
2,000.00
li, $
2,500.00
zhu, $
3,000.00

1.3 多播委托

前面介绍的委托都只包含一个方法调用,实际上委托可以包含多个方法,包含多个方法的委托叫做多播委托。如果调用多播委托,就可以按照顺序连续调用多个方法。为此多播委托的返回值必须是void(否则,返回值送到何处?)实际上,如果编译器发现某个委托返回void,就会自动假定这是一个多播委托。
delegate void DoubleOp(double value);
class MainEntryPoint
{
  
static void Main()
  
{
    DoubleOp operations 
= new DoubleOp(MathOperations.MultiplyByTwo);
    operation 
+= new DoubleOp(MathOperations.Square);
    
  }

}

//上面的多播委托等价于下面的代码
DoubleOp operation1 = new DoubleOp(MathOperations.MultiplyByTwo);
DoubleOp operation2 
= new DoubleOp(MathOperations.Square);
DoubleOp operations 
= operation1 + operation2;
注意:如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,因此应避免编写依赖于任意特定顺序调用方法的代码。

二、事件

在开发基于对象的应用程序时,对象之间需要通信,例如在一个对象中发生了什么有趣的事情时,需要通知其它对象发生了什么变化,这里就需要用到事件。可以把事件作为对象之间通信的介质。而委托就用作应用程序接收到消息时封装事件的方式。
下面通过一个例子来说明C#中事件的创建、引发、接收和取消事件。

//这个例子包含一个窗体,它会引发另一个类正在监听的事件。在引发事件后,接收对象就确定是否执行一个过程,如果该过程未能继续,就取消事件。
//用于生成事件的窗口包含一个按钮和一个标签
namespace EventTest
{
    
public  partial class Form1 : Form
    
{
        
public delegate void ActionEventHandler(object sender, ActionCancelEventArgs ev);
        
public static event ActionEventHandler Action;
        
public BusEntity busEntity = new BusEntity();
        
public Form1()
        
{
            InitializeComponent();            
            
        }


        
private void OnAction(object sender, ActionCancelEventArgs ev)
        
{
            
if (Action != null)
            
{
                Action(sender, ev);
            }

        }


        
private void button1_Click(object sender, EventArgs e)
        
{
            ActionCancelEventArgs cancelEvent 
= new ActionCancelEventArgs();
            OnAction(
this, cancelEvent);
            
if (cancelEvent.Cancel)
            
{
                
this.label1.Text = cancelEvent.Message;
            }

            
else
            
{
                
this.label1.Text = this.busEntity.TimeString;
            }

        }


    }

    
public class ActionCancelEventArgs : System.ComponentModel.CancelEventArgs
    
{
        
string msg = "";
        
public ActionCancelEventArgs() :base(){}
        
public ActionCancelEventArgs(bool cancel) : base(cancel) { }
        
public ActionCancelEventArgs(bool cancel, string message)
            : 
base(cancel)
        
{
            
this.msg = message;
        }


        
public string Message
        
{
            
get return this.msg;}
            
set {this.msg = value;}
        }

        
    }


    
//事件接收器
    public class BusEntity
    
{
        
string time = "";
        
public BusEntity()
        
{
            Form1.Action 
+= new Form1.ActionEventHandler(Form1_Action);
        }


        
private void Form1_Action(object sender, ActionCancelEventArgs ev)
        
{
            ev.Cancel 
= !DoAction();
            
if (ev.Cancel)
            
{
                ev.Message 
= "Wasn't the right time";
            }

        }

        
private bool DoAction()
        
{
            
bool retVal = false;
            DateTime tm 
= DateTime.Now;
            
if (tm.Second < 30)
            
{
                
this.time = "The time is " + DateTime.Now.ToLongTimeString();
                retVal 
= true;
            }

            
else
            
{
                
this.time = "";                
            }

            
return retVal;
        }

        
public string TimeString
        
{
            
get return this.time; }
        }

    }


}

posted @ 2007-02-01 17:11  C#开源即时通讯GGTalk  阅读(1527)  评论(1编辑  收藏  举报