B方案 - 基于通用元数据对象的使用手册

 

1. 基础使用

首先,先让我们来看一下要用到的元数据容器(Metadata Container)中的方法:

/// <summary>
///  获取一个指定key的属性值
/// </summary>
/// <param name="id">属性对应的Id值</param>
/// <returns>返回一个属性value</returns>
public virtual BaseValueStored GetAttribute(byte id)

/// <summary>
///  设置一个指定key的属性值
/// </summary>
/// <param name="id">设置属性对应的id</param>
/// <param name="baseValueStored">设置属性的value</param>
/// <exception cref="ArgumentNullException">baseValueStored不能为空</exception>
public virtual void SetAttribute(byte id, BaseValueStored baseValueStored)

/// <summary>
///  是否存在指定key的属性
/// </summary>
/// <param name="id">指定key</param>
/// <returns>是否移除</returns>
public virtual bool IsAttibuteExsits(byte id)

/// <summary>
///  移除指定key的属性
/// </summary>
/// <param name="id">指定key</param>
public virtual void RemoveAttribute(byte id)

 

通过上述定义我们就可以看出,其实在这个类型中我们主要就使用以下4种方法

  • 通过设置一个ID编号和一个ValueStored的类型对象来设置一个相关值到元数据对象
  • 通过一个已知的ID编号来获取一个ValueStored类型,即获取对象内部编号所对应的值
  • 检测一个指定的编号ID是否存在于元数据容器中
  • 从元数据容器中移除一个具有指定ID编号的数据插槽

 

       这其中大家可能会对BaseValueStored对象感到很迷惑,其实我们封装这个对象是为了统一数据
插槽的序列化/反序列化机制。请大家放心,我们的框架内部已经集成了所有.NET日常常用类型的扩展类
(包括数组)
。也就是说唯一牵扯到让使用者自己编写一个基于BaseValueStore派生类的机会,也就只有当一个
使用者想序列化自己自定义的类型(非智能对象)的时候。至于我们内部集成的那些派生类,使用者也无需记住,
因为当使用者尝试初始化一个派生自BaseValueStored的类型时,VS会弹出自动提示的,到那个时候使用者
只需要自己选择一个派生类即可。

      当我们需要往一个元数据容器中打入一个字符串的类型时,我们只需要制定一个这个数据插槽的ID编号,
并且调用相应方法即可,这看起来就像是如下的代码。

string test = "Test";
string test2 = "Test2";
//初始化元数据容器
MetadataContainer obj = new MetadataContainer();
//将一个字符串设置到容器中,并使用ID为1作为标示
obj.SetAttribute(1, new StringValueStored(test));
//将一个字符串设置到容器中,并使用ID为1作为标示
obj.SetAttribute(2, new StringValueStored(test2));

//读取一个ID为1的字符串
string t1 = obj.GetAttribute(1).GetValue<string>();
//读取一个ID为2的字符串
string t2 = obj.GetAttribute(2).GetValue<string>();

 

      看到了吧? 使用起来是非常方便的。但是这里有一个地方需要注意,那就是如果一个元数据容器中不存在
具有指定ID编号的值,那么返回的结果将会是null,这个时候我们就不能直接调用返回值对象(BaseValueStored)
对象的.GetValue<T>()方法了,因为这种情况下会产生空指针异常。在这种情况下,我们通常会建议大家使用
元数据容器中定义的一个新方法,方法定义如下:

/// <summary>
///  尝试获取一个指定key的属性值
///  <para>* 此方法不支持: T = 智能对象类型</para>
/// </summary>
/// <param name="id">属性对应的Id值</param>
/// <param name="value">输出的参数值</param>
/// <returns>如果当前对象内部不存在指定的key, 则返回false, 否则返回true.</returns>
public virtual bool TryGetAttributeAsType<T>(byte id, out T value)

 

       好了,我们刚刚说明了在基础使用层面上如何设置一个值到容器中,又讲了如何从容器中读取指定编号所
代表的值。那么我们现在来谈谈输出的格式化信息吧。我们在元数据容器内部重写了Object类型的ToString()
方法,以至于当我们调用元数据容器的ToString()方法时,能返回其内部的数据插槽详细信息,这看起来就像
是如下效果:

01: Test
02: Test2

       如果复杂一点的对象,返回的格式化输出字符串看起来就像如下效果:

