GetHashCode函数,看了它的名字就知道它会被用在哪里。没错,这个函数一般是在操作HashTable或者Dictionary之类的数据集的时候被调用。每个类型,不管是值类型还是引用类型,都提供这个基本函数,同样也可以像重写ToString或者Equals函数一样去重写它。但是我这里要说的,不建议重写此函数,而且在使用这个函数也需要加倍小心。 Why? 有些人看了我所说的,会产生类似的疑问。我这里要提的一点就是,对于引用类型自带的GetHashCode函数来说,基本上是正确的,但是效率不高;而对于值类型自带的GetHashCode函数而言,基本上是不正确的,即使正确也是效率不高。如果重写类型的GetHashCode函数,想要达到既正确又高效是不可能的。 为了解开如上的疑问之前,先来说说实现一个类型的GetHashCode函数,需要满足那几点才能是合格的。显然GetHashCode的目的是产生一个Key,为了方便在HashTable或者Dictionary中的检索。既然是这样,那么对于GetHashCode来说,要满足如下三点,这也是判断一个GetHashCode函数是否有效的标准。 第一,两个相等的对象,通过GetHashCode函数产生的结果要相等,此外两个不相等的对象,通过GetHashCode函数的返回值要不相等;否则,通过其产生HashCode而存入HashTable中的数据就无法取出来了。 第二,对于一个类型的对象来说,其GetHashCode函数的返回值要自始至终要保持一致。否则,和第一点一样。 第三,在GetHashCode函数中需要提供一个比较好的哈希函数,也就是在最小的范围内来实现数据分散,换句话说它的离散度决定HashTable存取效率。 知道了GetHashCode函数的验证标准,接下来就来解开前面的疑团。 首先说说引用类型自带的GetHashCode函数实现。一个.Net程序在运行的时候会对引用类型的对象进行标记,大致操作类似如下: 标记起始为0,当创建一个引用类型对象的时候,这个标记会自动加一,对象释放后标记并不做减一操作,这有点儿像数据库中的自增字段。 那么对于引用类型的GetHashCode其实就是返回当前引用标记。这也就是为什么说引用类型的GetHashCode函数基本是正确的原因。 因为对于第一条来说,如果类型没有重载Equals或者Operator==函数的话,类型自带的Equals函数只是在对象引用层进行验证,也就是说,一个对象等于另外一个对象就说明这个对象要么是另外一个对象的引用,要么另外一个对象是这个对象的引用。这说明没有新的引用对象产生,那么当引用标记也不会发生变化,所以对于第一条来说满足(如果要是重载了Equals或者Operator==函数的话,那么相应要提供此版本的GetHashCode函数,这一点在后面进行叙说)。 至于第二条来说,由于对象数据成员发生改变不会影响到引用标记的改变,所以对于第二条来说也是满足的。 前两点的满足,说明了引用类型的GetHashCode是正确地。但是对于第三点来说,由于引用标记是相对于整个程序而言的,并不是类型所特有的,那么它的效率不高是不言而喻的。 那么对于值类型自带的GetHashCode函数呢,就更有趣了,为了更形象地说明它的有趣,请先参看如下的代码,猜猜Debug的输出是什么。 public struct ErrorMessage { private string strMsg; private int nErrorCode; private DateTime dtInvoked; public ErrorMessage( string Msg, int ErrorCode ) { strMsg = Msg; nErrorCode = ErrorCode; dtInvoked = DateTime.Now; } public bool TestHashCode() { return this.GetHashCode() == strMsg.GetHashCode(); } } // Test "GetHashCode" function in value type ErrorMessage err = new ErrorMessage( "Test", 0 ); if( err.TestHashCode() ) Debug.WriteLine( "Both hash code equal!" ); else Debug.WriteLine( "Not equal!" ); 可能谁都没有想到,Debug中的输出是“Both hash code equal!”。为什么呢?原因很简单,值类型自带的GetHashCode是以其第一个成员的GetHashCode值作为其的返回值。 显然对于第一条来说,两个相等值类型对象,其的GetHashCode函数返回值是相等的,这没什么问题;但是对于不相等的两个对象来说,它们的GetHashCode返回值则有可能相等。显然违反了第一条。 其次对于第二条来说,由于值类型的GetHashCode返回值等于其第一个成员的GetHashCode函数值,那么修改了第一个成员的值,也就间接的修改了对象的GetHashCode值,从而对于一致性来说也是不满足的。 对于一二两条都不满足,去谈第三条是没有意义的,不过就函数本身来说,效率也和引用类型基本一样,没有采用特殊的算法,所以想得到比较好的效率也是不可能的。 经过逐个分析,再来重复一下我前面所说的。对于引用类型的GetHashCode函数来说,基本上是正确的,但是效率不高;而对于值类型而言,基本上是不正确的,而且效率也是不理想的。 那有人就说了,程序会要用到HashTable去存,必然会用到类型的GetHashCode函数,如何避免如上的错误,或者说提供一个比较正确的GetHashCode函数呢。那么接下来就分别说说如何去实现(这里所说的实现主要满足前两条即可,最后一条牵扯到Hash函数算法,这里不做讨论)。 这次先说说值类型,因为值类型本身提供的基本不正确。如果不想做过多处理,毕竟提供一个好的哈希函数不容易,那么从值类型的GetHashCode规律出发,即从类型自身元素出发。对于一个值类型,如果其本身存在某个数据可以唯一标明此类型对象,有点儿像数据库中的Key字段,那么用它作为类型的第一个元素。例如就前面所说的ErrorMessage来说,dtInvoked成员可以唯一表示这个类型数据,那么就可以如下修改。 public struct ErrorMessage { private DateTime dtInvoked; private string strMsg; private int nErrorCode; } 这样就满足了验证第一条,对于第二条,就是要保证这个类型的对象通过GetHashCode能自始至终一样,就要防止第一个成员被修改,比较好的做法就是给它加上readonly标示,那么比较完整的样式应该如下。 public struct ErrorMessage { private readonly DateTime dtInvoked; private string strMsg; private int nErrorCode; } 这样对于ErrorMessage类型的GetHashCode至少是正确的。有人说了,如果定义的类型没有一个单独成员能作为唯一标示,那我就建议你不要把这种类型的数据来产生Key。 接下来说说对于引用类型的GetHashCode函数改写。对于第一条来说,在引用类型中有可能重新编写Equals函数,那么类型自带的GetHashCode函数将不能适应这个要求,需要进行重写来适应这种改变。如何简便的改写GetHashCode函数而达到效果呢,这里可以延用前面值类型的做法,即选择一个能唯一标示这个对象的成员来生成HashCode,同时要避免这个成员被修改。 例如一个比较合理的引用类型的GetHashCode函数大致如下(此例引用于原书): public class Customer { private readonly string _name; private decimal _revenue; public Customer( string name ): this( name, 0 ) { } public Customer( string name, decimal revenue ) { _name = name; _revenue = revenue; } /// /// Name property which only can be accessed in reading mode /// public string Name { get{ return _name;} } /// /// Create a new object with new name /// /// /// public Customer ChangeName( string newName ) { return new Customer( newName, _revenue ); } /// /// Customer hash code generated by name /// /// public override int GetHashCode() { return _name.GetHashCode (); } } 对于Customer类型对象来说,它的HashCode是由其_name成员所决定的,所以不能轻易改变,如果通过调用ChangeName方法来替换原先的对象的时候,要首先操作HashTable,先把原先的删除,创建新的之后再保存。具体如下: Customer c1 = new Customer( "test1" ); object orders = new object(); myHashTable.Add( c1, orders ); //Change name Customer c2 = c1.ChangeName( "test2" ); object o = myHashTable[ c1 ]; myHashTable.Remove( c1 ); myHashTable.Add( c2, o ); 对于如上中Custemer对象来说,只是为了产生在HashTable中所存对象的HashCode,当然在实际应用中,两者需要关联,否则使用HashTable存这些数据就没有任何意义了。 这样对于值类型和引用类型的GetHashCode改写到此基本已经结束了,显然如上的改写,只是为了保证类型的GetHashCode正确,但是对于其的效率并没有得到长足的进步,或者换句话来说,改写后的GetHashCode函数仍然保留HashTable使用效率不高。如何在GetHashCode函数使用比较好的哈希函数,使产生的HashCode具有比较好的分布,我在此不对它进行讨论,因为光这个问题就足够写好几本书的。 对于GetHashCode函数,大致就说到这儿,最后为了加深记忆,总结一下。 首先,在不重写此函数的情况下,这里主要说说使用当中应该注意的。 1. 不建议使用值类型对象的GetHashCode函数返回值来作为HashTable对象的Key; 2. 引用类型是可以使用的,但是要注意如果重写了Equals函数,一定要重写GetHashCode函数来达到一致; 再说说重写此函数时需要注意的。 1. 不管是值类型还是引用类型,要保证产生HashCode的成员不能被修改; 2. 对于产生HashCode的成员修改,要以产生新对象进行处理,同时要在使用端作相应的修改,即先删除旧的在添加新的。
posted @ 2012-05-25 00:46 xiangxiong 阅读(8) 评论(0) 编辑

    

