Allie's blog

加油
posts - 11, comments - 0, trackbacks - 0, articles - 0
  Home :: Contact :: Syndication  :: Login

Tuesday, October 07, 2008

只有经历了才能体会到,幸福不是别人给你的,只能自己给自己创造。奢望别人给你幸福,太愚蠢了。没有人会随随便便给你你想要的。

posted @ 2:57 PM | Feedback (0)

Sunday, June 18, 2006

Attribute在.net编程中的应用(一)
         Attribute的基本概念

.NET Framework中对Attribute的支持是一个全新的功能,这种支持来自它的Attribute类。在你的程序中适当地使用这个类,或者是灵活巧妙地利用这个类,将使你的程序获得某种在以往编程中很难做到的能力。我们来看一个例子:
假如你是一个项目开发小组中的成员,你想要跟踪项目代码检查的信息,通常你可以把代码的检查信息保存在数据库中以便查询;或者把信息写到代码的注释里面,这样可以阅读代码的同时看到代码被检查的信息。我们知道.NET的组件是自描述的,那么是否可以让代码自己来描述它被检查的信息呢?这样我们既可以将信息和代码保存在一起,又可以通过代码的自我描述得到信息。答案就是使用Attribute.
下面的步骤和代码告诉你怎么做:
首先,我们创建一个自定义的Attribute,并且事先设定我们的Attribute将施加在class的元素上面以获取一个类代码的检查信息。


using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Class)] //还记得上一节的内容吗?
public class CodeReviewAttribute : System.Attribute //定义一个CodeReview的Attribute
{
private string reviewer; //代码检查人
private string date; //检查日期
private string comment; //检查结果信息

//参数构造器
public CodeReviewAttribute(string reviewer, string date)
{
this.reviewer=reviewer;
this.date=date;
}

public string Reviewer
{
get
{
return reviewer;
}
}

public string Date
{
get
{
return date;
}
}

public string Comment
{
get
{
return comment;
}
set
{
comment=value;
}
}
}



我们的自定义CodeReviewAttribute同普通的类没有区别,它从Attribute派生,同时通过AttributeUsage表示我们的Attribute仅可以施加到类元素上。

第二步就是使用我们的CodeReviewAttribute, 假如我们有一个Jack写的类MyClass,检查人Niwalker,检查日期2003年7月9日,于是我们施加Attribute如下:

[CodeReview("Niwalker","2003-7-9",Comment="Jack的代码")]
public class MyClass
{
//类的成员定义


当这段代码被编译的时候,编译器会调用CodeReviewAttribute的构造器并且把"Niwalker"和"2003-7-9"分别作为构造器的参数。注意到参数表中还有一个Comment属性的赋值,这是Attribute特有的方式,这里你可以设置更多的Attribute的公共属性(如果有的话),需要指出的是.NET Framework1.0允许向private的属性赋值,但在.NET Framework1.1已经不允许这样做,只能向public的属性赋值。

第三步就是取出我们需要的信息,这是通过.NET的反射来实现的,关于反射的知识,限于篇幅我不打算在这里进行说明,也许我会在以后另外写一篇介绍反射的文章。

class test
{
static void Main(string[] args)
{
System.Reflection.MemberInfo info=typeof(MyClass); //通过反射得到MyClass类的信息

//得到施加在MyClass类上的定制Attribute 
CodeReviewAttribute att=
(CodeReviewAttribute)Attribute.GetCustomAttribute(info,typeof(CodeReviewAttribute)); 
if(att!=null)
{
Console.WriteLine("代码检查人:{0}",att.Reviewer);
Console.WriteLine("检查时间:{0}",att.Date);
Console.WriteLine("注释:{0}",att.Comment);
}
}
}

在上面这个例子中,Attribute扮演着向一个类添加额外信息的角色,它并不影响MyClass类的行为。通过这个例子,我们大致可以知道如何写一个自定义的Attribute,以及如何在应用程序使用它。下一节,我将介绍如何使用Attribute来自动生成ADO.NET的数据访问类的代码。

用于参数的Attribute

在编写多层应用程序的时候,你是否为每次要写大量类似的数据访问代码而感到枯燥无味?比如我们需要编写调用存储过程的代码,或者编写T_SQL代码,这些代码往往需要传递各种参数,有的参数个数比较多,一不小心还容易写错。有没有一种一劳永逸的方法?当然,你可以使用MS的Data Access Application Block,也可以使用自己编写的Block。这里向你提供一种另类方法,那就是使用Attribute。

下面的代码是一个调用AddCustomer存储过程的常规方法:

public int AddCustomer(SqlConnection connection, 
                       string customerName,
                      string country,
                       string province,
                      string city,
                       string address,
                       string telephone) {
   SqlCommand command=new SqlCommand("AddCustomer", connection);
   command.CommandType=CommandType.StoredProcedure;
   command.Parameters.Add("@CustomerName",SqlDbType.NVarChar,50).Value=customerName;
   command.Parameters.Add("@country",SqlDbType.NVarChar,20).Value=country;
   command.Parameters.Add("@Province",SqlDbType.NVarChar,20).Value=province;
   command.Parameters.Add("@City",SqlDbType.NVarChar,20).Value=city;
   command.Parameters.Add("@Address",SqlDbType.NVarChar,60).Value=address;
   command.Parameters.Add("@Telephone",SqlDbType.NvarChar,16).Value=telephone;
   command.Parameters.Add("@CustomerId",SqlDbType.Int,4).Direction=ParameterDirection.Output;
   connection.Open();
   command.ExecuteNonQuery();
   connection.Close();
   int custId=(int)command.Parameters["@CustomerId"].Value;
   return custId;
}

上面的代码,创建一个Command实例,然后添加存储过程的参数,然后调用ExecuteMonQuery方法执行数据的插入操作,最后返回CustomerId。从代码可以看到参数的添加是一种重复单调的工作。如果一个项目有100多个甚至几百个存储过程,作为开发人员的你会不会要想办法偷懒?(反正我会的:-))。

下面开始我们的代码自动生成工程:

我们的目的是根据方法的参数以及方法的名称,自动生成一个Command对象实例,第一步我们要做的就是创建一个SqlParameterAttribute, 代码如下:

SqlCommandParameterAttribute.cs 
using System;
using System.Data;
using Debug=System.Diagnostics.Debug;
namespace DataAccess {
   // SqlParemeterAttribute 施加到存储过程参数
   [ AttributeUsage(AttributeTargets.Parameter) ]
   public class SqlParameterAttribute : Attribute {
      private string name; //参数名称
      private bool paramTypeDefined; //是否参数的类型已经定义
      private SqlDbType paramType; //参数类型
      private int size; //参数尺寸大小
      private byte precision; //参数精度
      private byte scale; //参数范围
      private bool directionDefined; //是否定义了参数方向
      private ParameterDirection direction; //参数方向


      public SqlParameterAttribute() { }

      public string Name {
         get { return name == null ? string.Empty : name; }
         set { _name = value; }
      }

      public int Size {
         get { return size; }
         set { size = value; }
      }

      public byte Precision {
         get { return precision; }
         set { precision = value; }
      }

      public byte Scale {
         get { return scale; }
         set { scale = value; }
      }

      public ParameterDirection Direction {
         get {
            Debug.Assert(directionDefined);
            return direction;
         }
         set {
            direction = value;
            directionDefined = true;
         }
      }

      public SqlDbType SqlDbType {
         get {
            Debug.Assert(paramTypeDefined);
            return paramType;
         }
         set {
            paramType = value;
            paramTypeDefined = true;
         }
      }

      public bool IsNameDefined {    
         get {
            return name != null && name.Length != 0;
         }
      }
   
      public bool IsSizeDefined {
         get { return size != 0; }
      }

      public bool IsTypeDefined {
         get { return paramTypeDefined; }
      }

      public bool IsDirectionDefined {
         get { return directionDefined; }
      }

      public bool IsScaleDefined {
         get { return _scale != 0; }
      }

      public bool IsPrecisionDefined {
         get { return _precision != 0; }
      }
      ...
以上定义了SqlParameterAttribute的字段和相应的属性,为了方便Attribute的使用,我们重载几个构造器,不同的重载构造器用于不用的参数:
      ... 
      // 重载构造器,如果方法中对应于存储过程参数名称不同的话,我们用它来设置存储过程的名称
      // 其他构造器的目的类似
      public SqlParameterAttribute(string name) {
         Name=name;
      }

      public SqlParameterAttribute(int size) {
         Size=size;
      }

      public SqlParameterAttribute(SqlDbType paramType) {
         SqlDbType=paramType;
      }

      public SqlParameterAttribute(string name, SqlDbType paramType) {
         Name = name;
         SqlDbType = paramType;
      }

      public SqlParameterAttribute(SqlDbType paramType, int size) {
         SqlDbType = paramType;
         Size = size;
      }

      public SqlParameterAttribute(string name, int size) {
         Name = name;
         Size = size;
      }

      public SqlParameterAttribute(string name, SqlDbType paramType, int size) {
         Name = name;
         SqlDbType = paramType;
         Size = size;
      }
   }
}

为了区分方法中不是存储过程参数的那些参数,比如SqlConnection,我们也需要定义一个非存储过程参数的Attribute:

//NonCommandParameterAttribute.cs 
using System;
namespace DataAccess {
   [ AttributeUsage(AttributeTargets.Parameter) ]
   public sealed class NonCommandParameterAttribute : Attribute { }
}

我们已经完成了SQL的参数Attribute的定义,在创建Command对象生成器之前,让我们考虑这样的一个事实,那就是如果我们数据访问层调用的不是存储过程,也就是说Command的CommandType不是存储过程,而是带有参数的SQL语句,我们想让我们的方法一样可以适合这种情况,同样我们仍然可以使用Attribute,定义一个用于方法的Attribute来表明该方法中的生成的Command的CommandType是存储过程还是SQL文本,下面是新定义的Attribute的代码:

//SqlCommandMethodAttribute.cs 
using System;
using System.Data;
namespace Emisonline.DataAccess {
   [AttributeUsage(AttributeTargets.Method)]
   public sealed class SqlCommandMethodAttribute : Attribute {
      private string commandText;
      private CommandType commandType;
      public SqlCommandMethodAttribute( CommandType commandType, string commandText) {
         commandType=commandType;
         commandText=commandText;
      }
   
      public SqlCommandMethodAttribute(CommandType commandType) : this(commandType, null){
      }
   
      public string CommandText {
         get { return commandText==null ? string.Empty : commandText; }
         set { commandText=value; }
      }

      public CommandType CommandType {
         get { return commandType; }
         set { commandType=value; }
      }
   }
}

