65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 3.0 中的“穷人代码注入器”(异常处理范围、策略、*.* 注入)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (15投票s)

2007年12月18日

CPOL

7分钟阅读

viewsIcon

50471

downloadIcon

244

一种在 C# 3.0 中实现代码注入的简单方法

引言

最近我一直在编写一些 WCF 服务,现在已经到了需要统一异常处理的地步。当然,我首先想到了 Enterprise Library。它的组成部分是 Exception Handling Application Block,这是一个非常好的解决方案;然而,我同样不喜欢它,原因与我不喜欢 Validation Application Block 一样(太复杂,难以使用)。或者,还有 Policy Injection Application Block。这也是一个不错的选择,但你需要付出很多努力才能取得任何成果。

如果我们仔细考虑一下,这些库只不过是代码,这些代码有一个范围(有效性范围),并且在此之前和之后,我们希望将代码注入到相同的有效性范围内。

EHAB 在我们的代码之后根据特定规则执行统一的异常代码,这样我们就不必在每个方法中编写处理程序。这些可以放在一个地方,EHAB 会处理其余的事情。

PIAB 将在执行我们的代码之前和之后执行某些处理程序。例如,之前进行验证和授权,之后进行异常处理和日志记录。

为了完成这项任务,面向方面编程也得到了发展。这方面也有各种各样的库,其中许多是开源的。

这些都是针对同一个问题的复杂解决方案。根据奥卡姆剃刀原则,最简单的解决方案才是正确的;这就是为什么我想象如何以最简单的方式解决这个问题,目前以及将来都用 C# 3.0。

想法

C# 中有一个非常有用的构造;很少有人知道它在程序员手中有多么有用。这就是匿名方法。从 C# 3.0 开始,这个机会的效率有所提高,并且也可以使用 lambda 语法进行输入。例如:

Func< int, bool > isPositive = v => v > 0;

这是一个 lambda 表达式,编译器将生成匿名方法,其类型为 delegate。但是也有机会给出代码块