你的类型应该有一个顺序关系,以便在集合中描述它们如何存储以及排 序。.Net框架为你提供了两个接口来描述对象的顺序关系:IComparable 和 IComparer。IComparable 为你的类定义了自然顺序,而实现IComparer接口的类 可以描述其它可选的顺序。你可以在实现接口时,定义并实现你自己关系操作符 (<,>,<=,>=),用于避免在运行时默认比较关系的低效问题。这 一原则将讨论如何实现顺序关系,以便.Net框架的核心可以通过你定义的接口对 你的类型进行排序。这样用户可以在些操作上得更好的效率。

IComparable接口只有一个方法:CompareTo(),这个方法沿用了传统的C 函数库里的strcmp函数的实现原则:如果当前对象比目标对象小,它的返回值小 于0;如果相等就返回0;如果当前对象比目标对象大,返回值就大于0。 IComparable以System.Object做为参数,因此在使用这个函数时,你须要对运行 时的对象进行检测。每次进行比较时,你必须重新解释参数的类型:

public struct Customer : IComparable
{
  private readonly string _name;
 public Customer( string name ) 
 {
  _name = name;
 }
 #region IComparable Members
 public int CompareTo( object right )
 {
   if ( ! ( right is Customer ) )
   throw new ArgumentException( "Argument not a customer",
    "right" );
  Customer rightCustomer = ( Customer )right;
   return _name.CompareTo( rightCustomer._name );
 }
  #endregion
}