我们的Attribute的定义工作已经全部完成,下一步就是要创建一个用来生成Command对象的类。
SqlCommandGenerator类的设计

SqlCommandGEnerator类的设计思路就是通过反射得到方法的参数,使用被SqlCommandParameterAttribute标记的参数来装配一个Command实例。

引用的命名空间:

//SqlCommandGenerator.cs 
using System;
using System.Reflection;
using System.Data;
using System.Data.SqlClient;
using Debug = System.Diagnostics.Debug;
using StackTrace = System.Diagnostics.StackTrace;

类代码:

namespace DataAccess { 
   public sealed class SqlCommandGenerator {
      //私有构造器,不允许使用无参数的构造器构造一个实例
      private SqlCommandGenerator() {
         throw new NotSupportedException();
      }

      //静态只读字段,定义用于返回值的参数名称
      public static readonly string ReturnValueParameterName = "RETURN_VALUE";
   
      //静态只读字段,用于不带参数的存储过程
      public static readonly object[] NoValues = new object[] {};
   
      public static SqlCommand GenerateCommand(SqlConnection connection, MethodInfo method, object[] values) {
         //如果没有指定方法名称,从堆栈帧得到方法名称
         if (method == null)
            method = (MethodInfo) (new StackTrace().GetFrame(1).GetMethod());
   
         // 获取方法传进来的SqlCommandMethodAttribute
         // 为了使用该方法来生成一个Command对象,要求有这个Attribute。
         SqlCommandMethodAttribute commandAttribute = (SqlCommandMethodAttribute) Attribute.GetCustomAttribute(method, typeof(SqlCommandMethodAttribute));
         Debug.Assert(commandAttribute != null);
         Debug.Assert(commandAttribute.CommandType == CommandType.StoredProcedure || commandAttribute.CommandType == CommandType.Text);
   
         // 创建一个SqlCommand对象,同时通过指定的attribute对它进行配置。
         SqlCommand command = new SqlCommand();
         command.Connection = connection;
         command.CommandType = commandAttribute.CommandType;
   
         // 获取command的文本,如果没有指定,那么使用方法的名称作为存储过程名称
         if (commandAttribute.CommandText.Length == 0) {
            Debug.Assert(commandAttribute.CommandType == CommandType.StoredProcedure);
            command.CommandText = method.Name;
         }
         else {
            command.CommandText = commandAttribute.CommandText;
         }
   
         // 调用GeneratorCommandParameters方法,生成command参数,同时添加一个返回值参数
         GenerateCommandParameters(command, method, values);
         command.Parameters.Add(ReturnValueParameterName, SqlDbType.Int).Direction =ParameterDirection.ReturnValue;
         return command;
      }
   
      private static void GenerateCommandParameters( SqlCommand command, MethodInfo method, object[] values) {
         // 得到所有的参数,通过循环一一进行处理。
         ParameterInfo[] methodParameters = method.GetParameters();
         int paramIndex = 0;
         foreach (ParameterInfo paramInfo in methodParameters) {
            // 忽略掉参数被标记为[NonCommandParameter ]的参数
            if (Attribute.IsDefined(paramInfo, typeof(NonCommandParameterAttribute)))
               continue;
         
            // 获取参数的SqlParameter attribute,如果没有指定,那么就创建一个并使用它的缺省设置。
            SqlParameterAttribute paramAttribute = (SqlParameterAttribute) Attribute.GetCustomAttribute( paramInfo, typeof(SqlParameterAttribute));
            if (paramAttribute == null)
               paramAttribute = new SqlParameterAttribute();
      
            //使用attribute的设置来配置一个参数对象。使用那些已经定义的参数值。如果没有定义,那么就从方法
            // 的参数来推断它的参数值。
            SqlParameter sqlParameter = new SqlParameter();
            if (paramAttribute.IsNameDefined)
               sqlParameter.ParameterName = paramAttribute.Name;
            else
               sqlParameter.ParameterName = paramInfo.Name;
   
            if (!sqlParameter.ParameterName.StartsWith("@"))
               sqlParameter.ParameterName = "@" + sqlParameter.ParameterName;
   
            if (paramAttribute.IsTypeDefined)
               sqlParameter.SqlDbType = paramAttribute.SqlDbType;


            if (paramAttribute.IsSizeDefined)
               sqlParameter.Size = paramAttribute.Size;

            if (paramAttribute.IsScaleDefined)
               sqlParameter.Scale = paramAttribute.Scale;

            if (paramAttribute.IsPrecisionDefined)
               sqlParameter.Precision = paramAttribute.Precision;

            if (paramAttribute.IsDirectionDefined) {
               sqlParameter.Direction = paramAttribute.Direction;
            }
            else {
               if (paramInfo.ParameterType.IsByRef) {
                  sqlParameter.Direction = paramInfo.IsOut ? ParameterDirection.Output : ParameterDirection.InputOutput;
               }
               else {
                  sqlParameter.Direction = ParameterDirection.Input;
               }
            }

            // 检测是否提供的足够的参数对象值
            Debug.Assert(paramIndex < values.Length);

            //把相应的对象值赋于参数。
            sqlParameter.Value = values[paramIndex];
      
            command.Parameters.Add(sqlParameter); paramIndex++;
         }
   
         //检测是否有多余的参数对象值
         Debug.Assert(paramIndex == values.Length);
      }
   }
}

必要的工作终于完成了。SqlCommandGenerator中的代码都加上了注释,所以并不难读懂。下面我们进入最后的一步,那就是使用新的方法来实现上一节我们一开始显示个那个AddCustomer的方法。

重构新的AddCustomer代码:

[ SqlCommandMethod(CommandType.StoredProcedure) ] 
public void AddCustomer( [NonCommandParameter] SqlConnection connection,
                         [SqlParameter(50)] string customerName,
                         [SqlParameter(20)] string country,
                         [SqlParameter(20)] string province,
                         [SqlParameter(20)] string city,
                         [SqlParameter(60)] string address,
                         [SqlParameter(16)] string telephone,
                         out int customerId ) {
   customerId=0; //需要初始化输出参数

   //调用Command生成器生成SqlCommand实例
   SqlCommand command = SqlCommandGenerator.GenerateCommand( connection, null, new object[] {customerName,country,province,city,address,telephone,customerId } );
   connection.Open();
   command.ExecuteNonQuery();
   connection.Close();
   
   //必须明确返回输出参数的值
   customerId=(int)command.Parameters["@CustomerId"].Value;
}

代码中必须注意的就是out参数,需要事先进行初始化,并在Command执行操作以后,把参数值传回给它。受益于Attribute,使我们摆脱了那种编写大量枯燥代码编程生涯。 我们甚至还可以使用Sql存储过程来编写生成整个方法的代码,如果那样做的话,可就大大节省了你的时间了,上一节和这一节中所示的代码,你可以把它们单独编译成一个组件,这样就可以在你的项目中不断的重用它们了。从下一节开始,我们将更深层次的介绍Attribute的应用,请继续关注。

Attribute在拦截机制上的应用

从这一节开始我们讨论Attribute的高级应用,为此我准备了一个实际的例子:我们有一个订单处理系统,当一份订单提交的时候,系统检查库存,如果库存存量满足订单的数量,系统记录订单处理记录,然后更新库存,如果库存存量低于订单的数量,系统做相应的记录,同时向库存管理员发送邮件。为了方便演示,我们对例子进行了简化:

//Inventory.cs using System; 
using System.Collections;
namespace NiwalkerDemo {
   public class Inventory {
      private Hashtable inventory=new Hashtable();

      public Inventory() {
         inventory["Item1"]=100;
         inventory["Item2"]=200;
      }
   
      public bool Checkout(string product, int quantity) {
         int qty=GetQuantity(product);
         return qty>=quantity;
      }

      public int GetQuantity(string product) {
         int qty=0;
         if(inventory[product]!=null)
            qty = (int)inventory[product];
            return qty;
      }
      
      public void Update(string product, int quantity) {
         int qty=GetQuantity(product);
         inventory[product]=qty-quantity;
      }
   }
}


//Logbook.cs
using System;
namespace NiwalkerDemo {
   public class Logbook {
      public static void Log(string logData) {
         Console.WriteLine("log:{0}",logData);
      }    
   }
}

//Order.cs
using System;
namespace NiwalkerDemo {
   public class Order {
      private int orderId;
      private string product;
      private int quantity;

      public Order(int orderId) { this.orderId=orderId; }
   
      public void Submit() {
         Inventory inventory=new Inventory(); //创建库存对象
   
         //检查库存
         if(inventory.Checkout(product,quantity)) {
            Logbook.Log("Order"+orderId+" available");
            inventory.Update(product,quantity);
         }
         else {    
            Logbook.Log("Order"+orderId+" unavailable");
            SendEmail();
         }
      }

      public string ProductName {
         get{ return product; }
         set{ product=value; }
      }

      public int OrderId {
         get{ return orderId; }
      }

      public int Quantity {
         get{ return quantity;}
         set{ quantity=value; }
      }

      public void SendEmail() {
         Console.WriteLine("Send email to manager");
      }
   }
}


下面是调用程序:

//AppMain.cs 
using System;
namespace NiwalkerDemo {
   public class AppMain {
      static void Main() {
         Order order1=new Order(100);
         order1.ProductName="Item1";
         order1.Quantity=150;
         order1.Submit();
      
         Order order2=new Order(101);
         order2.ProductName="Item2";
         order2.Quantity=150;
         order2.Submit();
      }
   }
}

程序看上去还不错,商务对象封装了商务规则,运行的结果也符合要求。但是我好像听到你在抱怨了,没有吗?当你的客户的需求改变的时候(客户总是经常改变他们的需求),比如库存检查的规则不是单一的检查产品的数量,还要检查产品是否被预订的多种情况,那么你需要改变Inventory的代码,同时还要修改Order中的代码,我们的例子只是一个简单的商务逻辑,实际的情况比这个要复杂的多。问题在于Order对象同其他的对象之间是紧耦合的,从OOP的观点出发,这样的设计是有问题的,如果你写出这样的程序,至少不会在我的团队里面被Pass.

你说了:“No problem! 我们可以把商务逻辑抽出来放到一个专门设计的用来处理事务的对象中。”嗯,好主意,如果你是这么想的,或许我还可以给你一个提议,使用Observer Design Pattern(观察者设计模式):你可以使用delegate,在Order对象中定义一个BeforeSubmit和AfterSubmit事件,然后创建一个对象链表,将相关的对象插入到这个链表中,这样就可以实现对Order提交事件的拦截,在Order提交之前和提交之后自动进行必要的事务处理。如果你感兴趣的话,你可以自己动手来编写这样的一个代码,或许还要考虑在分布式环境中(Order和Inventory不在一个地方)如何处理对象之间的交互问题。