01:
{
  01:
  {
    01:
    {
      NULL
      NULL
    }
    02:
    {
      0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00     ........
      0x0E, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00     ........
      0x00, 0x2E, 0x2E, 0x2E, 0x01, 0x00                 ......
    }
    03:
    {
      Kevin
      NULL
      Jee
    }
    04:
    {
      9988
      9999
    }
    05: 111111
    06: 222222
    07:
    {
      0x68, 0x61, 0x68, 0x61                             strstrstrstr
    }
    08: 2013/12/19 16:20:36
    09:
    {
      0x32, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00     2....'..
      0x00, 0xE4, 0xBD, 0xA0, 0xE8, 0xA7, 0x89, 0xE5     ........
      0xBE, 0x97, 0xE8, 0xBF, 0x99, 0xE6, 0xA0, 0xB7     ........
      0xE7, 0x9A, 0x84, 0xE8, 0x83, 0xBD, 0xE5, 0x8A     ........
      0x9B, 0xE5, 0x8F, 0xAF, 0xE4, 0xBB, 0xA5, 0xE4     ........
      0xBA, 0x86, 0xE5, 0x90, 0x97, 0xEF, 0xBC, 0x9F     ........
      0x01, 0x02                                         ..
    }
    0A: 10
    0C: 16
  }
}

       值得一提的是,如果想将在元数据容器这个方案里面使用智能对象,看起来的使用方式就如下的示例
代码:

MetadataContainer obj = new MetadataContainer();
//Test1为智能对象类型
Test1 test1 = new Test1{ServicelId = 1,ProtocolId = 2};         
//将一个智能对象类型设置到元数据容器中
obj.SetAttribute(1, new IntellectObjectValueStored(IntellectObjectHelper.BindIntellectObject(test1)));
//从元数据容器中读取一个具有指定ID的智能对象
Test1 test2 =  IntellectObjectHelper.GetAttributeAsIntellectObject<Test1>((IntellectObjectValueStored) obj.GetAttribute(1));

 

不参与序列化传输的值类型默认值:

*我们的B方案中依旧拥有"值类型默认值不参与序列化传输"的功能,所以我们为您准备了我们的框架中
  所定义的值类型默认值列表。
*引用类型/可空值类型(Nullable ValueType)以及不包含在以下列表中的值类型。 

Type                Default Value                     
bool false
char '\0'
byte 0
sbyte 0
decimal 0
short 0
ushort 0
int 0
uint 0
float 0
long 0
ulong 0
double 0
DateTime DateTime.MinValue
IntPtr IntPtr.Zero
Guid Guid.Empty
TimeSpan new TimeSpan(0, 0, 0)

 

2. 高级使用


       如果使用者需要定制化一个自定义类型的数据插槽(自定义对象的序列化/反序列化机制),那该如何去做呢?
其实这部分也很简单。使用者只需要很简单的创建一个派生自BaseValueStored的类型,并编写内部的几个抽象
方法即可。我们将会在下面给出一个官方的例子,在这个例子中我们系统内部自定义了一个名叫MessageIdentity
的类型(此类型不是智能对象类型),我们需要为这个类型自己创建一个数据插槽。

PS: 其实这个类型不用自己写数据插槽,只需要派生自智能对象类型就可以了,但是我们为什么还需要这样去做
     呢?其实在我们的系统中往往存在着这种对象的定义,我们希望这种对象在序列化的结果方面能够充分的短
     小精悍,如果派生自智能对象类型,那么在产生的序列化结果后,这个结果内部可能会增加一些字节数量,
     用于系统内部的解析方式和相关标记,这自然也就违背了我们当初的意愿,所以我们现在需要自己实现一个
     关于这种类型的数据插槽,以来满足我们的特殊需求。

官方示例代码:

using System;
using KJFramework.Messages.Contracts;
using KJFramework.Messages.Proxies;
using KJFramework.Messages.Types;
using KJFramework.Messages.ValueStored;
using KJFramework.Messages.ValueStored.StoredHelper;
using KJFramework.Net.Transaction.Identities;

namespace KJFramework.Net.Transaction.ValueStored
{
    /// <summary>
    ///     MessageIdentity类型的存储
    /// </summary>
    public class MessageIdentityValueStored : BaseValueStored
    {
        #region Members

        private readonly MessageIdentity _value;
        private static readonly PropertyValueStored<MessageIdentity> _instance;
        private static readonly Action<IMemorySegmentProxy, BaseValueStored> _toBytesDelegate;
        private static readonly Action<ResourceBlock, byte, byte[], int, uint> _toDataDelegate;

        #endregion

        #region Constructors

        /// <summary>
        ///     默认TypeId的构造函数
        /// </summary>
        public MessageIdentityValueStored()
        {
            _typeId = 255;
            IsExtension = true;
        }

        /// <summary>
        ///     MessageIdentity类型存储的初始化构造器
        /// </summary>
        public MessageIdentityValueStored(MessageIdentity value)
        {
            _value = value;
            _typeId = 255;
            IsExtension = true;
        }

