对不能用using的成对操作,快速扩展IDisposable的方法

日常操作中有很多需要打开/关闭   加锁/解锁的成对操作

有时候一些操作原生支持  IDisposable
Monitor 可以用Lock(){}   但是ReadWriteLock 就难了。 还有WCF Channel等)。
这种情况就要用  try/catch/finally,很是丑

封装成IDisposable可能很烦,因为多一个对象要多好多文档。

虽然AOP可能解决一些问题, 但是又没办法精确定位  scrope. 
还是 IDisposable +using最爽

 

所以写了一个缺省实现。

    /// <summary>  

    /// 销毁帮手,生成可以支持using的自定义IDisposable实例
    /// <remarks>感谢网友@doggo对于 +=OnDispose功能的測試,由於不完善這裡決定取消該功能</remarks>

    /// </summary>  

    public struct Disposable : IDisposable
    {


   
        /// <summary>  

        /// 创建销毁帮手实例  

        /// </summary>  

        /// <param name="onCreate">创建时要做的操作</param>  

        /// <param name="onDispose">销毁是要做的操作</param>  

        public Disposable(Action onCreate, Action onDispose)
        {
            OnDispose = onDispose;


            onCreate();

        }



        /// <summary>  

        /// 销毁时要做的操作          
       /// </summary>  

        private Action OnDispose
        {

                get ;set;

             }  

        ////// <summary>  

        ////// 销毁时要做的操作  支持+=/Addhandler附加操作 (撤銷)  

        ////// </summary>  

        //////public event Action OnDispose ;


        #region IDisposable 成员



        void IDisposable.Dispose()
        {

        
            OnDispose();

            OnDispose = null;

        }



        #endregion



    } 

 

 

思路是用一个扩展方法,给一个无dispose 能力的对象 建立一个IDisposable的引用。

由于onCreate onDispose是闭包 额外的参数也是非必要的。

 

这里提供一个简易读写锁的实现。  大家可以参考

 

 

    public static class ReaderWriteerLockSlimHelper 
    {
        /// <summary>
        /// 为读写锁创建支持using的IDisposable帮手
        /// </summary>
        /// <param name="instance">读写锁实例</param>
        /// <param name="lockType">加锁类型 读/写</param>
        /// <returns>帮手实例</returns>
        public static IDisposable CreateDisposable(this ReaderWriterLockSlim instance, LockType lockType)
        {
            var kvp = LockDisposeDic[lockType];
            return new Disposable(() => kvp.Key(instance), () => kvp.Value(instance));
        }

        /// <summary>
        /// 读写的不同操作字典
        /// </summary>
        static Dictionary<LockType, KeyValuePair<Action<ReaderWriterLockSlim>, Action<ReaderWriterLockSlim>>> LockDisposeDic = new Dictionary<LockType, KeyValuePair<Action<ReaderWriterLockSlim>, Action<ReaderWriterLockSlim>>>()
        {
            {
                LockType.Read, 
                new KeyValuePair<Action<ReaderWriterLockSlim>,Action<ReaderWriterLockSlim>> 
                    (
                        ins=>ins.EnterReadLock(),
                        ins=>ins.ExitReadLock()
                    ) 
               
            },
            {
                LockType.Write, 
                new KeyValuePair<Action<ReaderWriterLockSlim>,Action<ReaderWriterLockSlim>> 
                    (
                        ins=>ins.EnterWriteLock(),
                        ins=>ins.ExitWriteLock()
                    ) 
               
            }
        };
            
    }

    public enum LockType
    {
        Read,
        Write
    }

 

 

实际使用起来就是爽。 这是一个在需要并发访问的队列中 加入对象的方法。

 

 

/// <summary>
        /// 加入有序队列。
        /// </summary>
        /// <param name="item">加入的项目</param>
        public void Enqueue(TValue item)
        {
            using (_lock.CreateDisposable(LockType.Write))
            {
                Queue<TValue> enqueueTarget;
                var key=_keySelector(item);
                if (!_items.ContainsKey(key))
                {
                    var sortValue = _sortValueSelector(item);
                    if (!_index.TryGetValue( _sortValueSelector(item) ,out enqueueTarget ))
                    {
                        enqueueTarget = new Queue<TValue>();
                        _index.Add(sortValue, enqueueTarget);
                    }

                    enqueueTarget.Enqueue(item);
                    _items.Add(key, item);
                }
                else
                {
                    throw new InvalidOperationException("this Item already in queue");
                }
            }

        }

 