幸运的是,.NET Framework中提供了实现这种技术的支持。在.NET Framework中的对象Remoting和组件服务中,有一个重要的拦截机制,在对象Remoting中,不同的应用程序之间的对象的交互需要穿越他们的域边界,每一个应用域也可以细分为多个Context(上下文环境),每一个应用域也至少有一个默认的Context,即使在同一个应用域,也存在穿越不同Context的问题。NET的组件服务发展了COM+的组件服务,它使用Context Attribute来实现象COM+一样的拦截功能。通过对调用对象的拦截,我们可以对一个方法的调用进行前处理和后处理,同时也解决了上述的跨越边界的问题。

需要提醒你,如果你在MSDN文档查ContextAttribute,我可以保证你得不到任何有助于了解ContextAttribute的资料,你看到的将是这么一句话:“This type supports the .NET Framework infrastructure and is not intended to be used directly from your code.”——“本类型支持.NET Framework基础结构,它不打算直接用于你的代码。”不过,在msdn站点,你可以看到一些有关这方面的资料(见文章后面的参考链接)。

下面我们介绍有关的几个类和一些概念,首先是:

ContextAttribute类

ContextAttribute派生自Attribute,同时它还实现了IContextAttribute和IContextProperty接口。所有自定义的ContextAttribute必须从这个类派生。
构造器:
ContextAttribute:构造器带有一个参数,用来设置ContextAttribute的名称。

公共属性:
Name:只读属性。返回ContextAttribute的名称

公共方法:
GetPropertiesForNewContext:虚拟方法。向新的Context添加属性集合。
IsContextOK:虚拟方法。查询客户Context中是否存在指定的属性。
IsNewContextOK:虚拟方法。默认返回true。一个对象可能存在多个Context,使用这个方法来检查新的Context中属性是否存在冲突。
Freeze:虚拟方法。该方法用来定位被创建的Context的最后位置。

ContextBoundObject类

实现被拦截的类,需要从ContextBoundObject类派生,这个类的对象通过Attribute来指定它所在Context,凡是进入该Context的调用都可以被拦截。该类从MarshalByRefObject派生。

以下是涉及到的接口:

IMessage:定义了被传送的消息的实现。一个消息必须实现这个接口。

IMessageSink:定义了消息接收器的接口,一个消息接收器必须实现这个接口。

还有几个接口,我们将在下一节结合拦截构架的实现原理中进行介绍。
(待续)

posted @ 7:40 PM | Feedback (0)

Wednesday, June 14, 2006

Attribute在.net编程中的应用(一)
Attribute的基本概念

niwalker(选自中国软件)

经常有朋友问,Attribute是什么?它有什么用?好像没有这个东东程序也能运行。实际上在.Net中,Attribute是一个非常重要的组成部分,为了帮助大家理解和掌握Attribute,以及它的使用方法,特地收集了几个Attribute使用的例子,提供给大家参考。

在具体的演示之前,我想先大致介绍一下Attribute。我们知道在类的成员中有property成员,二者在中文中都做属性解释,那么它们到底是不是同一个东西呢?从代码上看,明显不同,首先就是它们的在代码中的位置不同,其次就是写法不同(Attribute必须写在一对方括符中)。

什么是Atrribute

首先,我们肯定Attribute是一个类,下面是msdn文档对它的描述:
公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型、字段、方法和属性等。Attributes和Microsoft .NET Framework文件的元数据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响应用程序的行为。

