当前位置:编程学习 > C#/ASP.NET >>

在C#中实现弱委托

 在C#中,使用 Delegate d = Object.Method; 的方式创建一个委托,在实现上,这个委托对象内部持有了源对象的一个强引用(System.Object),如果使用者恰好有特殊需求,比如“要求源对象一旦在其他任何地方都不再使用,应该被及时回收。”,那么,一旦委托对象的生命期足够长,由于委托内部的强引用存在,源对象的销毁将被延迟,与使用者预期不符,可能会导致Bug等问题。

  比如,这里有一个简单的测试对象:

1 class ClassForDeclTest2 {3     public string GetString() { return "call GetString"; }4     public int AddInt(int a, int b) { return a + b; }5     public void PrintString(string s) { Print(s);  }6 }  这个简单对象虽然没有成员数据,但各个方法也都生命为非Static的,因为在这里我要测试的是和对象绑定的委托,即 public static Delegate CreateDelegate(Type type, object target, string method); 方式创建的委托。另外,AddInt方法中用到了我自定义的一个函数Print,功能如其名。

  下面这段代码,演示的是强引用委托,和最直接的弱引用委托方式:

 1 Action<string> a = new ClassForDeclTest().PrintString; 2 a("abc"); 3 GC.Collect(); 4 a("abc"); 5  6 WeakReference weakRef = new WeakReference(new ClassForDeclTest()); 7 a = (s) => { object o = weakRef.Target; if (o != null) Print(s);  }; 8 a("def"); 9 GC.Collect();10 a("def");  输出是:

abcabcdef  "def”只被输出了一次,是因为,第6行用new ClassForDeclTest()创建的对象并没有被专门的变量保存下来,所以第9行的GC.Collect()会将这个对象给回收掉,第10行的委托Invoke将判断WeakReferece.Target为null,于是第10行不输出任何内容。

  我这篇文章的主要目的,就是要将6、7行的弱委托创建过程提取成一个专门的工具模块。

  第一次尝试:

 1 class WeakDelegate 2 { 3     public WeakDelegate(object o, string methodName) : 4         this(o, o.GetType().GetMethod(methodName)) 5     { 6     } 7  8     public WeakDelegate(object o, MethodInfo method) 9     {10         m_target = new WeakReference(o);11         m_method = method;12     }13 14     public object Invoke(params object[] args)15     {16         object target = m_target.Target;17         if (target != null) return m_method.Invoke(target, args);18         else return null;19     }20 21     private WeakReference m_target;22     private MethodInfo m_method;23 }24 25 WeakDelegate d = new WeakDelegate(new ClassForDeclTest(), "PrintString");26 d.Invoke("abc");27 GC.Collect();28 d.Invoke("abc");  这种方法很简单,只要事件发送方管理一个WeakDelegate的容器,就能很方便的使用弱委托。一个明显的缺点是,使用这个WeakDelegate类,对事件发送方是侵入性的,如果发送方是系统类型,不能修改,比如按钮事件 public event EventHandler Click; 要求事件响应者必须是形如 public delegate void EventHandler(Object sender, EventArgs e); 的委托,所以WeakDelegate不能满足需要。

  第二次尝试:    

 1 class WeakDelegate 2 { 3     public WeakDelegate(object o, string methodName): 4         this(o, o.GetType().GetMethod(methodName)) 5     { 6     } 7  8     public WeakDelegate(object o, MethodInfo method) 9     {10         m_target = new WeakReference(o);11         m_method = method;12     }13 14     public Delegate ToDelegate()15     {16         ParameterExpression[] parExps = null;17         {18             ParameterInfo[] parInfos = m_method.GetParameters();19             parExps = new ParameterExpression[parInfos.Length];20             for (int i = 0; i < parExps.Length; ++i)21             {22                 parExps[i] = Expression.Parameter(parInfos[i].ParameterType, "p" + i);23             }24         }25 26         Expression target = Expression.Field(Expression.Constant(this), GetType().GetField("m_target", BindingFlags.Instance | BindingFlags.NonPublic));27         target = Expression.Convert(Expression.Property(target, "Target"), m_method.ReflectedType);28 29         Expression body = 30             Expression.Condition(31             Expression.NotEqual(target, Expression.Constant(null)),32             Expression.Call(target, m_method, parExps),33             GetTypeDefaultExpression(m_method.ReturnType));34 35         return Expression.Lambda(body, parExps).Compile();36     }37 38     private static Expression GetTypeDefaultExpression(Type t)39     {40         if (t == typeof(void)) return Expression.Call(typeof(WeakDelegateHelper).GetMethod("EmptyFunc", BindingFlags.NonPublic | BindingFlags.Static));41         else if (t.IsClass) return Expression.Constant(null, t);42         else return Expression.Constant(t.InvokeMember(null, BindingFlags.CreateInstance, null, null, null));43     }44 45     private WeakReference   m_target;46     private MethodInfo      m_method;47 }  这个类的变化是,移除了Invoke方法,添加ToDelegate,后者的功能是根据WeakReference关联的源对象方法,生成一个Func<>或者Action<>类型的委托。WeakReference对象调用ToDelegate生成的委托,可以被用于各种需要委托的场合,比如上面按钮的Click事件响应。这个类的用法如:(Func<string>)new WeakDelegate(new ClassForDeclTest(), "GetString").ToDelegate(); 虽然也没有用专门的变量来存储WeakDelegate对象,但ToDelegate生成的委托中含有WeakDelegate对象的强引用(Expression.Constant(this)),而WeakDelegate内部又持有源对象的弱引用,故源对象的销毁并不受影响,能够达到目的。

  测试如下:

 1 // 输出一个委托执行时间 2  public static void PerfTimer(Action f, params string[] name) 3 { 4     Assert(name.Length <= 1); 5  6     Stopwatch watch = new Stopwatch(); 7     watch.Start(); 8     f(); 9     watch.Stop();10     float seconds = (watch.ElapsedMilliseconds / 1000.0f);11 12     if (name.Length > 0) Print(name[0], ":", seconds);13     else Print(seconds);14 }15 16 // 性能测试帮助类型17 class ClassForPerfTest18 {19     public int N { get; set; }20     public void Inc() { N += 1; }21 }22 23 ...24 25 {26     Print("-------测试 : Func<string>-------");27 28 &nb

补充:软件开发 , C# ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,