关于实现比较与IComparable接口的一致性有 很多不太喜欢的地方,首先就是你要检测参数的运行时类型。不正确的代码可以 用任何类型做为参数来调用CompareTo方法。还有,正确的参数还必须进行装箱 与拆箱后才能提供实际的比较。每次比较都要进行这样额外的开销。在对集合进 行排序时,在对象上进行的平均比较次数为N x log(N),而每次都会产生三次装 箱与拆箱。对于一个有1000个点的数组来说,这将会产生大概20000次的装箱与 拆箱操作,平均计算:N x log(n) 有7000次,每次比较有3次装箱与拆箱。因此 ,你必须自己找个可选的比较方法。你无法改变IComparable.CompareTo()的定 义,但这并不意味着你要被迫让你的用户在一个弱类型的实现上也要忍受性能的 损失。你可以重载CompareTo()方法,让它只对Customer 对象操作:

public struct Customer : IComparable
{
  private string _name;
 public Customer( string name )
  {
  _name = name;
 }
 #region IComparable Members
 // IComparable.CompareTo()
 // This is not type safe. The runtime type
 // of the right parameter must be checked.
 int IComparable.CompareTo( object right )
 {
  if ( ! ( right is Customer ) )
   throw new ArgumentException( "Argument not a customer",
     "right" );
  Customer rightCustomer = ( Customer ) right;
  return CompareTo( rightCustomer );
 }
 // type-safe CompareTo.
 // Right is a customer, or derived from Customer.
 public int CompareTo( Customer right )
 {
  return _name.CompareTo( right._name );
 }
  #endregion
}

现在,IComparable.CompareTo()就是一个隐 式的接口实现,它只能通过IComparable 接口的引用才能调用。你的用户则只能 使用一个类型安全的调用,而且不安全的比较是不可能访问的。下面这样无意的 错误就不能通过编译了:

Customer c1;
Employee e1;
if ( c1.CompareTo( e1 ) > 0 )
 Console.WriteLine( "Customer one is greater" );

