zoukankan      html  css  js  c++  java
  • 从AggregateException看异常类的设计

    异常是程序在有bug时最直观的表现形式,不担心有bug存在,而担心bug埋没在大堆的代码中而发现不了。

    这篇随笔简单谈谈从AggregateException类源码(http://www.projky.com/dotnet/4.5.1/System/AggregateException.cs.html)中窥出的.NET Framework类库设计的方式。

    总结有以下几点:

    1、特性的使用:DebuggerDisplayAttributeSerializableAttribute

    2、只读属性的运用

    3、简单的队列算法

    AggregateException主要用在TPL库中,在等待时发生异常的Task可能不止一个,所以,设计通过InnerExceptions属性访问所有的异常。需要特别注意的是,Exception对象本身有一个InnerException属性,是单数的,少了个s,最多只能记录一个异常。先来看看它的一个典型用法:

    int[] locations = GetCacheLocations();
    var readCacheTasks = new Task<CacheItem>[locations.Length];
    for (int i = 0; i < locations.Length; i++) {
        int location = locations[i];
        readCacheTasks[i] = new Task<CacheItem>(ReadDistributeCache, location);
        readCacheTasks[i].Start();
    }
    
    try {
        Task.WaitAll(readCacheTasks);
        for(int i = 0; i < locations.Length; i++){
            ProcessCacheItem(readCacheTasks[i].Result, i);
        }
    } catch (AggregateException ae) {
        ae.Handle(e => {
            return e is NotFoundException;
        });
        //throw ae.Flatten();
    }

    这段代码中,如果读取分布式缓存的多个任务发生了异常,也能及时确定是否存在一个bug。

    从AggregateException的源码看,只有两个特性声明在该类上,

    [Serializable]
    [DebuggerDisplay("Count = {InnerExceptionCount}")]
    public class AggregateException : Exception {
        private int InnerExceptionCount{
            get{
                return InnerExceptions.Count;
            }
        }
        
        ......
    }

    DebuggerDisplayAttribute特性让我们在下断点调试时,鼠标位置在该类上出现的提示。例如,包含了3个内部异常,那么在断点提示是会是“Count = 3”,这里访问了私有的属性,不一定要私有的,但私有的成员在写代码时不会出现在代码提示中,减少了干扰。

    SerializableAttribute特性让该类支持序列化,比如序列化成xml文件、流、json等格式,再反序列化得到该类对象。仅仅声明该属性是不够的,还添加实现两个成员,一是在序列化过程中,要往结果中存什么,GetObjectData方法,二是支持反序列化的构造函数。

    public override void GetObjectData(SerializationInfo info, StreamingContext context){
        base.GetObjectData(info, context);
        Exception[] innerExceptions = new Exception[m_innerExceptions.Count];
        m_innerExceptions.CopyTo(innerExceptions, 0);
        info.AddValue("InnerExceptions", innerExceptions, typeof(Exception[]));
    }
    
    protected AggregateException(SerializationInfo info, StreamingContext context) : base(info, context){
        Exception[] innerExceptions = info.GetValue("InnerExceptions", typeof(Exception[])) as Exception[];
        m_innerExceptions = new ReadOnlyCollection<Exception>(innerExceptions);
    }

    字符串“InnerExceptions”只是一个名字而已,相当于一个key,不同的属性,key必须不同,同时,还得避免继承可能导致的key重复问题。

    大部分情况下,使用AggregateException都是访问它的InnerExceptions属性,

    private ReadOnlyCollection<Exception> m_innerExceptions; // Complete set of exceptions.
    
    public ReadOnlyCollection<Exception> InnerExceptions{
        get { return m_innerExceptions; }
    }
    
    private AggregateException(string message, IList<Exception> innerExceptions)
                : base(message, innerExceptions != null && innerExceptions.Count > 0 ? innerExceptions[0] : null)
    {
        Exception[] exceptionsCopy = new Exception[innerExceptions.Count];
        for (int i = 0; i < exceptionsCopy.Length; i++){
            exceptionsCopy[i] = innerExceptions[i];
            if (exceptionsCopy[i] == null){
                throw new ArgumentException(Environment.GetResourceString("AggregateException_ctor_InnerExceptionNull"));
            }
        }
        m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy);
    }

    它的InnerExceptions属性是只读的,避免外部访问修改,而且在构造该AggregateException对象实例时,对参数检验和直接的浅拷贝,尽量使整个过程只在返回时修改类的状态,一般情况下,可以认为该过程没有副作用。

    如果你仔细想想,可能在InnerExceptions中,也存在AggregateException对象,所以,专门提供了一个Flatten方法,来提取层级中所有的非AggregateException对象到一个ReadOnlyCollection<Exception>对象中,字面意思理解就是扁平化。

    public AggregateException Flatten()
    {
        // Initialize a collection to contain the flattened exceptions.
        List<Exception> flattenedExceptions = new List<Exception>();
    
        // Create a list to remember all aggregates to be flattened, this will be accessed like a FIFO queue
        List<AggregateException> exceptionsToFlatten = new List<AggregateException>();
        exceptionsToFlatten.Add(this);
        int nDequeueIndex = 0;
    
        // Continue removing and recursively flattening exceptions, until there are no more.
        while (exceptionsToFlatten.Count > nDequeueIndex){
            // dequeue one from exceptionsToFlatten
            IList<Exception> currentInnerExceptions = exceptionsToFlatten[nDequeueIndex++].InnerExceptions;
    
            for (int i = 0; i < currentInnerExceptions.Count; i++){
                Exception currentInnerException = currentInnerExceptions[i];
    
                if (currentInnerException == null){
                    continue;
                }
    
                AggregateException currentInnerAsAggregate = currentInnerException as AggregateException;
    
                // If this exception is an aggregate, keep it around for later.  Otherwise,
                // simply add it to the list of flattened exceptions to be returned.
                if (currentInnerAsAggregate != null){
                    exceptionsToFlatten.Add(currentInnerAsAggregate);
                }
                else{
                    flattenedExceptions.Add(currentInnerException);
                }
            }
        }
    
        return new AggregateException(Message, flattenedExceptions);
    }

    这段代码实现了一个FIFO队列,但并不是传统意义上的队列,没有出队列,只有入队列,通过一个递增索引记录处理到哪一个元素了。仔细琢磨,设计得还是挺不错的,简单实用。

  • 相关阅读:
    Android中NFC编程
    动态的改变程序的主题
    第二章 Libgdx的目标和特性
    第一章 Libgdx简介
    JAVA过滤器和拦截器的区别(个人理解)
    Android下Activity的生命周期
    Ext JS 4.2.1 Beta 1发布了
    【翻译】Ext JS 4.2介绍
    jQuery 1.5发布 Ajax模块重写
    ASP.NET 服务器控件渲染到客户端之后对应的HTML标签
  • 原文地址:https://www.cnblogs.com/ProJKY/p/AggregateExceptionDesign.html
Copyright © 2011-2022 走看看