        /// <summary>
        ///     MessageIdentity类型存储的静态构造器
        /// </summary>
        static MessageIdentityValueStored()
        {
            _instance = ValueStoredHelper.BuildMethod<MessageIdentity>();
            _toBytesDelegate = delegate(IMemorySegmentProxy proxy, BaseValueStored messageIdentityValueStored)
            {
                proxy.WriteByte(messageIdentityValueStored.GetValue<MessageIdentity>().ProtocolId);
                proxy.WriteByte(messageIdentityValueStored.GetValue<MessageIdentity>().ServiceId);
                proxy.WriteByte(messageIdentityValueStored.GetValue<MessageIdentity>().DetailsId);
                proxy.WriteInt16(messageIdentityValueStored.GetValue<MessageIdentity>().Tid);
            };
            _toDataDelegate = delegate(ResourceBlock metadataObject, byte id, byte[] byteData, int offsetStart, uint offsetLength)
            {
                MessageIdentity messageIdentity = new MessageIdentity
                {
                    ProtocolId = byteData[offsetStart++],
                    ServiceId = byteData[offsetStart++],
                    DetailsId = byteData[offsetStart++],
                    Tid = BitConverter.ToInt16(byteData, offsetStart)
                };
                metadataObject.SetAttribute(id, new MessageIdentityValueStored(messageIdentity));
            };
        }

        #endregion

        #region Methods

        /// <summary>
        ///     获取存储的对应类型的Value值
        /// </summary>
        public override T GetValue<T>()
        {
            return _instance.Get<T>(_value);
        }

        /// <summary>
        ///   扩展类型序列化方法
        /// </summary>
        /// <param name="proxy">内存代理器</param>
        public override void ToBytes(IMemorySegmentProxy proxy)
        {
            _toBytesDelegate(proxy, this);
        }

        /// <summary>
        ///   扩展类型反序列化方法
        /// </summary>
        /// <param name="metadataObject">元数据集合</param>
        /// <param name="id">属性对应key</param>
        /// <param name="data">属性对应byte数组</param>
        /// <param name="offsetStart">属性在数组中的偏移值</param>
        /// <param name="length">属性在byte数组中的长度</param>
        public override void ToData(MetadataContainer metadataObject, byte id, byte[] data, int offsetStart, uint length)
        {
            _toDataDelegate(metadataObject, id, data, offsetStart, length);
        }

        /// <summary>
        ///   返回一个实例对象
        /// </summary>
        public override object Clone()
        {
            return new MessageIdentityValueStored();
        }

        public override string ToString()
        {
            return string.Format("(P: {0}, S: {1}, D: {2}, T: {3})", _value.ProtocolId, _value.ServiceId,
                                 _value.DetailsId, _value.Tid);
        }

        #endregion
    }
}

 

       在上述示例中唯一需要说明的是,BaseValueStored类型内部会用一个byte类型的变量“_typeId
来表明这是个什么类型的对象,这个_typeId字段仅仅是个ID的标示,在上述例子中用的是255,如果你
的系统中存在多个这种需要自己实现数据插槽的类型,请递减这个数即可,比如下一个类型的_typeId
254

       当我们完成了自定义类型的数据插槽后,只差一步就可以大功告成了,那就是将这个自定义的数据
插槽类型注册到这套框架中。请使用如下代码进行注册:

ExtensionTypeMapping.Regist(typeof(MessageIdentityValueStored));

 

 

3. 性能测试


       B方案由于在内部序列化/反序列化的机制上比A方案有着本质上的巨大优势,所以B方案的性能会
大大的超过A方案的性能指标
。具体如下:

 

测试平台:
CPU: Intel(R)Xeon(R)CPU X5670 @2.93GHz @2.93GHz (2处理器)
System: Windows Server 2008 R2 Enterprise

 

序列化复杂对象(DEBUG)

    .次数 100000:  450(ms)  *此值根据测试机器的配置不同而不同,仅供参考
    .Gen0回收次数: 32
    .Gen1回收次数: 0
    .Gen2回收次数: 0

反序列化复杂对象(DEBUG)

    .次数 100000:  397(ms)   *此值根据测试机器的配置不同而不同,仅供参考
    .Gen0回收次数: 20
    .Gen1回收次数: 1
    .Gen2回收次数: 0

 

序列化复杂对象(RELEASE)

    .次数 100000:  375(ms)  *此值根据测试机器的配置不同而不同,仅供参考
    .Gen0回收次数: 26
    .Gen1回收次数: 0
    .Gen2回收次数: 0

反序列化复杂对象(RELEASE)

    .次数 100000:  330(ms)   *此值根据测试机器的配置不同而不同,仅供参考
    .Gen0回收次数: 20
    .Gen1回收次数: 1
    .Gen2回收次数: 0

Last edited Dec 20, 2013 at 4:34 AM by g0194776, version 55