posted @ 2012-05-24 00:50 xiangxiong 阅读(12) 评论(0) 编辑

 

目的:

1.单点登录,是一个用得很多,也是大家比较常见的登录方式了。我在这里写这篇文章的目的,是因为我的这个单点登录里,用了简单的设计模式。供大家参考。

2.这篇文章的附件代码,是完整的单点登录代码。您可以直接下载,嵌入您的系统即可直接使用。这是基于前些文章,部分网友提出的要提供代码下载。所以,我采纳了这个建议,非常感谢各位朋友。

 

配置:

在web.config中,需要在<system.web>节点中,添加如下配置:

        <authentication mode="Forms">             <forms loginUrl="http://user.ccnf.com/login.aspx" timeout="2880" name=".CCNFAUTH" domain=".ccnf.com" defaultUrl="http://www.ccnf.com/" enableCrossAppRedirects="true"/>         </authentication>         <machineKey validationKey="ED757E9190083A50853834A0508738AC58A89661" decryptionKey="B8262DEBC6D1D33F1932AC3EE9C4325E6008A928077B73E6" validation="SHA1"/>

其中:machineKey的生成,网上有代码,请自行去google或baidu。关于authentication mode="Forms"的验证方式,请查阅msdn。

 

在Global.asax全局文件中,加入如下代码:

复制代码
///<summary>     /// 在验证完成后发生     ///</summary>     ///<param name="sender"></param>     ///<param name="e"></param>     ///<author>马志远(Marc)</author>    void Application_AuthenticateRequest(Object sender, EventArgs e)     {         HttpApplication app = (HttpApplication)sender;         HttpContext context = app.Context;         if (context.User != null)         {             if (context.Request.IsAuthenticated == true)             {                 FormsIdentity identity = (FormsIdentity)context.User.Identity;                 FormsAuthenticationTicket ticket = identity.Ticket;                 string userData = ticket.UserData;                 string[] roles = userData.Split(',');                 context.User = new System.Security.Principal.GenericPrincipal(identity, roles);             }         }     }
    ///<summary>     /// 发送身份请求时触发     ///</summary>     ///<param name="sender"></param>     ///<param name="e"></param>     ///<author>马志远(Marc)</author>    protected void Application_PostAuthenticateRequest(object sender, EventArgs e)     {         HttpContext ctx = ((HttpApplication)sender).Context;         HttpRequest req = ctx.Request;         HttpResponse resp = ctx.Response;         if (!UrlAuthorizationModule.CheckUrlAccessForPrincipal(req.AppRelativeCurrentExecutionFilePath, ctx.User, req.RequestType))         {             HttpContext.Current.Response.Redirect(String.Format("{0}?ReturnUrl={1}", FormsAuthentication.LoginUrl, req.Url.AbsoluteUri));         }     }
复制代码

 

 

文件目录结构:

 

用法:

1.登录时调用的方法在Passport.cs文件中。调用的代码如下:

CCNF.Tool.Authentication.Passport.GetInstance().SetCookie(user.PersonId);

通过上面的代码调用,那么用户即可以登录成功了。

2.登录成功后,要取得用户的cookie数据,比如用户名、昵称等。这些方法写在了User.cs中。

调用代码为:

CCNF.Tool.Authentication.User.GetInstance().AdministratorId等。

3.退出系统的代码在SignOut.cs的代码中。

CCNF.Tool.Authentication.SignOut.Logout(returnurl);

使用上述代码,即可退出系统。

 

知识点:

其中Passport.cs文件用到了单例模式。单例模式代码如下:

 

复制代码
    ///<summary>     /// 登录验证     ///</summary>     ///<creator>马志远(Marc)</creator>    public sealed class Passport : AuthenticationAbstract     {         private static readonly Passport instance = new Passport();
        ///<summary>         /// 构造函数         ///</summary>         ///<author>马志远(Marc)</author>        private Passport()         {
        }
        static Passport() { }
        public static Passport GetInstance()         {             return instance;         }
复制代码

 http://www.cnblogs.com/mazhiyuan/archive/2012/05/16/2503484.html

代码附件下载:

posted @ 2012-05-17 23:02 xiangxiong 阅读(8) 评论(0) 编辑

主要内容

1、数据库编程方式的进化

2、NH的系统架构

 

一、数据库编程方式的进化