Func< int, string > listOf = v =>
{
  string result = null;
  for (int i = 0; i < v; i++)
  {
    result = i == 0 ? i.ToString() : string.Format("{0}, {1}", result, i.ToString();
  }
  return result;
} 

所以,我们有一个比长语法更短、更简单的交叉方式。

delegate (string arg1, int arg2, ..., FooNs.FooClass argn) { ... } 

为什么这个匿名方法如此有趣?让我们看看它是如何工作的

class MyClass
{
  // 1. object context from the class

  Func< int, bool > MyMethod(int arg1, int arg2)
  {
    // 2. object context from the class, together with the given arguments

    Func< int, bool > anonim = (/*implicitly typed int*/ argOfAninom) =>
    {
       // 3. context from the anonym method, together with the 
       // given arguments for this anonim method
       return true.
    }

    return anonim;
  }
} 

在任何地方、任何上下文中,示例中的匿名方法都将访问创建点 1、2 和 3 的上下文,特别是**它将保留在 MyMethod 的调用有效范围之内!**

如果我们给它一些递归,我们就会得到一个作用域列表,这些列表相互构建并完全由 CLR 管理。我们也可以使用它来捕获异常,并且代码堆栈在整个调用列表中都有效!

解决方案

了解了这些,实现简单的代码注入并不是一件困难的任务。我们只需要一个结构

public struct Scope
{
    public delegate Chain Chain(Chain code);
     
    public delegate void Block(); 
    
    public Scope(params Chain[] codes)
    {
        this.code = null;
        if (codes != null)
        {
            foreach (Chain code in codes)
            {
                AddCode(code);
            }
        }
    }

    public Scope(params Scope[] others)
    {
        this.code = null;
        if (others != null)
        {
            foreach (Scope other in others)
            {
                AddCode(other.code);
            }
        }
    } 
    
    Chain code; 
    
    private static Chain BlockToChain(Block code)
    {
        return c =>
        {
            code();
            return null;
        };
    } 
    
    private void AddCode(Chain otherCode)
    {
        if (otherCode != null)
        {
            Chain thisCode = this.code;
            if (thisCode == null)
            {
                this.code = otherCode;
            }
            else
            {
                this.code = (c) => otherCode(thisCode(c)); // Chain of anonym delegates.
            }
        }
    } 
    
    public Block Begin
    {
        set
        {
            if (value == null) throw new ArgumentNullException("value");
            if (code != null)
            {
                code(BlockToChain(value))(null); // Calling chain of delegates
            }
        }
    } 
    
    public static Scope operator +(Scope scope1, Scope scope2)
    {
        Scope result = new Scope(scope1);
        result.AddCode(scope2.code);
        return result;
    } 
    
    public static Scope operator +(Scope scope, Chain code)
    {
        Scope result = new Scope(scope);
        result.AddCode(code);
        return result;
    } 
    
    public static implicit operator Scope(Chain code)
    {
        return new Scope(code);
    } 
    
    public static implicit operator Chain(Scope scope)
    {
        return scope.code;
    }
}

它的核心是一个指向自身的 delegate 构造。在此基础上,我们能够创建调用匿名方法的链接。这个小结构可以被视为一个编译器,它可以与其他 Scope 结构或 Chain 处理程序方法组合(参见构造函数和运算符)。

Using the Code

这很简单。让我们看看如何定义异常处理 scope。将异常按功能分类、将它们划分为类并为这些类编写处理程序是有用的。

例如,参数异常处理

static Scope.Chain ArgumentExceptionScope(Scope.Chain upperNode)
{
    return (chainNode) =>
    {
        try
        {
            upperNode(null);
        }
        catch (ArgumentNullException ex)
        {
            if (echo) Console.WriteLine("Argument '{0}' is null.", ex.ParamName);
        }
        catch (ArgumentException ex)
        {
            if (echo) Console.WriteLine("Argument '{0}' is invalid.", ex.ParamName);
        }
        return null;
    };
} 

操作错误的异常处理程序

static Scope.Chain InvalidOpertaionExceptionScope(Scope.Chain upperNode)
{
    return (chainNode) =>
    {
        try
        {
            upperNode(null);
        }
        catch (InvalidOperationException)
        {
            if (echo) Console.WriteLine("Invalid operation.");
        }
        return null;
    };
}

一般异常处理程序

static Scope.Chain ExceptionScope(Scope.Chain upperNode)
{
    return (chainNode) =>
    {
        try
        {
            upperNode(null);
        }
        catch (Exception ex)
        {
            if (echo) Console.WriteLine("Unhandled '{0}' exception: {1}", 
					ex.GetType().Name, ex.Message);
        }
        return null;
    };
} 

等等。这些是 static 方法,但这并不是必须的,它们也可以属于一个类实例。这个想法的本质是 upperNode(null); 代表要修饰的代码,我们可以在处理程序中为此编写任何代码(例如 TransactionScope),事物将在此处注入。upperNode() 调用参数当然是 null,匿名方法的返回值也是 null,因为 chainNode 参数和返回值只是负担,本质是调用匿名方法。

处理程序可以看到我们定义的那个环境,这就是为什么创建基于它的日志记录器很简单

public class Logger
{
    public Logger(string name)
    {
        Enabled = true;
        Name = name;
        logScope = new Scope(Log);
    } 
    
    public bool Enabled { get; set; } 
    
    public string Name { get; private set; } 
    
    Scope logScope; 
    
    public Scope LogScope
    {
        get { return logScope; }
    } 
    
    Scope.Chain Log(Scope.Chain upperNode)
    {
        if (Enabled) 
            return DoLog(upperNode); 
        else
            return ln => { upperNode(null); return null; };
    } 
    
    private Scope.Chain DoLog(Scope.Chain upperNode)
    {
        return node =>
        {
            Console.WriteLine(">LOG --- Entering: {0} ---", Name);
            try
            {
                upperNode(null);
            }
            catch (Exception ex)
            {
                Console.WriteLine(">LOG --- Exception caught: {0} ---", 
						ex.GetType().Name);
                throw ex;
            }
            Console.WriteLine(">LOG --- Leaving: {0} ---", Name);
            return null;
        };
    }
}

好了,我们有很多处理程序可以创建并简单地运行;如何使用它们?很简单:让我们用运算符构建一个(或多个) Scope 结构

Scope scope = new Scope();
scope += logger.LogScope;
scope += ArgumentExceptionScope;
scope += InvalidOpertaionExceptionScope;
scope += ExceptionScope; 

或者使用构造函数

scope = new Scope(logger.LogScope, 
            new Scope(ArgumentExceptionScope, InvalidOpertaionExceptionScope), 
            ExceptionScope); 

scope 是从内到外构建的,因此最后给出的处理程序将用其代码环绕整个范围。无论我们是从现有的或复杂的范围还是从处理程序构建它,它都将运行,并且“气泡”规则从内到外将是有效的。

它非常灵活,因为它的某些部分可以由我们创建它的上下文驱动。在此示例中,可以通过 scope 中的 logger 对象打开或关闭日志记录,其中存在此实例(logger Enabled)。但是我们可以用任何复杂的业务规则来驱动处理程序。它比 AOP 和 EntLib 灵活得多,因为它们是独立的系统,不需要实现任何接口,事先不会有任何连接,CLR 会为我们安排一切。

只剩下在给定的 scope 中运行我们的代码(注入)。没有什么比这更容易的了

myCoolScope.Begin = () =>
{
   // Do stuff 
   
   // throw errors
   throw new Exception("Something went really wrong out there!");
}; 

我们可以在任何方法中这样做,代码将被注入到 myCoolScope 定义的环境中,可以记录日志,可以处理异常,可以定义策略处理,等等。从 Scope 的代码可以看出,Begin 之后的代码会立即执行(属性设置):这个 scopeBegin 构造之后应该被想象成一个普通的 using 块!

与 using 相比有一个区别:在 Scope Begin 代码中,你不能简单地通过“return”从方法中返回,但这也并非大问题。为此保留一个局部变量就足够了

private static void GetSomeValueTest()
{
    Console.WriteLine("\nGetSomeValueTest()");
    int v = 0;
    Console.WriteLine("Value is {0}.", v);
    v = GetSomeValueFromInjectedCode(v);
    Console.WriteLine("Value is {0}.", v);
} 

private static int GetSomeValueFromInjectedCode(int fromValue)
{
    fullScope.Begin = () =>
    {
        fromValue = fromValue + 1978;
    };
    return fromValue;
} 

HandlerBase

我还绘制了一个处理程序 abstract 基类,以简化生活

public sealed class ScopeExceptionEventArg : EventArgs
{
    public Exception Exception { get; internal set; }

    public bool Handled { get; set; }
}

public abstract class HandlerBase
{
    public HandlerBase()
    {
        scope = new Scope(ScopeMethod);
        Enabled = true;
    }
    
    Scope scope;

    // The Scope structure for compiling scopes:
    public Scope Scope
    {
        get { return scope; }
    }

    public static implicit operator Scope(HandlerBase handler)
    {
        if (handler == null) return new Scope();
        return handler.scope;
    }

    bool inFlag;

    public bool Enabled { get; set; } // Can be enabled or disabled

    // There are events also:
    
    public event EventHandler EnterScope;

    public event EventHandler LeaveScope;

    public event EventHandler< ScopeExceptionEventArg > ScopeException;


    Scope.Chain ScopeMethod(Scope.Chain code)
    {
        if (inFlag || !Enabled) return (p) => { code(null); return null; };

        return (p) => 
        {
            inFlag = true;
            try
            {
                OnEnterScope(EventArgs.Empty);
                this.code = code;
                Call();
            }
            finally
            {
                OnLeaveScope(EventArgs.Empty);
                inFlag = false;
            }
            return null; 
        };
    }

    Scope.Chain code;

    // Injected code called as a virtual method
    protected virtual void Call()
    {
        if (code != null)
        {
            try
            {
                code(null);
            }
            catch (Exception ex)
            {
                ScopeExceptionEventArg e = new ScopeExceptionEventArg { Exception = ex };
                OnScopeException(e);
                if (!e.Handled) throw ex;
            }
            finally
            {
                code = null;
            }
        }
    }

    // Event handler methods are virtual also
    
    protected virtual void OnEnterScope(EventArgs e)
    {
        EventHandler handler = EnterScope;
        if (handler != null) handler(this, e);
    }

    protected virtual void OnLeaveScope(EventArgs e)
    {
        EventHandler handler = LeaveScope;
        if (handler != null) handler(this, e);
    }

    protected virtual void OnScopeException(ScopeExceptionEventArg e)
    {
        EventHandler< ScopeExceptionEventArg > handler = ScopeException;
        if (handler != null) handler(this, e);
    }
}

通过这种方式,我们可以更容易地创建一个日志记录器(或任何其他东西)

public class LoggerScope : HandlerBase
{
    public LoggerScope() : base() { }

    public string Message { get; set; }

    protected override void OnEnterScope(EventArgs e)
    {
        base.OnEnterScope(e);
        if (!string.IsNullOrEmpty(Message))
        {
            Console.WriteLine(">LOG --- Entering: {0} ---", Message);
        }
    }

    protected override void OnLeaveScope(EventArgs e)
    {
        base.OnLeaveScope(e);
        if (!string.IsNullOrEmpty(Message))
        {
            Console.WriteLine(">LOG --- Leaving: {0} ---", Message);
            Message = null;
        }
    }

    protected override void OnScopeException(ScopeExceptionEventArg e)
    {
        base.OnScopeException(e);
        if (!string.IsNullOrEmpty(Message))
        {
            Console.WriteLine(">LOG --- Exception caught: {0}, 
		Type: {1}  ---", Message, e.Exception.GetType().Name);
        }
    }
}

或者一个性能分析器

public class ProfilerScope : HandlerBase
{
    DateTime start;

    public TimeSpan EllapsedTime
    {
        get;
        private set;
    }

    protected override void OnEnterScope(EventArgs e)
    {
        base.OnEnterScope(e);
        start = DateTime.Now;
        EllapsedTime = TimeSpan.Zero;
    }

    protected override void OnLeaveScope(EventArgs e)
    {
        EllapsedTime = DateTime.Now - start;
        base.OnLeaveScope(e);
    }
}

您可以从这些构建一个 scope

LoggerScope logger = new LoggerScope();
ProfilerScope profiler = new ProfilerScope();
Scope scope = new Scope(profiler.Scope, logger.Scope);

// ...

scope.Begin = () => // profiled and logged
{
 // Do stuff.
}

// ...

// Turn off logger for this call:
logger.Enabled = false;
scope.Begin = () =>
{
 // Do stuff.
}

// And so on. The scope can be handled with proper handler classes.

我附上了一个测试项目,这样您就可以看到它的简单和高效。在测试程序中,使用此方法进行异常处理的速度并不比临时编写处理程序慢(大约是 delegate 调用),但比 EHAB 快得多,也简单得多。

一个实际示例

举个例子:我有一个实体管理器服务。在其业务逻辑中,可能会发生异常。例如,我们想将一个城市移动到一个属于另一个国家(而不是该城市所在国家)的地区——这会产生异常,因为它错了,不能这样做。这些异常由业务逻辑定义,它们是标准的 CLR 异常。但是,这些异常不会跨 SOAP 通信,它们必须转换为兼容的 SOAP 错误消息。“伐木工方法”将是如果我将捕获和转换异常的代码添加到外观(facade)的代码中。这不好,这应该是业务逻辑的任务——这是它的职责。也许我可以编写一个 WCF 处理程序来自动执行这项工作,但问题是,我将无法在没有 WCF 的情况下静态地测试这些库。

所以我为业务逻辑编写了一个 Scope 处理程序,外观可以从中创建适当的 scope

public class DataExceptionConvertHandler : HandlerBase
{
    protected override void Call()
    {
        try
        {
            base.Call();
        }

        catch (NameStoreItemNotFoundExcepton ex)
        {
            throw new FaultException< NameStoreItemNotFoundFault >
                (new NameStoreItemNotFoundFault { NameStoreItemID = ex.ID }, ex.Message);
        }

        catch (NameStoreItemIsUnremovableExcepton ex)
        {
            throw new FaultException< NameStoreItemIsUnremovableFault >
                (new NameStoreItemIsUnremovableFault 
			{ NameStoreItemID = ex.ID }, ex.Message);
        }

        catch (RegionNotFoundExcepton ex)
        {
            throw new FaultException< RegionNotFoundFault >
                (new RegionNotFoundFault { RegionUID = ex.EnitityUID, 
		CountryCode = GeoUtils.GetCountryCodeOfLCID(ex.LCID) }, ex.Message);
        }

        catch (RegionIsUnremovableExcepton ex)
        {
            throw new FaultException< RegionIsUnremovableFault >
                (new RegionIsUnremovableFault { RegionUID = ex.EnitityUID });
        }

        catch (SettlementNotFoundException ex)
        {
            throw new FaultException< SettlementNotFoundFault >
                (new SettlementNotFoundFault { SettlementUID = ex.EnitityUID });
        }

        catch (SettlementIsUnremovableException ex)
        {
            throw new FaultException< SettlementIsUnremovableFault >
                (new SettlementIsUnremovableFault { SettlementUID = ex.EnitityUID });
        }
    }
}

一切都在正确的地方,它透明、快速且易于维护。服务外观(service-façade)现在看起来像这样(在此示例中,目前只有这个转换 scope 可用)

public partial class PartnerManagementService : IPartnerManagement
{
    public PartnerManagementService()
    {
        facadeScope = new DataExceptionConvertHandler();
    }
    
    #region Scopes 
    
    Scope facadeScope; 
    
    #endregion
     
    #region Validation 
    
    private static void ValidateWithNeutralLocale(object request)
    {
        // I have a C 3.0 lambda based validator also. This will be my second post. ;)
    }
     
    private static void Validate(object request)
    {
        // ...
    }
    
    #endregion
} 
public partial class PartnerManagementService
{
    public NACENamesResponse GetNACENames(NACENamesRequest request)
    {
        NACENamesResponse response = null; 
        facadeScope.Begin = () =>
        {
            ValidateWithNeutralLocale(request); 
            INACEManager man = DataManagerFactory.CreateNACEManager();
            List< NACENameModel > list = man.GetByLCID
		(request.International.GetNeutralCulture().LCID);
            response = new NACENamesResponse
            {
                SectorNames = list.ToArray()
            };
        }; 
        return response;
    } 
    
    public NACENameResponse GetNACEName(NACENameRequest request)
    {
        NACENameResponse response = null; 
        facadeScope.Begin = () =>
        {
            ValidateWithNeutralLocale(request); 
            INACEManager man = DataManagerFactory.CreateNACEManager();
            response = new NACENameResponse
            {
                NACENameValue = man.GetName(request.NACECode, 
		request.International.GetNeutralCulture().LCID)
            };
        }; 
        return response;
    } 
    
    public void DefineNACEName(DefineNACENameRequest request)
    {
        facadeScope.Begin = () =>
        {
            ValidateWithNeutralLocale(request); 
            INACEManager man = DataManagerFactory.CreateNACEManager();
            man.DefineName(request.NACEName, 
		request.International.GetNeutralCulture().LCID);
        };
    } 
    
    public void UndefineNACEName(UndefineNACENameRequest request)
    {
        facadeScope.Begin = () =>
        {
            ValidateWithNeutralLocale(request); 
            INACEManager man = DataManagerFactory.CreateNACEManager();
            man.UndefineName(request.NACECode, 
		request.International.GetNeutralCulture().LCID);
        };
    }
} 

这就是 C# 3.0 中的“穷人代码注入器”。请好好使用它!

历史

  • 2007 年 12 月 18 日 - 第一个版本
© . All rights reserved.