个人工作分享。希望能够帮助大家提高工作效率

 

附: 容易漏调结束行为的实现  和try catch finally 实现  大家比较下

  

 

   出异常会死锁的错误成对掉用 

        /// <summary>
        /// 加入有序队列。
        /// </summary>
        /// <param name="item">加入的项目</param>
        public void Enqueue1(TValue item)
        {
            _lock.EnterWriteLock();

    
                Queue<TValue> enqueueTarget;
                var key = _keySelector(item);
                if (!_items.ContainsKey(key))
                {
                    var sortValue = _sortValueSelector(item);
                    if (!_index.TryGetValue(_sortValueSelector(item), out enqueueTarget))
                    {
                        enqueueTarget = new Queue<TValue>();
                        _index.Add(sortValue, enqueueTarget);
                    }

                    enqueueTarget.Enqueue(item);
                    _items.Add(key, item);
                }
                else
                {
                    throw new InvalidOperationException("this Item already in queue");
                }
                _lock.ExitReadLock(); //不但可能中途退出 还可能像这样写错解锁方法


        }

 

   正确而麻烦的try catch finally

        /// <summary>
        /// 加入有序队列。
        /// </summary>
        /// <param name="item">加入的项目</param>
        public void Enqueue2(TValue item)
        {
            _lock.EnterWriteLock();

            try
            {
                Queue<TValue> enqueueTarget;
                var key = _keySelector(item);
                if (!_items.ContainsKey(key))
                {
                    var sortValue = _sortValueSelector(item);
                    if (!_index.TryGetValue(_sortValueSelector(item), out enqueueTarget))
                    {
                        enqueueTarget = new Queue<TValue>();
                        _index.Add(sortValue, enqueueTarget);
                    }

                    enqueueTarget.Enqueue(item);
                    _items.Add(key, item);
                }
                else
                {
                    throw new InvalidOperationException("this Item already in queue");
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                _lock.ExitWriteLock();
            
            }


        }

标签: 懒惰
posted @ 2010-12-09 11:13 韦恩卑鄙 a-zhewg @waynebaby 阅读(1888) 评论(23) 编辑 收藏

 回复 引用 查看   
#1楼 2010-12-09 11:36 坤坤      
先顶再看,难得看到老兄发首页。哈哈
 回复 引用 查看   
#2楼[楼主] 2010-12-09 11:55 韦恩卑鄙 v-zhewg @waynebaby      
@坤坤
老实说发首页是有阴谋的。
就算被下架 也会被谷歌爬到 作为有帮助的小文章加入搜索引擎的目的也达到了 咩哈哈

 回复 引用 查看   
#3楼 2010-12-09 12:36 顾磊(kyo-yo)      
很不错,很实用。支持个~
 回复 引用 查看   
#4楼 2010-12-09 17:20 Jack Fan      
不能不顶一个啊!
 回复 引用 查看   
#5楼[楼主] 2010-12-09 17:41 韦恩卑鄙 v-zhewg @waynebaby      
@Jack Fan
@顾磊(kyo-yo)
@坤坤
谢谢大家支持~

 回复 引用 查看   
#6楼 2010-12-09 18:16       
我用匿名代理,效果也一样。做事务的时候。

command.Begin(delegate()
{
sdfasdfsafl;sadfjksal;
});

 回复 引用 查看   
#7楼 2010-12-10 09:36 Ivony...      
很实用,但接口为何不写成这样?


  public static class DisposableHelper
  {
    public static IDisposable AsDisposable<T>( this T obj, Action<T> disposeMethod )
    {
      return new DisposableWrapper<T>( obj, disposeMethod );
    }

    private class DisposableWrapper<T> : IDisposable
    {
      private T _obj;
      private Action<T> _disposableMethod;

      public DisposableWrapper( T obj, Action<T> disposeMethod )
      {
        if ( disposeMethod == null )
          throw new ArgumentNullException( "disposeMethod" );

        _disposableMethod = disposeMethod;
        _obj = obj;
      }

      public void Dispose()
      {
        if ( _obj != null )
          _disposableMethod( _obj );
      }
    }
  }

 回复 引用 查看   
#8楼[楼主] 2010-12-10 10:22 韦恩卑鄙 v-zhewg @waynebaby      
@Ivony...
您的接口很美观。
我这边和你的设计不同的地方在于 我需要的成对操作,而你专注在Dispose() 实现上,OnCreate的操作与OnDispose的成对维护并没有额外的照顾。
会产生
_lock.EnterWriteLock();  
using(_lock.AsDisposable((l)=>l.ExitWriteLock()))
{

   //blahblah
}


这样的代码
对EnterLock ExitLock 这样的操作 增加了未封装的部分 多少是有害的

另外关于Action<T> 我是比较反对的,因为Dispose时的操作千奇百怪,T不足以涵盖其上下文状态,不如不加。

我还提供了可以+=扩展 Ondispose的接口,如果用 Action<T>多少有些怪异。


 回复 引用 查看   
#9楼 2010-12-12 10:50 doggo      
非常好文章,也很实用。推荐一个。
有两点想请教一下:
1.用class是否比struct更好一点。struct一定会有一个默认的无参数构造函数,使用struct的话就使用这个无参数的构造函数就可以跳过OnCreate和OnDispose赋值。
2.如果OnDispose接口可以进行+=扩展的话是否应该提供相应的set方法。
public Action OnDispose
{
get { return _onDispose; }
set { _onDispose = value;}
}
否则在使用+=的时候会因为是只读的而报错。

 回复 引用 查看   
#10楼[楼主] 2010-12-12 11:08 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
我并没有对dispose构造函数考虑很多,因为dispose生存期应该是栈内,struct性能略好。不过按你的建议我会做一点修改,让无参构造出来的也能工作。感谢提醒

+=c操作是調用屬性delegate的combine操作,和事件訂閱一樣不需要寫權限

 回复 引用 查看   
#11楼 2010-12-12 11:19 doggo      
@韦恩卑鄙 v-zhewg @waynebaby
我试了一下+=的操作,编译的时候确实是会报错。不知道是不是我的用法有问题。能否给个例子?我尝试的代码是这样的:
objDisposable.OnDispose += () => {System.Console("用+=添加的方法");};
谢谢!

 回复 引用 查看   
#12楼[楼主] 2010-12-12 11:24 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
有這等事?戴我馬上查查問題 嘻嘻

 回复 引用 查看   
#13楼[楼主] 2010-12-12 11:40 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
果然如你所說,readonly不能操作呢! 感謝感謝,幫我找到記憶中一個大錯誤!

我的設計是不允許取消前面定義的ondispose操作的 所以還是不能用get;set;. 所以做了個折衷,把ondispose做成事件。

    /// <summary>  

    /// 销毁帮手,生成可以支持using的自定义IDisposable实例  

    /// </summary>  

    public struct Disposable : IDisposable
    {


   
        /// <summary>  

        /// 创建销毁帮手实例  

        /// </summary>  

        /// <param name="onCreate">创建时要做的操作</param>  

        /// <param name="onDispose">销毁是要做的操作</param>  

        public Disposable(Action onCreate, Action onDispose)
        {
            OnDispose = onDispose;


            onCreate();

        }



        /// <summary>  

        /// 销毁时要做的操作  支持+=/Addhandler附加操作  

        /// </summary>  

        public event Action OnDispose ;


        #region IDisposable 成员



        void IDisposable.Dispose()
        {

        
            OnDispose();

            OnDispose = null;

        }



        #endregion



    } 

 回复 引用 查看   
#14楼[楼主] 2010-12-12 11:47 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
已經更新了代碼 加上了感謝註釋 再次表示感謝嘻嘻

 回复 引用 查看   
#15楼 2010-12-12 18:25 doggo      
关于是使用class还是struct的问题,我仔细考虑了一下,觉得虽然使用struct在性能上会略好一点,但会带来一些潜在的问题。我进行了以下的测试:
事件实现版本:
1.测试代码如下:
Disposable disp = new Disposable(() => { Console.WriteLine("创建时的信息"); }, () => { Console.WriteLine("构造函数中的销毁信息。"); });
using (disp)
{
Console.WriteLine("using方法体中的信息");
disp.OnDispose += () => { Console.WriteLine("using方法体中添加的销毁信息"); };
}
Console.WriteLine("using结束后的信息");
==============================================
得到的输出结果如下:
创建时的信息
using方法体中的信息
构造函数中的销毁信息。
using结束后的信息
==============================================
并非预期中的结果,也就是说在using体中添加的事件代码并没有执行。

2.测试代码如下:
using (Disposable disp = new Disposable(() => { Console.WriteLine("创建时的信息"); }, () => { Console.WriteLine("构造函数中的销毁信息。"); }))
{
Console.WriteLine("using方法体中的信息");
disp.OnDispose += () => { Console.WriteLine("using方法体中添加的销毁信息"); };
}
Console.WriteLine("using结束后的信息");
==============================================
得到的输出结果如下:
创建时的信息
using方法体中的信息
构造函数中的销毁信息。
using方法体中添加的销毁信息
using结束后的信息
==============================================
与希望的结果一致。

事件实现版本:
1.测试代码与事件实现版本1的相同:
得到的输出结果如下:
创建时的信息
using方法体中的信息
构造函数中的销毁信息。
using结束后的信息
==============================================
并非预期中的结果,也就是说在using体中添加的事件代码并没有执行。

2.测试代码与事件实现版本2的相同:
编译时直接报错:“disp”是一个“using 变量”,因此无法修改其成员。

把struct改为class以后都能得到所希望的结果。具体的原因应该和struct是值类型有关。

 回复 引用 查看   
#16楼[楼主] 2010-12-12 23:11 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
這是一個兩難的境地了。如果一定要用 class 我寧可取消掉+=的支持。

 回复 引用 查看   
#17楼[楼主] 2010-12-12 23:49 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
我再次測試了下
問題不是出在 struct本身 而是 c#的using(變量名) 會像調用函數一樣把參數做一個複製。

Disposable disp = new Disposable(() => { Console.WriteLine("创建时的信息"); }, () => { Console.WriteLine("构造函数中的销毁信息。"); });
using (disp)
{
//這裡起作用的已經是disp的一個副本了
Console.WriteLine("using方法体中的信息");

disp.OnDispose += () => { Console.WriteLine("using方法体中添加的销毁信息"); };
//這裡的set操作還是對舊的disp地址進行操作
}


所以沒有辦法正確的加入附加的内容。





using ( var a =disp)
{
//對A進行操作
}

就沒問題。



 回复 引用 查看   
#18楼[楼主] 2010-12-13 00:05 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
這。。。這算語言缺陷了。。。
查了msdn根本沒有這方面的提示。

 回复 引用 查看   
#19楼 2010-12-13 00:45 doggo      
@韦恩卑鄙 v-zhewg @waynebaby
确实如你所说在using语句中存在一个复制的问题,实际上这时候编译器采用的是using(expression)方式来执行的。也是因为考虑到这个问题我才设计了这么一个测试。两个测试方式的代码实际上的差异也就是是否在using语句里面声明变量。这个的确不能说是struct的问题,而是在using语句对于using(expression)方式的处理导致struct和class存在差异。这一点在C#的语言规范上讲了一点,但不是很清晰。
是否算语言缺陷不太好说,但类似的用法是很可能的。一旦用到了对于使用者来说对于这种结果会觉得十分奇怪。有时候类库的设计就是这么让人两难。如何取舍就看个人的出发点了。
你的这个思路我想用在工作中,测试的也就多了一点。呵呵....
多谢你的分享。

 回复 引用 查看   
#20楼[楼主] 2010-12-13 01:03 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
感謝你的測試 讓我也長了見識.

我覺得就算是小缺陷 也是缺陷了
至少要在using這個語句的msdn説明上有所標註。

因爲大家都知道using=try+catch+finally

var disp=new Dispose();
try
{
    disp.OnDisopose +=xxx;
}
catch Exception ex
{
    throw ex;

}
finally
{
    disp.Dispose();
}


這個代碼能執行通過 using沒有理由不能過的。
明天我發個mail給c#組 問問他們怎麽看。。

 回复 引用 查看   
#21楼[楼主] 2010-12-13 01:58 韦恩卑鄙 v-zhewg @waynebaby      
@doggo
找到了 msdn的解釋

http://msdn.microsoft.com/zh-cn/library/aa664736(VS.71).aspx

当 ResourceType 是值类型时,扩展为

{
ResourceType resource = expression;
try {
statement;
}
finally {
((IDisposable)resource).Dispose();
}
}


還真是讓人語塞。 誰做的這個設計 哎

我看還是放棄+=功能算了 這對大家都好


 回复 引用 查看   
#22楼 2011-01-14 13:09 Mingle      
现在离开Cognizant了吧
 回复 引用 查看   
#23楼[楼主] 2011-01-14 17:41 韦恩卑鄙 v-zhewg @waynebaby      
@Mingle
是啊,已在微软做了一年了