在开始介绍NH的系统架构之前,先谈谈我所经历的.net项目组进行数据库编程的方式的变化:

过去:主要是调用存储过程,对于比较简单的数据库操作也可直接编写简单的sql语句。

现在:由于存储过程难以维护,所以建议尽量采取执行sql语句的方式进行数据库操作。同时,为了让大家更加容易的执行sql语句,还总结出了DAAB之类的数据库操作辅助包。

下一步:随着系统的不断增大,之前大量采用sql实现业务逻辑的设计和编码的做法会加大我们项目的维护的难度和成本。而且采取上述方式进行数据库开发的效率非常低,开发人员往往花费大量时间在和数据库打交道,更谈不上thinking in OO。数据库编程转向O/R mapping。

二、NH的系统架构

在继续阅读之前,如果你想先体验一下NH的便利和优雅,可以参考一下我之前发的NHibernate学习手记(1) - 对象的简单CRUD操作

1、术语解释

1)持久化(persistent)和持久化对象(persitent object):在程序运行时创建的对象,并要求保存到数据库、文件等存储介质中。如订单、人员信息等。

2、NH的持久层功能示意

 


Application即任何需要持久化服务(即数据存储)的应用程序。NH的持久化功能是以服务形式提供的,实现应用程序代码和数据库操作之间的解偶。当然,为了能够使用NH,我们必须要进行一些配置工作。

3、NH系统架构简图

 


上图传达了NH进行持久化的简单思想。在应用程序(Application)中创建的临时对象(Transient objects),开始是保存在内存中的;接着应用程序和NH进行对话(Session,事先需要先由SessionFactory创建Session),告知NH需要进行的持久化请求;NH根据配置文件把持久化操作转化为相应的数据库操作,调用相应的ADO.Net API执行数据库操作。

4、NH系统架构全景图

下图揭示了NH更多的技术细节,我将在后续的文章中,继续介绍。

posted @ 2012-05-16 01:34 xiangxiong 阅读(15) 评论(0) 编辑

  英文原文:Three things you should never put in your database 

  我已经在很多演讲里说过,改进你的系统的最好的方法是先避免做“蠢事”。我并不是说你或你开发的东西“蠢”,只是有些决定很容易被人们忽略掉其暗含的牵连,认识不到这样做对系统维护尤其是系统升级带来多大的麻烦。作为一个顾问,像这样的事情我到处都能见到,我还从来没有见过做出这样的决定的人有过好的结果的。

  图片,文件,二进制数据

  既然数据库支持 BLOB 类型的数据,把文件塞进 BLOB 字段里一定没有错了!?错,不是这样的!别的先不提,在很多数据库语言里,处理大字段都不是很容易。

  把文件存放在数据库里有很多问题:

  • 对数据库的读/写的速度永远都赶不上文件系统处理的速度
  • 数据库备份变的巨大,越来越耗时间
  • 对文件的访问需要穿越你的应用层和数据库层

  这后两个是真正的杀手。把图片缩略图存到数据库里?很好,那你就不能使用 nginx 或其它类型的轻量级服务器来处理它们了。

  给自己行个方便吧,在数据库里只简单的存放一个磁盘上你的文件的相对路径,或者使用 S3 或 CDN 之类的服务。

  短生命期数据

  使用情况统计数据,测量数据,GPS 定位数据,session 数据,任何只是短时间内对你有用,或经常变化的数据。如果你发现自己正在使用定时任务从某个表里删除有效期只有一小时,一天或数周的数据,那说明你没有找对正确的做事情的方法。使用 redisstatsd/graphite, Riak,它们都是干这种事情更合适的工具。这建议也适用于对于收集那些短生命期的数据。

  当然,用挖土机在后花园里种土豆也是可行的,但相比起从储物间里拿出一把铲子,你预约一台挖土机、等它赶到你的园子里挖坑,这显然更慢。你要选择合适的工具来处理手头上的事。

  日志文件

  把日志数据存放到数据库里,表面上看起来似乎不错,而且“将来也许我需要对这些数据进行复杂的查询”,这样的话很得人心。这样做并不是一个特别差的做法,但如果你把日志数据和你的产品数据存放到一个数据库里就非常不好了。

  也许你的日志记录做的很保守,每次 web 请求只产生一条日志。对于整个网站的每个事件来说,这仍然会产生大量的数据库插入操作,争夺你用户需要的数据库资源。如果你的日志级别设置为 verbose 或 debug,那等着看你的数据库着火吧。

  你应该使用一些比如 Splunk Loggly 或纯文本文件来存放你的日志数据。这样去查看它们也许会不方便,但这样的时候不多,甚至有时候你需要写出一些代码来分析出你想要的答案,但总的来说是值得的。

  可是稍等一下,你是那片不一样的雪花,你遇到的问题会如此的不同,所以,如果你把上面提到的三种东西中的某一种放到了数据库里也不会有问题。不,你错了,不,你不特殊。相信我。