在.NET中,Attribute被用来处理多种问题,比如序列化、程序的安全特征、防止即时编译器对程序代码进行优化从而代码容易调试等等。下面,我们先来看几个在.NET中标准的属性的使用,稍后我们再回过头来讨论Attribute这个类本身。(文中的代码使用C#编写,但同样适用所有基于.NET的所有语言)

Attribute作为编译器的指令

在C#中存在着一定数量的编译器指令,如:#define DEBUG, #undefine DEBUG, #if等。这些指令专属于C#,而且在数量上是固定的。而Attribute用作编译器指令则不受数量限制。比如下面的三个Attribute:

  • Conditional:起条件编译的作用,只有满足条件,才允许编译器对它的代码进行编译。一般在程序调试的时候使用。
  • DllImport:用来标记非.NET的函数,表明该方法在一个外部的DLL中定义。
  • Obsolete:这个属性用来标记当前的方法已经被废弃,不再使用了。

下面的代码演示了上述三个属性的使用:

 #define DEBUG //这里定义条件 
 using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace AttributeDemo {
    class MainProgramClass {
       [DllImport("User32.dll")]
       public static extern int MessageBox(int hParent, string Message, string Caption, int Type);


       static void Main(string[] args) {
          DisplayRunningMessage();
          DisplayDebugMessage();
          MessageBox(0,"Hello","Message",0);
          Console.ReadLine();
       }

       [Conditional("DEBUG")]
       private static void DisplayRunningMessage() {
          Console.WriteLine("开始运行Main子程序。当前时间是"+DateTime.Now);
       }

       [Conditional("DEBUG")]
       [Obsolete]
       private static void DisplayDebugMessage() {
          Console.WriteLine("开始Main子程序");
       }
    }
}

如果在一个程序元素前面声明一个Attribute,那么就表示这个Attribute被施加到该元素上,前面的代码,[DllImport]施加到MessageBox函数上, [Conditional]施加到DisplayRuntimeMessage方法和DisplayDebugMessage方法,[Obsolete]施加到DisplayDebugMessage方法上。

根据上面涉及到的三个Attribute的说明,我们可以猜到程序运行的时候产生的输出:DllImport Attribute表明了MessageBox是User32.DLL中的函数,这样我们就可以像内部方法一样调用这个函数。

重要的一点就是Attribute就是一个类,所以DllImport也是一个类,Attribute类是在编译的时候被实例化的,而不是像通常的类那样在运行时候才实例化。Attribute实例化的时候根据该Attribute类的设计可以带参数,也可以不带参数,比如DllImport就带有"User32.dll"的参数。Conditional对满足参数的定义条件的代码进行编译,如果没有定义DEBUG,那么该方法将不被编译,读者可以把#define DEBUG一行注释掉看看输出的结果(release版本,在Debug版本中Conditional的debug总是成立的)。Obsolete表明了DispalyDebugMessage方法已经过时了,它有一个更好的方法来代替它,当我们的程序调用一个声明了Obsolete的方法时,那么编译器会给出信息,Obsolete还有其他两个重载的版本。大家可以参考msdn中关于的ObsoleteAttribute 类的描述。

Attribute类

除了.NET提供的那些Attribute派生类之外,我们可以自定义我们自己的Attribute,所有自定义的Attribute必须从Attribute类派生。现在我们来看一下Attribute 类的细节:

protected Attribute(): 保护的构造器,只能被Attribute的派生类调用。

三个静态方法:

static Attribute GetCustomAttribute():这个方法有8种重载的版本,它被用来取出施加在类成员上指定类型的Attribute。

static Attribute[] GetCustomAttributes(): 这个方法有16种重载版本,用来取出施加在类成员上指定类型的Attribute数组。

static bool IsDefined():由八种重载版本,看是否指定类型的定制attribute被施加到类的成员上面。

实例方法:

bool IsDefaultAttribute(): 如果Attribute的值是默认的值,那么返回true。

bool Match():表明这个Attribute实例是否等于一个指定的对象。

公共属性: TypeId: 得到一个唯一的标识,这个标识被用来区分同一个Attribute的不同实例。

我们简单地介绍了Attribute类的方法和属性,还有一些是从object继承来的。这里就不列出来了。

下面介绍如何自定义一个Attribute: 自定义一个Attribute并不需要特别的知识,其实就和编写一个类差不多。自定义的Attribute必须直接或者间接地从Attribute这个类派生,如:

public MyCustomAttribute : Attribute { ... }

这里需要指出的是Attribute的命名规范,也就是你的Attribute的类名+"Attribute",当你的Attribute施加到一个程序的元素上的时候,编译器先查找你的Attribute的定义,如果没有找到,那么它就会查找“Attribute名称"+Attribute的定义。如果都没有找到,那么编译器就报错。

对于一个自定义的Attribute,你可以通过AttributeUsage的Attribute来限定你的Attribute 所施加的元素的类型。代码形式如下: [AttriubteUsage(参数设置)] public 自定义Attribute : Attribute { ... }

非常有意思的是,AttributeUsage本身也是一个Attribute,这是专门施加在Attribute类的Attribute. AttributeUsage自然也是从Attribute派生,它有一个带参数的构造器,这个参数是AttributeTargets的枚举类型。下面是AttributeTargets 的定义:

public enum AttributeTargets { 
   All=16383,
   Assembly=1,
   Module=2,
   Class=4,
   Struct=8,
   Enum=16,
   Constructor=32,
   Method=64,
   Property=128,
   Field=256,
   Event=512,
   Interface=1024,
   Parameter=2048,
   Delegate=4096,
   ReturnValue=8192
}

作为参数的AttributeTarges的值允许通过“或”操作来进行多个值得组合,如果你没有指定参数,那么默认参数就是All 。 AttributeUsage除了继承Attribute 的方法和属性之外,还定义了以下三个属性:

AllowMultiple: 读取或者设置这个属性,表示是否可以对一个程序元素施加多个Attribute 。

Inherited:读取或者设置这个属性,表示是否施加的Attribute 可以被派生类继承或者重载。

ValidOn: 读取或者设置这个属性,指明Attribute 可以被施加的元素的类型。

AttributeUsage 的使用例子:

using System; 
namespace AttTargsCS {
   // 该Attribute只对类有效.
   [AttributeUsage(AttributeTargets.Class)]
   public class ClassTargetAttribute : Attribute { }

   // 该Attribute只对方法有效.
   [AttributeUsage(AttributeTargets.Method)]
   public class MethodTargetAttribute : Attribute { }

   // 该Attribute只对构造器有效。
   [AttributeUsage(AttributeTargets.Constructor)]
   public class ConstructorTargetAttribute : Attribute { }

   // 该Attribute只对字段有效.
   [AttributeUsage(AttributeTargets.Field)]
   public class FieldTargetAttribute : Attribute { }

   // 该Attribute对类或者方法有效(组合).
   [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
   public class ClassMethodTargetAttribute : Attribute { }
   
   // 该Attribute对所有的元素有效.
   [AttributeUsage(AttributeTargets.All)]
   public class AllTargetsAttribute : Attribute { }
   
   //上面定义的Attribute施加到程序元素上的用法



   [ClassTarget] //施加到类
   [ClassMethodTarget]//施加到类
   [AllTargets] //施加到类
   public class TestClassAttribute {
      [ConstructorTarget] //施加到构造器
      [AllTargets] //施加到构造器
      TestClassAttribute() { }

      [MethodTarget] //施加到方法
      [ClassMethodTarget] //施加到方法
      [AllTargets] //施加到方法
      public void Method1() { }

      [FieldTarget] //施加到字段
      [AllTargets] //施加到字段
      public int myInt;

      static void Main(string[] args) { }
   }
}

 

至此,我们介绍了有关Attribute类和它们的代码格式。你一定想知道到底如何在你的应用程序中使用Attribute,如果仅仅是前面介绍的内容,还是不足以说明Attribute有什么实用价值的话,那么从后面的章节开始我们将介绍几个Attribute的不同用法,相信你一定会对Attribute有一个新的了解。

posted @ 7:30 PM | Feedback (0)

Thursday, May 18, 2006

(第五十二回)
  
  今天子龙讲了一个故事,说有人把一个人关在一间不透光的屋子里,在墙上挖一个小洞,把那人的胳膊拽出来,然后用刀子在他的手指头上划一小口,上面挂一盛满水的木桶,钻一小眼,让水缓慢地一滴一滴地落到下面的一个盆里。过了一夜,屋里的那个人便死掉了,面色苍白象是死于失血过多,但其实他手上的伤口只流了一滴血而已。

  我听完以后觉得有些毛骨悚然,试想一下,在一个黑咕隆咚的屋子里,听着外面滴答滴答的声音,感觉自己的血正在源源不断地流失,的确是件KB的事情。

  子龙说这是一种最阴险的杀人方法,因为你在尸体上几乎找不到任何痕迹。杀人者利用的是一种巧妙的心理暗示。

  说起心理暗示,不由得又想起曹操。当年曹操带兵去讨张绣时,正值夏天,骄阳当空,军士们个个都汗流浃背。走了几十里的山路,途中遍寻水源不见,所有人都口干舌燥,军士们怨声载道,有几个胆大的索性一屁股坐在地上不走了。眼瞅着军心涣散,曹操忽然心生一计,命人传令下去:当年我来过此地,前方不远处有一梅林。众军士一听,不由得精神大震,心里想着酸酸的梅子,哈喇子便流了出来。你要知道在你嗓子冒烟的时候咽一口唾沫是非常爽的。最终曹操的军队顺利地前进并找到了水源。

  后来人们把这件事叫做望梅止渴,这也是一个心理暗示的现象。虽然没有放血的那个精致巧妙,但却是信手拈来,实际上远比处心积虑杀人的那个高明得多。

  有一天,我说好了请魏延来家里喝酒,一大早我在客厅的茶几上放了一盘绿豆糕,然后坐在椅子上等客人上门。吃饭喝酒的事魏延总是很积极的,来了以后我安排魏延坐下,然后说,你先坐会儿,我到后面方便一下。我故意在后堂磨蹭了一会,出来时果然发现魏延正端着茶杯吃绿豆糕呢。于是连忙抢上前去作大惊失色状:哎呀,这绿豆糕是我命医师特制的,最近我有点便秘,于是他放了一些巴豆在里面。话说完不大工夫,就见魏延捂着肚子龇牙咧嘴地往后面跑,不到一盏茶的时间他去了六次茅厕。到后来我实在憋不住了,哈哈大笑地对他说了实话:那绿豆糕只是普通的绿豆糕,其实里面什么东西都没放。魏延傻愣愣地盯着我,脑袋摇得跟拨浪鼓似的:你就甭蒙我了,没放巴豆?没放巴豆我能拉那么稀?

  军师说,心理暗示其实是一把双刃剑,用好了便是动力,用得不好也就成了压力。在很多时候,我们都会受环境影响自己给自己一些心理暗示,也就是说,有很多事其实是自己想出来的。

 

(第五十三回)

  有天早晨我起的比较早,于是便独自去巡营。此时天色微明,军士们有的已经起床了,有的则还在酣睡。转了一圈,没发现什么异状,正想回去,忽然听左前方有人在大声地唱歌:小小姑娘,清早起床,迎着阳光上茅房~~~~那歌声嘹亮雄厚,余音袅袅,我不禁暗暗称奇,于是便顺着声音走过去。

  前面是一个简易的茅厕,声音就是从里面传出来的,我站在外面等了一会儿,见一人提着裤子从里面出来,不由得大吃一惊,见此人五短身材,面黄肌瘦,与刚才那歌声全然无法联系在一起,于是我就冷不防地问了一句:刚才那歌儿是你唱的吗?那人吃了一惊,转头一见是我,连忙立正,说道:是的,将军!他这一张嘴又把我吓一跳,我嗓门本身就够大的了,当年在长阪坡我差点把肺给喊出来。可眼前的这个小矮子比我嗓门还要大还有洪亮,我觉得有点意思,于是把他带回帐中。

  回到帐中,经过询问得知此人姓刘,家中排行老三,人称刘三。我问他:从小嗓门就这么大吗?刘三答道:本来也不怎么大,我小时侯赶上我们那里闹饥荒,家中没什么吃的,后来实在饿得不行了,我就偷偷地跑到田里去抓蛤蟆吃,吃了一个夏天,从那以后说话嗓门就大了,想小都小不了。

  我忍不住哈哈大笑,原来是吃蛤蟆吃的啊?怪不得呢,我以前见过一种小蛤蟆,叫起来跟牛似的,再看看面前的这个矮子,粗脖子,大嘴巴,趴鼻梁,两只眼睛向外突出,确实有点象蛤蟆。

  不管怎么说,嗓门大也是人才啊,我便把他留在身边做了个传令兵。不服不行,有时候我下一个十万火急的命令,他索性就站在军营中间仰脖那么一喊,方圆几里地的人都听得一清二楚。有一次军师来视察,见识了一下,也不禁惊为天人。回去跟大哥一说,大哥也想见识一下,便下旨把刘三调到城里去,做大哥的传令官,想不到这小子凭着一副大嗓门居然平步青云,周围的兵士们也都羡慕不已。

  本以为从此刘三就在城里吃香的喝辣的了,没成想不几天他便又被大哥派回来了。原来进城以后,大哥让刘三在早朝晚朝的时候喊一喊,早朝倒罢了,赶上晚朝,刘三站在殿前,双手叉腰这么一叫,就听得满城的犬吠鸡鸣,小孩啼哭不止,大人怨声载道。于是大哥赶紧把他给弄回来了。

  那年张郃来犯,我带兵与之相拒在瓦口隘,张郃凭据山险,闭门不出。一连几天攻不下来,我有点着急,晚上喝点酒后突然想了一个主意。第二天便把刘三找来,让他站在山下对着敌营叫骂,这刘三甫一开口,便震的遍山的鸟儿齐飞,冬眠的蛇都被惊醒了,纷纷爬出洞口张望。骂的词也是他自己编的,蛮有意思的。什么新一代的乌龟新一代的人新一代的王八不敢出门,还有什么小小张郃躲在茅厕迎着阳光流鼻血……

  如此骂了没俩时辰,张郃受不了了,命军士一起鼓噪,试图把刘三的声音给盖住,可他不知道刘三是谁啊?是吃蛤蟆长大的主儿啊,千军万马中刘三的声音直入云霄,清晰可闻。最后张郃终于坚持不住了,引一队人马出来厮杀,结果中了我的埋伏,大败而归。

  大哥后来论功封赏,我给刘三请了一大功。军师笑着说,所谓知人善用,翼德此番做得不错啊!

  其实我也不知道什么叫知人善用,我只知道尺有所长寸有所短,其实每个人都有自己的长处,关键看你怎么用和用到哪方面了。

(第五十四回)

  今天演习阵法的时候,有个军士的头盔不小心掉在地上,竟露出一头白发,而看他的相貌不过三十左右,于是众人都哄然而笑。

  军师也忍不住莞尔,说道:莫非阁下乃伍子胥转世?

  我听着伍子胥这个名字好耳熟,但却想不起他是干什么的,于是演习完了便去找子龙,却没想到竟从子龙那里听到了一种前所未闻的见解。

  伍子胥,楚国人,在历史上赫赫有名,是个顶天立地的角色。书中写他身长一丈,腰大十围,眉广一尺,目光如电,有扛鼎拔山之勇,经文纬武之才。在春秋战国时期,端的是一号人物,几乎所有的书中提起他来都是一片赞誉之词。

  按算命的说法,两眉之间的距离代表着一个人的气量,眉距越宽,心胸越宽广,反之则越狭窄。照此说法,伍子胥眉广一尺算是宰相肚里能撑船的主儿了。说到这里子龙话锋一转,然而就我分析,很多事实证明,伍子胥乃是一个不折不扣的小人,尤其是其心胸狭窄,简直令人发指。

  我忙问为什么,子龙给我讲道:

  伍子胥的父亲被当时的楚平王囚禁,其父手书一封令伍子胥哥俩前来面君,否则将被平王斩首。伍子胥认为去了必死,于是坚决不肯去,他的哥哥说,如果咱们俩不去的话,父亲肯定会死,这是父亲的亲笔书信,我一定要去,否则就是不孝。于是伍子胥与其兄断绝兄弟关系,自己一个人逃难去了。在离家之前,他觉得自己的妻子是个累赘,于是他把他的妻子给勒死了。

  军师今天所提到的是一个流传甚广的典故:伍子胥过韶关,一夜白头。说当年的伍子胥逃到韶关,城门处都悬挂着画像,过往行人一一盘查,伍子胥自认插翅难飞,于是一夜之间愁白了头。伍子胥堂堂一介丈夫,号称智勇双全,你可以想方设法用计谋过关,实在过不去,便是拼得一死,也要豪气凛然,居然会一夜白头?着实可笑。

  再往下走,伍子胥做的事就开始越发难以自圆了。出了韶关,前面有条大河,后面追兵已经赶到,此时有个渔翁用小船救了子胥一命,子胥过河后,怕渔翁泄露他的行踪,于是拔剑杀了渔翁。

  行至溧阳,伍子胥饥饿难耐,见一女子在河边洗衣服,于是上前讨食,那女子便给他吃了个饱,子胥这次也没有例外,依然将其抛尸河中,防止泄露踪迹。

  至于后来伍子胥终于利用两个刺客报了仇,这两个刺客在历史上也很有名,一个叫专诸,一个叫要离,其实不过是两个亡命徒,相当于他养的两条狗而已。专诸倒罢了,要离行刺的过程简直就是变态。他自知不是庆忌的对手,于是自己出了一个主意,要吴王把自己的妻子给杀了,然后斩断了自己的右手,以此换得庆忌的信任,最终在船上杀了庆忌,自己也不免一死。

  当然书上记载的都不是这样说的,他的妻子、渔翁、洗衣女子都是自杀的,他的妻子倒是有可能自杀,但后两个却纯属胡说八道,因为无论从哪个方面来讲都讲不过去。当年曹操与陈宫结伴出逃时,也曾在路上杀了两个人,于是我们都骂曹操心胸狭窄,而伍子胥则是正面人物,于是坏人都是他杀的,好人则都是为了他而自杀的。

  子龙越说越愤愤不平,都说历史是人民写的,但实际上大多数人民是不识字的!

  而我听着却有些糊涂起来,照子龙的说法,那很多前人记载下来的东西都不可信?

  于是子龙给我举了一个浅显易懂的例子,我觉得非常有趣。

  子龙是用射箭来打比方的。

  拿着弓箭比划一下,然后把箭给手下人,让他跑步到靶前插上。——这是历朝历代的皇帝。

  跑过去把箭插在靶心上。——这是历朝历代的功臣。

  跑错了方向或者跑过去插歪了。——这是历朝历代的庸臣。

  见人家快要把箭插上去的时候,在背后突施冷箭将其放倒。——这是历朝历代的奸臣。

  在自己亲戚射出的箭周围画一个圈,标明:靶心。——这是历朝历代的历史学家。

  在自己喜欢的人射出的箭上挂一个死兔子或者去了毛的鸡。——这是历朝历代的评论家。

  把评论家挂上去的兔子或鸡换成烤牛肉或者酱猪蹄。——这是历朝历代的文学家。

  那你是什么家呢?我忍不住笑着问子龙。
  
  我?子龙想了一下,说道:把所有的箭都拔了,然后让当事人当着我的面再射一次。——这就是我,一个梦想家。

(第五十五回)

  二哥死了。

  他的尸体躺在麦城的荒郊,而他的头则埋在洛阳城的南门。

  他的赤兔马被一个叫马忠的人骑着,他的青龙偃月刀被一个叫潘璋的人拿着。

  我最近一次见他是三个月以前,他一个人在荆州待了很久,我很想念他,于是星夜跑去见他,他表面上虽然不动声色,但我知道他见到我很开心。我走的时候他送我送了很远,我记得他说,三弟,咱们都老了。这世界已经不再是咱们的世界,这天下也不再是咱们的天下了。

  他说这话的时候,风吹着他的胡须,有些凌乱。

  大哥哭得晕过去好几次,我没有哭,我静坐了好几天,脑子里一片空白,什么都没想。周围的人都不敢靠近我,可能是我的脸色太可怕。
后来我饿了,于是找来东西吃,却发现连豆腐都咬不动了,原来这几日我竟然一直咬着牙。

  他们说二哥死后成了神,我不知道这世界上有没有神,我也不指望二哥的在天之灵能保佑我什么,倘若他真的活在另一个世界上的话,我只希望他能开心。

  晚上我一个人坐在帐外,抬头看着南方的星星,正值冬天,星星看起来很遥远,模模糊糊想起一句话,遥远的地方真的一无所有。寒风吹过来,四周的山有黑色的轮廓,隐约有狼的嚎声,我扯开衣襟,仰天长啸了一声,隔了很久,却没有回音。

  酒是好东西,他可以让我忘掉很多无法忘掉的事情。所有的悲伤和喜悦都被酒精所稀释,在半醉半醒之间我仿佛到了另一个世界。二哥在那里,坐着看书,见到我只是微微一笑,我喜极而泣,他轻轻地对我说,三弟,好想回去再看一眼家乡那桃花。

  我知道这是梦,但我希望我永远不要醒来。

  我看着大哥红肿的双眼以及两鬓那苍苍白发,突然觉得他很可怜,他比我更了解二哥,也比我更加悲痛。我不是鱼,因此我不知道鱼的快乐也不知道鱼的悲伤。

  大哥哭够了以后拍着桌子要去报仇,相反我却表现得很冷静。突然之间我对生死有了另一种看法,很多年前,我在锦屏山上遇到一个异人,道号紫虚上人,据说他能知人生死贵贱,于是我便去见识了一下,老道却只送了我一句话:生有何欢?死有何苦?直到今日我才领悟到这句话的含义,可惜已经晚了。

  回到军中,我把平日里打的最多的两员末将范疆、张达找来,命他们三日内备齐白旗白甲,否则满门抄斩,见二人面有难色,我便叫军士把他们绑在树上痛打了一顿。临走时我用眼角的余光清楚地看到他们的恨意。

  仇恨也是个好东西,它能促使人做出很多意想不到的事情。猛然间我想起了那个眼神跟锥子似的少年纪同,不知道为什么,他再也没来找过我,但我知道,只要我不死他不死,总有一天他会找上来的,忽然之间我很渴望他现在来。

  然而他终究没有来,来的是范疆、张达,我睁大了眼睛,据说如果刀快的话人临死时可以看到自己的心。刀不是很快,但很锋利,我清楚地感觉到了冰冷的刀锋没在骨肉里,象一条凉凉的蛇。血飞溅出来,在半空中竟似凝固了,在陷入黑色空间的一刹那,我清楚地看到了一树桃花,我知道,那就是家乡的那树桃花。

(后记)

  很多朋友看完我的流水帐后总要发一些感慨,有人说,真的好搞笑,笑得我肚子疼。还有一些人说,好悲伤,看得我心里酸酸的。

  其实我不知道是前一部分朋友的观点正确还是后一部分朋友的理解深刻,因为我写的时候并没有在一种特定的什么基调下进行。其实我一直把写字做为一种消遣,有东西憋着的时候,不吐出来是很难受的。因为没有条件写得华丽,尽量通顺便好了,前提是想法要真实,倘若能稍微的加点有趣,那我自己就很满意了。

  张飞流水帐在性质上其实应该归入无厘头一类,但又不是单纯的无厘头,里面夹杂了我对人生的一些思考。有人说,这里有王家卫的影子,又有人说,似乎有王小波的痕迹,但实际上我不姓王,我比他们少一横。由于当初它是一种连载的性质,又有历史背景的局限性,所以它其实比一般的随笔要难写一些。开始的几篇我有些信马由缰,后来我慢慢开始变得郑重起来。当然每一篇我都是很严肃地写出来的,写东西不是一件很轻松的事情,在此对所有在网上或在纸上从事认真创作的人们表示敬意和尊重。

  这世界上任何一样东西都会有人喜欢有人骂,每个人的审美观与出发点失之毫厘,对一件事物的看法可能会谬以千里。我们不会指望所有人的看法一致,世界大同那是Communist主义的事,可望不可及。但我们需要接受所有不同的声音,汇百流方成江河。在此对看了我的作品感到反胃的朋友说一声抱歉。

  在写这篇东西的时候,我经历了事业以及家庭上的种种挫折,我虽然一直是笑着面对这个世界的,但终于不能做到坦然。杜甫说,文章憎命达。马克吐温说,幽默的内在根源不是欢乐,而是悲哀;天堂里是没有幽默的。

  于是我用这两位中外名人的话来给自己戴一顶高帽,这看起来似乎有点阿Q。

  有一类电影注定不是用来娱乐大众的,就像有一些国家注定没有面目,有一些河流注定没有名字,有一些人注定只能张大嘴巴却发不出声音。

  这是一篇影评中的几句话,给我的触动很大。我总认为我们之中的大多数都属于张大嘴巴却发不出声音的人。

  我比较幸运,在朋友的不懈鼓励下写完了这个东西,发出了自己的一点声音。我知道这声音微不足道,但倘若能引起你的一点点共鸣,那便已经超出了我写这个东西的初衷。

  我总认为,世界上只有三种人,一种人开心,一种人不开心,另一种人不知道自己开心不开心。我希望所有看到这本书的读者朋友成为第一种人,不要成为第二种人,更不要成为第三种,跟我似的。

  最后的最后,送朋友们一句话,当你睁大眼睛却发现自己什么都看不到的时候,不要以为是自己瞎了,或许,前方真的是一无所有。

posted @ 6:51 AM | Feedback (0)

(第四十三回)
  
  今天士兵捉到一个魏国的奸细,大哥对此事非常重视,派我和魏延亲自去审。

  战争期间,捉到一个奸细很正常,为何此人还惊动了大哥呢?原来这个间细非同寻常,他自大哥进川就隐姓埋名藏进内务府,乔装成一个哑巴,每日里打扫卫生,干些杂活,因为见他是一个哑巴,所以我们有很多机密都当着他的面商议,因此此人知道我们的内情颇多,要不是有天夜里有打更的恰巧路过听到他在说梦话,估计我们至今也不会怀疑他。

  我和魏延得了命令,便去牢房提审,到了一看,见此人绑在柱子上奄奄一息,浑身上下已经没一个好地方了,各种刑具摆了一地,可这人端的是条硬汉子,至今只字未吐。我平生最欣赏硬骨头,要不是他的身份特殊,我还真想跟他交个朋友。

  边上的士兵过来低声道:将军,不能再打了,再打下去估计会死人的。

  我们当然不是来审死人的,这可如何是好呢?我和魏延大眼瞪小眼地看了半天。最后还是魏延想了一个主意,魏延的意思是他不是不怕死,他是知道他一说出来肯定是个死,不说的话可能还能有转机,咱们吓唬他一下试试。

  商量完了以后,魏延便去死牢里提了个犯人砍了,然后拖着半截血淋淋的大腿走进来,命士兵将那奸细用凉水泼醒,然后我故意问他:魏延,你拖的什么东西?魏延道:还不是上次吴国的那个奸细,上老虎凳时弄断了条胳膊,他央求说身体发肤受之父母,求我们帮他将断臂送回家乡,我见他可怜就照办了,谁知没几天他又断了一条腿,还让我给他捎回去,我回去想了一下这事不对头,他分明是有计划地想分批逃跑,你说对吧?于是我一生气就把他给杀了,喏,砍头去尾也就剩这么点东西了,幸亏我发现的及时,否则还真让他跑了。

  魏延边说边在那奸细周围晃来晃去,可说了半天,唾沫星子溅了那人一脸,那人居然眼皮都没眨一下,更别说开口了。这下我和魏延都傻眼了,还是去找军师帮忙吧。

  军师想了半天,要不你们用软的试试?试试用金钱美女等东西诱惑他一下。不行的话就再用硬的,然后再用软的,这叫打一巴掌给一个甜枣。抡圆了给一个大嘴巴,让他眼冒金星的时候给他嘴里塞一个甜枣,他一咂摸嘴,哎,还真甜,生活还真美好,鲜明的对比说不定他一下就招了呢。

  要不说军师有学问呢,这有学问的人就是不一样,平日里治国安邦的就不说了,连出个馊点子都这么专业。我和魏延连连点头称谢,然后照着军师的指示去忙活了。

  没成想到最后还是失败了,首先金钱这招没用,都拿黄金把他给埋起来了,他无动于衷。至于美女呢,更别提了,派了一个妓 女去试探了一下,此人竟是一个天阉,曹操可真会选人,所谓知人善用啊。

  眼看就快到大哥规定的时间了,把我和魏延急得跟热锅上的蚂蚁似的,正好马超路过,问清了状况以后,马超自告奋勇地说他去试试。死马权当活马医,让他去试一下也好。谁料马超进去以后只在那人耳边耳语了几句,那人面色大变,竟连连作揖讨饶,随后马超命人拿来纸笔,那人便一五一十地开始招供。

  马超摇头晃脑地走出来,我和魏延连忙迎上去,你在那人耳边说了些什么呀?马超哈哈一笑,原来他过去说:你要是再不招的话,我们就把你洗的白白净净的,穿的整整齐齐的,选一个阳光明媚视线良好的天,把你从大门送出去,临了儿还跟你挥手道别。

  说这个他怎么会害怕呢?他高兴还来不及呢。我越听越糊涂。马超解释道:你想啊,他宁死不招为了什么?还不就图一个名声吗?我们这样做,别人都会以为此人已经变节了,曹操眼线众多,肯定会知道的,而且以曹操的性格,定会派人追杀此人,其手段估计比我们的还要残忍。为了名节宁死不屈还值得,死了还落一个叛徒的名声,便是傻子也不愿意的。于是他权衡利弊就招了。

  我和魏延听到最后恍然大悟,齐齐冲马超伸出大拇指:高,实在是高!
  
(第四十四回)

  今天又去听二哥讲列国故事,二哥说,列国时代有很多人都跟禽兽一样,他举了两个例子。一个是吴起杀妻求将,说吴起当年在鲁国当差,当时齐国讨伐鲁国,吴起想做大将军带兵迎战,可鲁国国君嫌他妻子是齐国人,怕吴起到时候变节,吴起居然回家将自己的妻子杀死,拿着人头去见国君。另一个例子是易牙煮婴,易牙是齐桓公的一个厨子,做菜非常好吃,有一次齐桓公说:易牙做的饭太好吃了,只是还没有吃过易牙做的蒸婴儿肉。第二天,易牙就把自己的儿子蒸了端来给齐桓公吃。

  我听着听着突然想起一个人,这个人叫刘安。

  当日芒殇山兵败的时候,大哥也东奔西跑的,有一日到了一个村庄,投宿在一个猎户家中,这个猎户就是刘安。刘安见大哥来了,慌忙准备饭菜,端上一盆热气腾腾的肉,大哥当时已经饿了好几顿了,狼吞虎咽之后,随口问了一句:这是什么肉啊?刘安说是狼肉。等第二天大哥要走的时候,去后院牵马,忽然发现一个妇人躺在厨房里,已经死了,两个胳膊上的肉都被割了,于是惊问这是什么人,刘安老实交代说这是他妻子,昨晚吃的就是她的肉。

  大哥事后说他当时感动得哭了,可我觉得要是我的话首先得吐了。

  要说吴起杀妻是为了求将,易牙煮婴是为了讨好君主,按常理来看,刘安杀妻也是为了讨好大哥,以便取得点功名利益,但事实并非如此。

  这事或许只有我和孙乾知道。当日大哥终于遇到了曹操,把此事一说,曹操也很震惊,于是派孙乾拿了一百两黄金给刘安送去。

  很多年以后,有一天我忽然想起这个人,于是跑去找孙乾问起当时的情景。孙乾听我提起这个人,忽然有些紧张,我觉得不太对头,于是便连哄带吓,孙乾对我说了实话。

  原来当日孙乾来到那个村庄,恰巧刘安不在,据说上山打猎去了,跟村民随便聊起来,才知道刘安根本不是为了大哥而杀妻的。此人是个烂酒鬼,喝点酒就对妻子非打即骂,后来有次下手重了点,失手将妻子打死了,正不知所措的时候,大哥恰好来了,于是一不做二不休,索性割了妻子的肉来孝敬刘豫州。

  原来是这么回事啊,其实说起来失手打死妻子总比故意杀死要好一些,后来割肉就权当废物利用了。

  那后来呢?你见到这个人没有?

  后来?孙乾脸上又显示出扭捏的样子,后来……我就离开了,那一百两黄金我也自己留下了。

  啊?你贪污了?

  三将军,你有所不知,我虽然不才,可却生了八个孩子,加上老母妻妾,当时的那点俸禄根本不够用啊。

  八个孩子!他还真能折腾,瞧他那小干巴样还真有两下子。我临走的时候嘱咐孙乾:好好干,多赚点银子,否则小心哪天你家揭不开锅,就是把你煮了都不够你那八个孩子吃一顿的。
  
(第四十五回)

  那年我们在新野,大哥命我去征兵。

  战乱年代,征兵其实比较容易,有饭吃不说每个月还能领点俸禄,战死总比饿死强。

  但我征了三天却只征了几百个人,觉得很纳闷。新野虽然不大,按后来许庶的话来说,新野也就是个屁大的地方。当然他的话有些夸张,谁的屁股也不可能有那么大,但新野的确是个小地方。

  但再小也不应该就征这么点兵啊,当年我和大哥讨伐黄巾军时没钱没粮没名没望,还一天征了五百呢。我越想越奇怪,于是便走到街上看看到底是怎么回事。

  正走着,忽然一个乞丐引起了我的注意,大街上乞丐很多,但这个乞丐却和别的乞丐不同,一般的乞丐都是蓬头垢面衣衫褴褛的,他却白白胖胖的,穿的也很整洁,要命的是他的气质还非常好,往那一坐有种君临天下的气势,要不是他面前摆着一只破碗和身边的那根打狗棒,没有人会认为他是一个乞丐。

  我越看越纳闷,忍不住走过去,他见我来了却也不抬头,我掏出几文钱丢在碗里,他也不道谢,我越发觉得有意思。

  低下身来,问他:我给你钱你怎么不谢我呢?

  那人这才抬头看了我一眼,说道:钱是你的,你想给便给,我为什么要谢你呢?

  嘿,我忍不住有点恼火,于是伸手去碗里拿钱,嘴上说:那我现在不给你了,我再拿回来。

  那人一抬手将我拦住,说道:慢着,现在这碗里的钱是我的了,你若想要得经过我同意。

  我怒极反笑,哈哈,你这乞丐还真赖皮。算了,我也不跟你计较了。我索性也坐了下来。

  你好胳膊好腿的为什么不去当兵而做乞丐呢?坐下以后我问他。

  那人反问我:好胳膊好腿为什么就不能做乞丐而非要去当兵呢?

  他这么一问我倒愣了,当兵驰骋沙场,好男儿应当为社稷立汗马功劳,做乞丐低三下四的有何出息?

  那人接着问我:那你说这大街上为何有如此多的乞丐呢?

  我四顾了一下,向阳的墙角里坐满了形形色色的乞丐,想了一下答到:因为他们吃不上饭了呗。

  那他们为什么吃不上饭呢?
 
  我被问住了,本来我就不喜欢想问题,被他这么问来问去的我有点烦躁,反问他:那你说来看。

  那人忽然长叹一声:连年战乱,民不聊生,青壮年劳力都被征走了,大片的土地荒芜,剩下一些老弱病残只能去做乞丐。因为吃不上饭所以去当兵,而当兵的越多,就有越多的人吃不上饭。刘豫州也好,曹孟德也罢,袁绍、袁术、孙策、刘表,他们谁得了天下我都没意见,只希望你们越早越好。

  我愕然:照你这么说来,我们匡复汉室却跟曹贼谋权篡位没什么区别?

  那人说道:匡复汉室?当年高祖斩白蛇揭杆而起反的是秦,你若匡复为何不匡复秦室呢?

  我哑口无言,此言虽然大逆不道,却让我无从反驳。

  曹孟德挟天子以令诸侯,世人都骂他为国贼,我们打着匡复汉室的旗号讨伐他,却不知我们捍卫的汉室也是一个反贼建立的,那我们却真的是师出无名了。

  大哥真的跟曹孟德没什么区别吗?匡复汉室真的无足轻重吗?我坐在人来人往的大街上却听不到任何声音。
  
(第四十六回)
  
  二哥最引人注目一是他的红脸,再一个就是他的胡子。

  二哥的胡子足足有二尺长,而且特别顺,不跟我似的,乱蓬蓬的都找不到嘴。当年二哥在曹营的时候,上朝的时候皇帝看见了,忍不住赞了一句:真乃美髯公也!虽说当时的皇帝只不过是一个摆设,但毕竟也是皇帝,说的话都是金口玉言,于是美髯公这个称号不径而走,天下皆知。

  二哥对自己的胡子异常的珍爱,每日都要细心地梳洗理顺,一般人洗脸很快就完事了,二哥洗一次要半个时辰。每到冬天,他还要给胡子戴一个特制的口袋,两边有绳系在脖子后。因为冬天气候干冷,胡子特别容易掉。

  不过二哥由于名气大,走到哪里都有百姓夹道观看,更有顽童冲上来撕扯胡须,按说胡子掉几根没什么,可到了二哥这个年龄,当真是掉一根少一根了,于是二哥走到哪里一般都左有周仓右有关平,他们不是保护二哥,而是保护二哥的胡子。

  说起来也好笑,有一天吃饭,我看着二哥的胡子忽然想到一个问题,于是就问二哥:你睡觉的时候是把胡子放在被子里面还是外面啊?

  二哥愣了一下,歪着头想了半天,然后说:我还真没注意呢,你等我今晚回去留意一下明天告诉你。

  第二天一早,我发现二哥的眼睛红红的,我吓了一跳,连忙问他:怎么了?是不是嫂子死了?二哥白了我一眼:死了还省心呢,还不是怪你,昨天问的那个事,我晚上睡觉的时候就上了心,居然发现我把胡子放在被子里面也不舒服放在被子外面也不舒服,折腾了一晚上也没睡着。

  竟然有这种事?我忍不住哈哈大笑,正这时,军师来了,他听完以后也忍不住笑了,然后对二哥说:云长啊,我给你讲个故事,说有一个老和尚和一个小和尚一起过河,恰巧遇到一个女人也要过河,女人都是三寸小脚,过河不方便,于是小和尚就犹豫了,因为出家人是不近女色的,但出家人又要以慈悲为怀,正犹豫呢,忽见老和尚挽起裤腿背起女人就走。等过了河,一路上小和尚都在想着这件事,最后终于忍不住问道:师傅,刚才那个女人……老和尚微微一笑说了一句:我都已经放下了,你还放不下?

  军师讲完了以后我和二哥都有点纳闷,这跟胡子有什么关系?军师接着对二哥说:你睡不着是因为心里想着胡子的事,其实放在里面放在外面都是个习惯的问题,你这一追究,反而觉得不舒服了。你索性把它放下,今晚回去倒头就睡,什么都不要想,早晨起床时你再看胡子在外面还是里面。

  二哥连声称是,眉开眼笑地走了。
  
  晚上我抱着酒坛子即将入睡的那一刻,想着军师白天讲的那个故事,忽然脑子里灵光一闪明白了一件事:一个人最严重的病不是绝症,而是心病;一个人最大的敌人不是别人,而是自己。
  
(第四十七回)

  在我成年了以后,周围开始有很多人信一个人,家中供奉着他的名字:大贤良师张角。传说中张角得到神仙亲授的一本《太平要术》,能呼风唤雨撒豆成兵,每到一处便散施符水,可以治百病。在很短的时间里手底下聚集了十几万信徒,于是便开始造反,口号是“苍天已死,黄天当立”。个个头戴黄巾,称为黄巾军。打起仗来,个个身上贴满画符,口中念念有词,当真是勇猛无比。后来大哥和我们便是因破黄巾军而扬名天下的,这是我始料未及的。

  后来汉中出了个张鲁,他父亲据说也是个世外高人,平日里画点符,然后到处传教,凡入教者须交五斗米,于是朝廷称之为米贼。汉中号称鱼米之乡,果然富足,很快他的手下也有十几万人,不过他没造反,只是在汉中一带称王,朝廷也嫌路途艰辛没有派兵讨伐。等大哥进川以后,曹操出兵把张鲁给灭了。

  张角和张鲁在很多人眼中都是神一般的人,而朝廷却说他们是装神弄鬼。后来证明他们的确不是神,但为什么会有那么多的人追随他们呢?

  子龙说,要想成事必须找个借口,喝了一口水以后接着对我说:这么说吧三哥,你走在大街上肯定不是见到一个人就打吧?至少他踩了你一脚或者他瞪了你一眼。这跟造反的道理一样,老百姓吃不上饭必然要反,但一定要有个借口,比如张角的天平要术,比如张鲁的五斗米。

  听子龙这么一说,我忽然想起小时侯听大人说书,最常说的就是高祖斩白蛇起义的故事。说书的说高祖本来就不是凡人,而是一条龙,还是红颜色的龙。说他喝多酒的时候身上有龙显现出来,我怀疑那可能是青筋,不过人家说的煞有其事栩栩如生,还说什么他斩了白蛇以后,一个老太太当街而哭道:我儿白帝子,被赤帝子杀了。这摆明了真龙天子的意思嘛,不过后来他真的当了皇帝,便不是真龙也是天子了。

  按子龙的意思,这高祖当年斩白蛇也是个幌子了?忍不住又接着想了下去,如此说来,曹操的挟天子以令诸侯,孙权的世居江东,大哥的汉室宗亲,都只不过是他们拉拢人心的幌子?

  想到这里我隐隐有些不安。当年高祖起事成了,于是他被称为高祖,张角则是乱party。这便是军师经常说的成者为王败者为寇吧?

  那大哥会是王还是寇呢?这大概要等很多年以后才有人做结论吧?反正我是看不到了。很多人的一生都可以盖棺定论,但有些人则盖上去又被挖出来然后再盖上去。

  由此说来,其实做个普通人挺好的,至少死后很安宁。
  
(第四十八回)
  
  我总喜欢跟别人讲那个关于小草发芽的故事,因为总有人问我为什么脾气如此暴躁。可几乎没有人听完以后明白我的意思,或许是我的表达能力太差了。

  有时候我搞不懂人活在这世上的意义,更搞不懂人和人之间的关系。比如大哥和二哥。军师说,子非鱼,安之鱼之乐?可大哥又说,子非我,安知我不知鱼之乐?

  我既不是鱼,也不是大哥,因此我什么乐都不知道,我只知道当第三碗酒还剩三分之一的时候我仿佛成了仙。

  人活在这个世界上的目的就是寻找乐趣的。子龙对我说。

  可乐趣在哪里呢?除了喝酒,我到哪里去找乐趣?

  我看着子龙不辞辛苦地去山上采野花准备送给他新泡的妞;我看着魏延跟黄忠永不疲倦地斗嘴;我看着大哥和二哥相视而笑;我看着军师衣衫凌乱地被夫人推出门外;我看着马超面带微笑地与士兵聊天;我看着阿斗趴在地上观察蚂蚁;我看着张苞咧着嘴斗着蛐蛐。我突然发现我很寂寞。

  他们说寂寞是高手的一种境界,是那种天人合一举世皆浊我独清的境界。可我不是高手,但同样寂寞。一个人独处时的寂寞不可怕,可怕的是我在熙熙攘攘的人群中感到了寂寞。

  我信步来到街上,漫无目的地走着,不知不觉出了城,又走了一会儿,看到前面有座独木桥,桥中间站了两个人,一个背着一捆柴,腰里别着把斧子,看起来是个樵夫。另一个则挑着一副担子,看起来象是个挑夫。两个人就那么面对面站着,一动不动,谁也不让谁。

  我觉得有点意思,便找了块石头坐下来,看看到底是谁先认输。一柱香的时间过去了,两个人的脸上都见汗了。又过了一柱香,身子都有点摇晃了。正在这时,突然一个人一路小跑地赶过来,冲那个樵夫喊道:二郎,快回家,你媳妇生了!那樵夫听完以后身子没动,嘴上说道:不行啊,爹,我眼看就要赢了啊。却见后来的那个人走过去说:来,把柴给我,我替你背着继续,你赶紧回家看孩子去。这时那挑夫发话了:慢着,这不公平,你等着,我也回家叫我爹去。

  后来他们到底谁赢了我也没看,但着实让我的心情变得愉快了很多,快中午了,我得回去吃饭了。回到城里,听说大哥中午请客,连忙赶过去,见众人已经坐好了等着开饭了,于是我也找了个座位坐下。吃饭的时候,魏延伸手夹了一个鸡翅膀,不巧没夹住,掉在地上了,子龙在边上开口了:我说魏延,你喜欢吃鸡翅膀也用不着藏一块吧?你以为你藏在桌子下面我们就不跟你抢了?魏延愣了一下居然反应奇快:没看我用脚轻轻踩着呢?你们抢不去的!嘿嘿……于是满桌的人一起哈哈大笑。

  从饭桌上下来,我突然发现我的心情好得要命,于是明白,生活中总有一些乐趣等你去发现。你不可能每时每刻都快乐,但你可以努力把自己的心情调节到最接近快乐的那种状态。
  
(第四十九回)
  
  蜀中气候潮湿,一年内难得见到几次太阳,来之前听人说蜀中的狗见到太阳都会感到很奇怪,以为是什么怪物,不停地朝太阳狂叫。乍一听象是夸张,不过来了以后才知道确有其事。

  连着下了几天的雨,好容易盼到雨停,却只能隔着灰色的云看到一个模模糊糊的太阳,即使这样也很难得了。子龙来约我出去饮酒,据说城东新开了一个馆子,那里有道鱼头做得不错,于是我俩都换了便装,没骑马也没带随从,说着话溜溜达达地步行过去。

  快到了的时候,忽见一家门口晾了一床褥子,中间有一大片黄色的痕迹,想来是家中小孩尿床所致。我和子龙不禁相视一笑,走过去后子龙突然又返回去,站在那里又端详了一会,我觉得有点奇怪,却见子龙笑道:三哥,你过来看,这象不象西蜀地形图?我走近了仔细看了一下,忍不住哈哈大笑,果然很象!

  说起西蜀地形图来,忍不住要说起一个人,此人姓张名松,是个土生土长的成都人,当初在刘彰手下官居别驾。提到这个人总让我想起弥横,弥横是大脑袋细脖子长得挺吓人,张松是五短身材,尖嘴猴腮,獐头鼠目,不仔细看的话还以为是哪个马戏团里跑出来的猴子。当年我见弥横的时候想下马揍他一顿,而我第一次见到张松时真想朝他脸上踹一脚。

  就是这个张松,当年揣着一张西蜀地形图东奔西走,先到曹操那里准备把西川推销给曹操,结果差点让曹操给杀了,后来遇到了大哥,于是西川四十一州都归了大哥。

  说起张松见曹操跟弥横有点相似之处,弥横是裸衣击鼓骂曹操,张松没那么大的胆,但同样没给曹操好脸色。先是出言顶撞,后来曹操领他去看兵马演习,想借此震一震张松,没想到张松不以为然,整个演习过程都是斜着眼看下来的(他眼睛本来就不正,想不斜眼的话需要把脖子转好大的一个角度)。曹操有点恼火,吓唬张松道:我的大军所到之处,战无不胜,攻无不取;顺我者生,逆我者死。张松连连点头说:是啊,曹丞相战必胜,攻必取,我早就听说了。比如濮阳攻吕布之时,宛城战张绣之日;赤壁遇周郎,华容逢关羽;割须弃袍于潼关,夺船避箭于渭水。这都是无敌于天下的事啊。这下可把曹操给气坏了,因为曹孟德一生打过很多胜仗,但也有几次惨败,差点儿连命也丢了。张松列举
的,恰恰是曹操一生处境最狼狈的几次。当下就要把张松给砍了,幸亏杨修拦阻才暂时把张松的脑袋留在他的脖子上。

  弥横当年是持才傲物,张松虽说也有才,一目十行过目不忘,惊得曹操连他的孟德新书都烧了。但张松这么张狂却不仅仅是自持有才,更重要的是他怀里有张西蜀地形图啊,西川四十一州画得清清楚楚,有了它取西蜀不过是囊中取物,所谓奇货可居,因此张松目空一切。谁想到曹操也是个吃生肉的主儿,虽说当时曹操刚在赤壁被烧了个须眉皆无,但曹操自命丞相坐镇许都,视天下皆为囊中之物,想来是不会为了区区四十一州而低三下四地去巴结张松的。其实张松的许都之行很失败,表面上的任务是想让曹操帮忙攻打张鲁,暗地里的目的是想把西川献给曹操弄个官做做,到头来却是一无所获。要不是后来遇到了大哥,他还得揣着图灰溜溜地回去做他的别驾。

  按说现在我们坐在成都城里喝酒吃菜有张松的功劳,但实际上当年张松一出成都大哥就派人盯着他呢,他的一举一动都在大哥的掌握之中,所以很多看起来偶然的事情其实都是必然的。
  
很多人说当年曹操冷落张松的主要原因是厌恶他的长相,这或许只是推测,但不能不说人的仪表有时候的确很重要,比如刚才向我们推荐鱼头的那个店小二,如果他能把牙缝里的菜叶子和指甲里的黑泥清理一下,或许我们就不会换饭店了。

(第五十回)
  
  曹操前不久刚刚病死,这个人一生的故事太多,我不想一一诉说,反倒想提一提曹操的儿子们。

  曹操生性风流,大小老婆无数,因此儿子也颇多,有很多都默默无闻,不为人知,但出来混的几个却都天下闻名。

  曹操的长子叫曹昂,乃曹操的原配刘夫人所生,后由二房丁夫人养大成人。长得一表人才,虽无什么过人之处,却也中规中矩。由于是长子,所以理所当然地应该成为曹家王朝的继承人。可惜死的太早,他的死也比较冤。当年曹操南征张绣,绣不战而降,本来是件挺好的事,没想到曹操竟然看中了张绣的婶婶,强行拉回营中与之作乐,张绣大怒,在一个月黑风高的晚上偷袭曹营,如果不是曹昂把自己的马给了曹操的话,曹操早已是个死人了,而曹昂也被乱箭射死。当然说起来当时还有一个比曹昂更冤的,那就是曹操的贴身保膘典韦。主子在里面行乐,他在寒风中守夜,喝了点小酒,吃饭的家伙双铁戟竟然被人偷走了,弄了把单刀使不惯,最后没办法抓了两个尸体当双铁戟来用,最后被射得跟一只大刺猬似的。
  曹昂的死使得一人很高兴,这便是曹操的另一个儿子曹丕。因为曹昂死了曹丕便是长子。曹丕是个人才,聪明绝顶,见识过人,其文治武功十倍于曹昂,按说合理地成为继承人曹操应该很开心,但事实上却不是这样。因为曹操还有一个更加优秀的儿子,这便是赫赫有名的曹植。曹子建的文章名满天下,当年火烧赤壁之前,军师曾经拿着曹植的一篇文章去戏弄周瑜,里面有一句“揽二乔于东南兮,乐朝夕之与共”,军师说这里的二乔便指周瑜和孙策的老婆,把周瑜差点给气死。其实后来军师说这不是曹植的原文,原文是什么“连二桥于东西兮,若长空之锁殊”,不过军师也顺便说了一句,说曹子建的文章天马行空,有着空前绝后的想象力。

  由于曹丕和曹植都这么优秀,曹操欢喜之余还有点犯愁,因为只能选一个作为继承人,所谓鱼和熊掌不可兼得也。其实这个问题本来不是什么问题,倘若曹操最小的那个儿子不夭折的话。那个夭折的天才儿童叫曹冲,曹冲七岁称象,满朝皆惊!曹操一生中最喜欢的就是这个儿子,可惜天妒英才,曹冲十三岁便生一场重病死了。当时曹操痛不欲生,曹丕在旁边劝父亲节哀,曹操悲痛之余竟然说了这么一句:此吾之不幸,而汝之大幸也!意思也就是说,如果曹冲不死的话你的一切都是他的。

  上面说的几个基本都是文人才子,而曹操却还有一个学武的儿子曹彰。一脸黄须,气力惊人,人称“黄须儿”。这个家伙的确有俩下子,据说当年曾经手搏猛虎,最后拖着老虎的尾巴倒着走,老虎一点反抗的能力都没有。曹操对这个儿子也是很喜爱,当年在渭河遇到马超的时候,马超勇冠三军无人能敌,曹操忍不住想起了曹彰,说了一句:吾儿若在此,倒可以跟马超斗上几回。

  曹操在我眼中一直以来是一个坏人的形象,不过听说他的死讯,竟然忍不住有一些失落。之所以提到他的这些儿子,是想从一个侧面来描述一下曹操。人们常说,龙生龙凤生凤,老鼠的儿子会打洞。倘若只有一个儿子出类拔萃,或许是偶然,但曹操的儿子个个都如此优秀,仅从家教这方面来说,不由得让人对曹操肃然起敬。

  听手下人来报,说曹丕已经自立魏王,改建安二十五年为延康元年。忍不住有些感慨,曹操一生挟天子以令诸侯,却终未篡权,现下他尸骨未寒,他的后代已经称王称帝了。而子龙最近几天则一直在念叨曹操生前的一句话:设使天下无有孤,不知当几人称帝,几人称王?

(第五十一回)
  
  连着几天阴雨,道路泥泞,蜀道本来就难走,这下更不好走了。有一天我看到一个探子,四处找工匠做一副高跷,我觉得很奇怪,就过去问他,那探子愁眉苦脸地对我说:将军有所不知,现在那路上一脚下去能带起五斤泥,根本没法走,我估计踩个高跷能快一些。

  连轻装步行都这么难,更别说那些负责运输的了,粮草啊武器啊各种军需是进不来出不去。

  大哥拉长了脸摆弄着他那两只大耳朵,他郁闷的时候总是这个样子。连军师似乎也束手无策。平时只要大哥脸一长,军师便凑过去慢吞吞地来一句微臣有一计之类的话,然后大哥便眉开眼笑,而军师也一副踌躇满志的样子。但现在不行了,军师便是有天大的本领也无法把西蜀的山路都变成平路,把西蜀的泥道都铺上石板啊。

  不过军师就是军师,这世界上没有什么人什么事可以难倒军师,除了他老婆。军师找了一批工匠,画了一些图纸,命他们各自照样去做,几天以后,组装起来,大概是一头木牛的样子,用手一掰耳朵,便启动里面的机关,木头牛竟然能迈步走路!真是神奇啊!

  木牛做出来以后大伙儿纷纷来看,除了张大嘴巴赞叹之外没有什么别的表情。其中一个老木匠饭也不吃觉也不睡研究了三天三夜,最后说了一句:这简直比鲁板发明的锯子还要伟大啊!丞相真乃神人下凡呀!

  军师很得意,他这次得意的表情甚至比气死周瑜的那次都要明显,不过他的确值得得意,因为他发明了如此一件了不起的东西。

  晚上魏延陪我喝酒的时候突然冒出一句:三哥,你说咱也弄出点东西来给大伙瞧瞧,也在青史上留个名行不?我当时正晕晕忽忽的,听他这么一说,嘿,听起来似乎不错嘛。于是我们哥俩各自去忙活了。

  我本来就是个不愿意动脑子的人,最近这几天为了搞发明我把一辈子的脑子都用了,结果还是什么也没想出来。不搞不知道,做起来我才发现,能发明的东西几乎都已经被人发明了,没有我看不到的,只有我想不到的。这下把我给愁坏了,张苞见我如此伤脑筋,就也坐下来跟我一起想,唉,有其父必有其子啊,我也没指望他能想出点儿什么来,不过他这份孝心让我很安慰。

  这世上的事都没有绝对的,隔了几日,张苞满脸兴奋地来找我,对我说:爹,我发明出东西来了!你快来看!我将信将疑地被他拉到后花园,见张苞手里拿着一个形状奇怪的东西,有点象月牙,一头是把柄,一头很锋利。张苞给我解释说这是一种暗器,是人类历史上最伟大的暗器!它飞出去以后还能绕回来!真的假的?我越发地怀疑了,张苞说爹你退后我给你演示一下。说完他拉开架式,对准前面的一片野花扔了出去,但见那东西呼啸着飞过去斩落一朵野花之后真的转了一圈往回飞,我正惊讶之间,却听张苞一声惨叫,定睛一看,见那东西直直地扎在张苞的右肩膀上,血流如注。

  有了张苞的这次,我更加对发明东西灰了心,没想到魏延又颠颠地来找我,一进门没说话先喜笑颜开,双手放在背后神秘兮兮地对我说:三哥,我成功了!说完从背后取出一物朝我得意地晃着,仔细一看,一把黑色的雨伞,切,这也算发明?我一脸的不屑,却听魏延说道:三哥,这不是一把普通的雨伞!它能自动打开!你跟我出来试一下。我跟着魏延来到院子里,天气不错,阳光明媚的,魏延把伞举起来对着太阳,只听咯的一声果然自动打开了!我连声赞叹,真的不错啊,魏延你怎么弄的?魏延此时的表情象一只骄傲的公鸡,卖了半天关子才给我解释说是利用太阳的能量让伞自动打开的。

  送走了魏延,我有点郁闷,魏延的发明成功虽然是件高兴事,但也从一个方面证明了我的确比他蠢,坐在那里情绪有些低落,忽然脑子灵光一闪,想起一件事,魏延的伞只能在有太阳的时候用,可是有太阳的时候谁打雨伞啊?我拔腿想去告诉魏延,转念又一想,还是不告诉他了,让他美一阵子吧,反正早晚他也会发现的,他发明的不过是一件废物,想到这里忍不住哈哈大笑。

  晚上临睡觉前我想明白了,发明这种事是聪明人做的,这世界上有好多好多的聪明人,他们的脑子里有好多好多希奇古怪的想法,而象我们这种蠢人能做的就是好好享用他们的成果,这也算是对他们最大的肯定了吧。

posted @ 6:50 AM | Feedback (0)

(第三十一回)

  魏延新得了一匹马,样子很雄伟,他很得意的牵来向我显摆。我一直对马这种动物有好感,于是便借来溜溜。

  这马的脚力的确可以,我骑得起劲,不知不觉已经出了成都城,沿着官道跑了一会我顺势插到了一条小路上,往前跑了大概一柱香的时间,我勒住了缰绳,翻鞍下马,见那马呼吸均匀神态自若,不由得暗暗赞叹。牵着马往前走了几步,忽见前面树林之间露出一个屋角,于是便朝那儿走了过去。走近时发现是一个小道观。

  推门进去,真的是一间小道观,里面除了一张供桌之外几乎没有别的东西,甚至连个神像都没有,只一个牌位,上书“太上老君混元上德皇帝”几个大字,牌位前有个小香炉,里面连点香灰都没有,更别说香了。墙角到处都挂满了蜘蛛网,如果不是地上蹲着一个道士的话我还以为这是一座废弃了的道观呢。

  说到这个道士,着实有点奇怪,我自进门来他始终背对着我竟然没有回头,我忍不住走过去看他到底在做什么。走到他正面,发现他面前摆着一个小火炉,里面有几块红红的木炭,道士双手各持一串东西在火上面烤着,你猜他在烤什么东西?反正当时是吓了我一跳,他居然在烤大蒜!我见过烤羊的,烤鸡翅膀的,烤馒头片的,却从