永利国际源码下
地址一:【hubawl.com】狐霸源码论坛
地址二:【bbscherry.com】
由于采用字典的方式来保存属性变更值的底层设计思想,导致了性能问题,虽然.NET的字典实现已经很高效了,但相对于直接读写字段的方式而言依然有巨大的性能差距,同时也会导致对属性的读写过程中产生不必要的装箱和拆箱。
那么这次我们就来彻底解决这个问题,同时还要解决“哪些属性发生过变更”、“获取变更的属性集”这些功能特性,所以我们先把接口定义出来,以便后续问题讲解。
/ 源码位于 Zongsoft.CoreLibary 项目的 Zongsoft.Data 命名空间中 /
/// <summary> 表示数据实体的接口。</summary>
public interface IEntity
{
/// <summary>
/// 判断指定的属性或任意属性是否被变更过。
/// </summary>
/// <param name="names">指定要判断的属性名数组,如果为空(null)或空数组则表示判断任意属性。</param>
/// <returns>
/// <para>如果指定的<paramref name="names"/>参数有值,当只有参数中指定的属性发生过更改则返回真(True),否则返回假(False);</para>
/// <para>如果指定的<paramref name="names"/>参数为空(null)或空数组,当实体中任意属性发生过更改则返回真(True),否则返回假(False)。</para>
/// </returns>
bool HasChanges(params string[] names);
/// <summary>
/// 获取实体中发生过变更的属性集。
/// </summary>
/// <returns>如果实体没有属性发生过变更,则返回空(null),否则返回被变更过的属性键值对。</returns>
IDictionary<string, object> GetChanges();
/// <summary>
/// 尝试获取指定名称的属性变更后的值。
/// </summary>
/// <param name="name">指定要获取的属性名。</param>
/// <param name="value">输出参数,指定属性名对应的变更后的值。</param>
/// <returns>如果指定名称的属性是存在的并且发生过变更,则返回真(True),否则返回假(False)。</returns>
/// <remarks>注意:即使指定名称的属性是存在的,但只要其值未被更改过,也会返回假(False)。</remarks>
bool TryGetValue(string name, out object value);
/// <summary>
/// 尝试设置指定名称的属性值。
/// </summary>
/// <param name="name">指定要设置的属性名。</param>
/// <param name="value">指定要设置的属性值。</param>
/// <returns>如果指定名称的属性是存在的并且可写入,则返回真(True),否则返回假(False)。</returns>
bool TrySetValue(string name, object value);
}
设计思想
根本要点是取消用字典来保存属性值回归到字段方式,只有这样才能确保性能,关键问题是如何在写入字段值的时候,标记对应的属性发生过变更的呢?应用布隆过滤器(Bloom Filter)算法的思路来处理这个应用场景是一个完美的解决方案,因为布隆过滤器的空间效率和查询效率极高,而它的缺点在此恰好可以针对性的优化掉。
将每个属性映射到一个整型数(byte/ushort/uint/ulong)的某个比特位(bit),如果发生过变更则将该 bit 置为 1,只要确保属性与二进制位顺序是确定的即可,算法复杂度是O(1)常量,并且比特位操作的效率也是极高的。
实现示范
有了算法,我们写一个简单范例来感受下:
public class Person : IEntity
{
#region 静态字段
private static readonly string[] NAMES = new string[] { "Name", "Gender", "Birthdate" };
private static readonly Dictionary<string, PropertyToken<Person>> TOKENS = new Dictionary<string, PropertyToken<Person>>()
{
{ "Name", new PropertyToken<Person>(0, target => target._name, (target, value) => target.Name = (string) value) },
{ "Gender", new PropertyToken<Person>(1, target => target._gender, (target, value) => target.Gender = (Gender?) value) },
{ "Birthdate", new PropertyToken<Person>(2, target => target._birthdate, (target, value) => target.Birthdate = (DateTime) value) },
};
#endregion
#region 标记变量
private byte _MASK_;
#endregion
#region 成员字段
private string _name;
private bool? _gender;
private DateTime _birthdate;
#endregion
#region 公共属性
public string Name
{
get => _name;
set
{
_name = value;
_MASK_ |= 1;
}
}
public bool? Gender
{
get => _gender;
set
{
_gender = value;
_MASK_ |= 2;
}
}
public DateTime Birthdate
{
get => _birthdate;
set
{
_birthdate = value;
_MASK_ |= 4;
}
}
#endregion
#region 接口实现
public bool HasChanges(string[] names)
{
PropertyToken<Person> property;
if(names == null || names.Length == 0)
return _MASK_ != 0;
for(var i = 0; i < names.Length; i++)
{
if(__TOKENS__.TryGetValue(names[i], out property) && (_MASK_ >> property.Ordinal & 1) == 1)
return true;
}
return false;
}
public IDictionary<string, object> GetChanges()
{
if(_MASK_ == 0)
return null;
var dictionary = new Dictionary<string, object>(__NAMES__.Length);
for(int i = 0; i < __NAMES__.Length; i++)
{
if((_MASK_ >> i & 1) == 1)
dictionary[__NAMES__[i]] = __TOKENS__[__NAMES__[i]].Getter(this);
}
return dictionary;
}
public bool TryGetValue(string name, out object value)
{
value = null;
if(__TOKENS__.TryGetValue(name, out var property) && (_MASK_ >> property.Ordinal & 1) == 1)
{
value = property.Getter(this);
return true;
}
return false;
}
public bool TrySetValue(string name, object value)
{
if(__TOKENS__.TryGetValue(name, out var property))
{
property.Setter(this, value);
return true;
}
return false;
}
#endregion
}
// 辅助结构
public struct PropertyToken<T>
{
public PropertyToken(int ordinal, Func<T, object> getter, Action<T, object> setter)
{
this.Ordinal = ordinal;
this.Getter = getter;
this.Setter = setter;
}
public readonly int Ordinal;
public readonly Func<T, object> Getter;
public readonly Action<T, object> Setter;
}
上面实现代码,主要有以下几个要点:
属性设置器中除了对字段赋值外,多了一个位或赋值操作(这是一句非常低成本的代码);
需要一个额外的整型数的实例字段 MASK ,来标记对应更改属性序号;
分别增加 NAMES 和 TOKENS 两个静态只读变量,来保存实体类的元数据,以便更高效的实现 IEntity接口方法。
根据代码可分析出其理论执行性能与原生实现基本一致,内存消耗只多了一个字节(如果可写属性数量小于9),由于 NAMES 和 TOKENS 是静态变量,因此不占用实例空间,理论上该方案的整体效率非常高。
原文地址:http://blog.51cto.com/13880620/2148010