posted @ 2012-05-15 23:05 xiangxiong 阅读(8) 评论(0) 编辑
摘要: public class AppEvent{//键值对委托的形式 public static AppEvent Events = new AppEvents() private Dictionary<string, EventHandler> DelegateList = new Dictionary<string ,EventHandler>(); #region 装备事件索引 public const string Aoi_GeoDraw="aoigeodraw"; #endregion public void AddListener(strin阅读全文
posted @ 2012-05-02 22:28 xiangxiong 阅读(775) 评论(0) 编辑
摘要: 编者按:Astrid 是一家旨在帮助人们更好地分担和管理任务的移动应用开发商,Jon Paris 是 Astrid 的 CEO 兼联合创始人。Astrid 曾在去年参加过孵化器 AngelPad 的孵化项目并成功毕业,之后,公司很快就获得来自 Google Ventures 等的种子投资。下面这篇文章是 Jon Paris 的观点,他在文章中向那些希望加入孵化器的创业公司提供了几点建议。 孵化器在帮助创业公司获得融资方面发挥着越来越重要的作用。Techcrunch 曾在一篇文章中表示,Y Combinator 等知名孵化器平均每分钟就收到一份创业公司加入孵化项目的申请。AngelPad 也曾.阅读全文
posted @ 2012-01-30 09:39 xiangxiong 阅读(36) 评论(0) 编辑
摘要: 业信息化除了ERP外,还需要什么? 当然不是ERP2了!ERP与ERP2之争源于当年管理软件市场的泡沫,现在泡沫已经消去了大半,企业对信息化选型也越来越理智。喧闹过后,很多人才发现从ERP到ERP2还必须要跨越一道障碍:接入! 如果企业仅仅是局限于局域网内的应用,就不会存在接入的问题。然而,当企业需要实时能够了解各地分支机构的经营状况;出差人员能够随时随地访问办公系统;远程合作伙伴能够进入分销系统下订单……慎重地选择合适的接入策略,将帮助企业打造协同、实时和敏捷商务的能力,建立企业持续的竞争优势。应用软件连接:企业信息化的大问题 企业应用架构的不断创新也导致了另一个问题,那就...阅读全文
posted @ 2012-01-29 20:52 xiangxiong 阅读(136) 评论(0) 编辑
摘要: 1.随时做好,项目外出展示的准备2.项目的移植性,稳定性,兼容性测试,随时准备着。3.ppt,视频,文档,演讲稿,项目介绍文档,总体设计方案。4.出差之前准备的资料,要经常有更新版本的。阅读全文
posted @ 2012-01-15 02:38 xiangxiong 阅读(34) 评论(0) 编辑
摘要: 作为程序员的你,不知道曾经是否尝试过这样一种开发模式:你有一个伙伴,你们坐在一起,并肩作战,面对着同一台显示器,使用着同一键盘,同一个鼠标,你们一起思考,一起分析,一起编程。如果你尝试过,那你可以继续读下去,看看我们是不是有同样的感受;如果你没有尝试过,那你更应该读下去,因为这篇文章将会带你体会这种编程模式,带你走进结对编程的世界。下面,我就来讲讲我所经历的结对编程吧。这次结对项目的名称为“学术会议的展示”,即在原有的微软学术地图的基础上,添加学术会议的信息及地理位置显示。这是一个不大不小的项目,两个人共同花了9天的时间,在完成基本功能并保证稳定性的同时,添加了一些华丽的界面元素,总的来讲,感阅读全文
posted @ 2012-01-06 14:08 xiangxiong 阅读(23) 评论(0) 编辑