在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# ,