2006年06月19日

一个人年轻的时候最好不要给自己设界或是划一道线,比如5+年计算机毕业,你对自己说我是学程序设计的,做技术和做程序员更适合我,至少30岁前我会倾向于技术方面。那么也许真的30岁之前你一直在做技术和程序员,你非常的执着但也许你错过了很多机会,5年10年之后的校友聚会上你会发现之前上下铺的室友变得非常的不同,一个天一个地,除了他说他的运气比你好之外,也许当他刚毕业的时候,没有你一直这么执着吧,年轻的时候你不知道你能做什么,你不知道你有多大的能量,所以你主观的设了一个界给自己,那么也就束缚了自己,许多人说30岁我还当程序员吗?这是一个令人迷惑的悖论,答案取决于你很多年前是否给自己设了一个界,比如你立志要做一个程序员。

朋友说,假如你年青的时候不去尝试很多机会和变化,那么年老的时候你就无法专注。

我说许多围棋高手从小就立志成为国手,他们不是一直很专注很成功吗?

朋友又说,是啊,他们很成功,但他们也会不停地问自己60岁的时候我还当国手吗? 

反观许多著名和赫赫有名的人物,很多时候30岁40岁之前都碌碌无为,似乎非常的失败,之后某个十倍速的转折就造就了他,除了运气,也许是他从没有给自己设过界吧,他试图体验更多,尝试更多,我想也许这是在发现自己潜力和方向的过程吧。

2006年06月14日

 

泛型解决了boxing/unboxing的问题,但System.Collections.Generic下面的集合类没有解决性能问题(这个性能问题不是boxing/unboxing带来的,是数据复制带来的)。这应该是更精确的描述(换句话说,前面我说的是不对的)。

所以不要把大量的值类型大对象放到集合里,这个.NET 1.x里面的适用的结论在有了泛型的.NET 2.0依然适用。(这个结论对于明白人来说是显而易见的。而我的这个post出于论坛,是为了纠正论坛里的一些错误言论,见回复)

—– update on 12/18/2005

原贴名为“C#范型的用处不是很大”,现在改为“C# 2.0泛型探讨”。

我原来确实认为C#泛型用处不会很大,因为我看了很多介绍泛型的文章,像这个,把C#泛型的好处主要归纳为这三点:类型安全、二进制代码重用、性能。更好的类型安全是显而易见的,但后面两个好处并不明显。

关于“二进制代码重用”,是建立在“不使用泛型就得为一个功能的实现写多个版本的代码”的假设上的,然而实际上用List Generic能完成的事情ArrayList也能做到,只是类型安全很难保证。这样最终还是类型安全的好处。

关于泛型的性能,你可以看到这个帖子引来了这么多的讨论,我现在觉得这个 问题得分应用场合而讨论。

对于用泛型实现的集合类,比如System.Collections.Generic namespace下面那些,范型可以提升一些性能,但仍然无法避免boxing/unboxing带来的性能问题,所以依然不可滥用值类型。
  — 这是我认为泛型在性能上也无太大优势的主要原因。最后我总结道:我写这个blog并不是说范型没用,而是说范型可能没有很多人想象中的那么有用。特别是,不要认为范型能解决boxing/unboxing带来的性能问题。就这样。

而对于其它一些领域的范型应用,比如我在回复中提到的quick sort算法,泛型确实可以提高很多的性能,我的测试代码显示它提高了三倍的性能。
  — 这些领域的泛型应用大概是Ninputer反驳我的主要原因。他这样总结道:使用“object来支持任意类型”的做法相比,能够消除unboxing/boxing,因此在使用object后unboxing/boxing在你的程序里影响了性能的时候,泛型能够帮助你避免它。

这两种应用场合完全不同,对范型性能问题的研究自然有不同的结果。所以在各自的应用场合下,我和Ninputer的结论都应该是正确的。(这大概反映了每个程序员的关注点都很不一样
我首先关注的是最常用和最重要的部分,对于.NET泛型,给程序员带来最大好处的肯定非System.Collections.Generic莫属。结果却是忽略了其他的一些方面。

但我原来的标题里面的大结论是错误的(原来我认为泛型用处不大。换句话说,就是.NET的泛型玩不出什么花样),Ninputer用他的VBF有力的证明了这一点。所以现在改了一下标题。同时为update之前的错误言论造成的误导而道歉。

—– update on 12/17/2005

我的这段果然成功的让很多人不满意了

要完整地评估泛型在性能上的问题,可以做这样一个表来对比:

Int32 LargeDataStruct LargeDataClass
ArrayList 87.7 2808.7 76.9
List Generic 31.3 1903.1 70.5

Athlon64 2800+/Biostar NForce4/Apacer 512M DDR400 x 2

这是在我的机器上的测试,把数据装入集合再从集合获取数据,循环50w次得到的结果。注意横向的比较(标为红色的部分)。

所以我的结论是:

  1. 泛型总是能带来性能提升,但提升不大;或者说仅在int这样很小的数据类型上才有很明显的作用
  2. 泛型并不能解决boxing/unboxing的问题,大型的值类型,还是应该设计为引用类型

这回应该说清楚了吧

—– original

前些天尝试让CSDN的.NET论坛增加一点讨论的气氛,于是发了这个帖子:

C# 2.0会给我们带来什么

说来说去还是说到了泛型。但我觉得这个feature真的没什么,最起码没有想象中的那么有用,和C++模板实际上差别很大的,为什么说到C# 2.0就都要先说泛型。最后我这样回复:

.NET的泛型,除了能用编译器更好的进行类型检查之外,别的地方用处不大。
并且,性能提升并不明显,装箱/拆箱的过程,不要和类型转换相混淆。以装箱为例:
int i = 5;
object o = i;
这里实际上做了两步操作,一个是把数据从线程堆栈复制到GC堆上,另一步是类型转换。这两步形成了装箱的操作。

泛型只能避免第二步,得到一点点性能提升;而最影响效率的地方却是第一步:把数据从线程堆栈复制到GC堆上。

C#的泛型也远不如C++模板那样灵活,要做“模板元编程”之类的高级用法,在C#根本就不可能。各位有兴趣的话可以比较一下C#和C++在这方面的差别。

PS.我这段大概要让人失望了,但这是事实,C#泛型被讨论的实在太多了,而实际上它的用处不大。其好处主要在编译时更好的类型检查,减少人为错误的可能性。

我目前能看到的好处仅在于“编译时更好的类型检查”而已 — 当然,这个好处也非常重要,但别的真的就没什么了。特别是,很多网友把C#泛型当作解决boxing/unboxing性能问题的药方,实际上根本就不是这样,这种错误的观点会带来更大的性能问题。

误导来自于这种测试代码:

int i = 5; ArrayList list1 = new ArrayList(); list1.Add(i); i = (int)list1[0]; List<int> list2 = new List<int>(); list2.Add(i); i = list2[0];

没错,后者的性能几乎要比前面的高100%。这让人产生了泛型可以解决boxing/unboxing性能问题的错觉,但实际上这是因为这里是小小的int,boxing/unboxing和类型转换的代价是差不多的。但是可以从反面证明我的观点:

假如泛型能解决boxing/unboxing性能问题,那么这两段代码的运行速度应该是大致相当的:

struct LargeDataStruct { decimal A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; } class LargeDataClass { decimal A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z; } LargeDataStruct data1 = new LargeDataStruct(); List<LargeDataStruct> list1 = new List<LargeDataStruct>(); list1.Add(data1); data1 = list1[0]; LargeDataClass data2 = new LargeDataClass(); List<LargeDataClass> list2 = new List<LargeDataClass>(); list1.Add(data2); data2 = list2[0];

实际运行速度如何?完全是天壤之别,根据我的测试,前者所花费的时间是后者的100倍左右;测试可能不精确,但一两个数量级肯定是有的。

2006年06月03日

1、C#/.Net中的事件与组播委托很相似,它就是一个较特殊的组播委托。它易于使用,更强健、安全;
2、委托是一个类型;事件是一个类型的成员;
3、最重要的区别:

在客户代码(声明事件的类之外)绑定事件只能用使用 +=、-= 这两个复合运算符(只能使用复合运算符的形式)。在声明事件的类内部可以使用 = 运算符为事件赋值,这样带来的副作用是移除了以前绑定在此事件上的方法。或许这是不允许在客户代码中使用 = 的原因。当编译器遇到 event 关键字,它隐式声明事件为 pivate,并创建两个仅有的在 MSIL 可见的属性:add_..,remove_..。它们通过 +=、-= 被访问。在客户代码或声明委托的类中都可以对委托使用 +=、-=、= 操作符。

写了很长时间.net程序,最近对委托和事件有了一个新的理解.其实说穿了,事件只是委托的一个特殊形式.委托能实现的,事件也能实现.同理把事件代码中的event去除掉,变成委托,代码也不会出现问题.为什么微软定义一个事件关键字呢?我的理解是,当使用event关键字,标识一个事件时,事件成员不能使用赋值将委托链表中的其他代码注册的方法去除掉.这样会破坏订阅的完整性,当用event标识后,使用事件时,只能使用+=或-=来添加和删除订阅.保证一行代码访问事件时不会产生误操作.同时在asp.net中,使用事件,框架会解析页面标签中的OnEventName属性,将属性值注册到事件链中去.这些都是框架上做的一点代码转换,希望我的理解,能给大家一点提示.也不知道理解是否正确.欢迎大家指导.

2006年05月25日

 1. 嵌套类

首先必须要注意,内部类和嵌套类的区别。在C#中只支持嵌套类,嵌套类拥有访问宿主类任意成员的权限。嵌套类除了访问权限外,并不会获得宿主类实例的引用,也不会随宿主类自动产生实例。

C#

class Class1
{
  private int x = 13;

  public void Test()
  {
    new Class1Sub(this).Test();
  }

  public class Class1Sub
  {
    private Class1 o;

    public Class1Sub(Class1 o)
    {
      this.o = o;
    }

    public void Test()
    {
      Console.WriteLine(o.x);
    }
  }
}

class Program
{
  static void Main()
  {
    // 1.
    new Class1().Test();

    // 2.
    Class1 o = new Class1();
    Class1.Class1Sub s = new Class1.Class1Sub(o);
    s.Test();
  }
}


Java

class Class1 {
  private int x = 13;
  
  public void test() {
    new Class1Sub(this).test();
  }
  
  public static class Class1Sub {
    private Class1 o;
    
    public Class1Sub(Class1 o){
      this.o = o;
    }
    
    public void test() {
      System.out.println(o.x);
    }
  }
}

public class program {
  public static void main(String[] args) {
    // 1.
    new Class1().test();
    
    // 2.
    Class1 o = new Class1();
    Class1.Class1Sub s = new Class1.Class1Sub(o);
    s.test();
  }
}


Java 的例子和 C# 基本类似,不过要注意的是 Java Class1.Class1Sub 被申明为 "static class"。
另外,C# 嵌套类缺省是 private 方式的,也就是说不能被外界访问。可以被赋予 private/internal/protected/internal protected/public中的任一种权限。Java 的嵌套类缺省是 friendly 方式,可以被赋予 private/protected/public/friendly中的一种。

2. 内部类

内部类和嵌套类比较相似,但没有static修饰,不能像嵌套类那样在外部创建内部类的实例对象。它虽然不会自动随宿主类生成实例,但却能自动获取宿主类的引用,从而访问宿主类的实例成员。

我们修改上面的例子,将其改成内部类,我们发现Class1Sub可以直接访问宿主实例的任意字段,并不需要使用宿主引用。这其实是Java编译器做的一些“魔法”,当在宿主实例内生成内部类时会自动将宿主引用隐性传递给内部类实例。当然,在内部类也可以使用"宿主名.this"获取宿主引用。

class Class1 {
  private int x = 13;
  
  public void test() {
    new Class1Sub().test();
  }
  
  class Class1Sub {  
    public void test() {
      System.out.println(Class1.this.toString());
      System.out.println(x);
    }
  }
}

public class program {
  public static void main(String[] args) {
    new Class1().test();
  }
}


内部类可以跨越多重嵌套,访问任意级别的嵌套类成员。

class Class1 {
  void doSomething(){};
  
  class Class1Sub {
    class class1Sub2 {
      void test() { doSomething(); }
    }
  }
}


3. 本地内部类

我们还可以在方法内部创建内部类,这种内部类被称之为“本地内部类(local inner class)”。本地内部类不能有权限修饰符,和下面的匿名内部类非常相似。如果本地内部类需要使用方法参数,则必须为该参数添加final关键字。

class Class1 {
  private int x = 13;
  
  void test() {
    class Sub {
      Sub() { System.out.println(x); }
    }
    
    new Sub();
  }
  
  void test2(final String s) {
    class Sub {
      Sub() { System.out.println(s); }
    }
    
    new Sub();    
  }
}

public class program {
  public static void main(String[] args) {
    new Class1().test();
    new Class1().test2("abc");
  }
}


3. 匿名内部类

我们可以在方法内部创建一个实现某个接口或者继承某个类的匿名类,它具备内部类的全部功能,只不过因为没有“名字”,我们不能在外面实例化它,也不能创建构造方法,而只能使用实例初始化代码段。如果匿名类需要使用外部方法参数,必须为该参数加上final关键字。

abstract class Base {
  abstract void test();
}

interface IBase {
  void test();
}

class Class1 {
  private int x = 13;
  
  public Base makeBase() {
    return new Base(){
      {
        System.out.println("new Base…");
      }
      
      public void test() {
        System.out.println(x);
      }
    };
  }
  
  public IBase makeIBase(final String s) {
    return new IBase(){
      public void test() {
        System.out.println(s);
      }
    };
  }
}

public class program {
  public static void main(String[] args) {
    new Class1().makeBase().test();
    new Class1().makeIBase("abc").test();
  }
}


一般情况下,匿名内部类和本地内部类都可以实现相同的功能。选择本地内部类的额外理由是:

1. 需要或者覆写构造方法。
2. 创建多个本地内部类实例。
3. 没有可用的基类或接口。(匿名内部类必须实现某个接口和继承自某个基类。)

匿名内部类和本地内部类还可以放在任意的代码范畴(scope)内。

interface I1 {}

class ClassX
{
  void print(final int i) {
    if (i > 0) {
      class Sub {
        Sub() { System.out.println(i); }
      }
      
      new Sub();
    }
    else {
      new I1() {
        {
          System.out.println("i <= 0");
        }
      };
    }
  }
}

public class program {
  public static void main(String[] args) {
    new ClassX().print(1);
    new ClassX().print(0);
  }
}


4. 继承内部类

在宿主类内部继承内部类,和正常情况下没什么区别。

class Class1 {
  
  private int x = 10;
  public Class1() { new Sub2(); }
  
  class Sub {
    Sub(){ System.out.println("Sub:" + x); }
  }
  
  class Sub2 extends Sub {
    Sub2(){ System.out.println("Sub2:" + x); }
  }
}


而为了在外部继承内部类,就必须:
1. 包含有宿主类引用参数的构造方法。
2. 在构造方法内部调用 super() 方法。

class Class1 {
  class Class1Sub {
  }
}

class Class1SubX extends Class1.Class1Sub {
  public Class1SubX(Class1 o) {
    o.super();
  }
}

public class program {
  public static void main(String[] args) {
    new Class1SubX(new Class1());
  }
}


虽然在外部继承内部类以后,我们可以在外部创建内部类实例,但同时也失去了隐性访问宿主类成员的权利。

5. 覆写内部类

继承含有内部类的宿主类,并不会同时继承其内部类,即便在继承类内部有相同名称的内部类,也完全是两个不同的类型。

class Class1 {
  void test() { new Sub(); }
  
  class Sub {
    Sub() { System.out.println("Class1.Sub"); }
  }
}

class Class2 extends Class1 {

  void test() { new Sub(); }
  
  class Sub {
    Sub() { System.out.println("Class2.Sub"); }
  }  
}

public class program {
  public static void main(String[] args) {
    Class1 o = new Class2();
    o.test();
  }
}


输出
Class2.Sub

由于编译器并不会因为 Class2.Sub 是 Class1.Sub 的继承类,因此并没有自动调用 Class1.Sub 构造方法。我们修改一下,使得 Class2.Sub extends Class1.Sub,OK!这回对了。

class Class1 {
  void test() { new Sub(); }
  
  class Sub {
    Sub() { System.out.println("Class1.Sub"); }
  }
}

class Class2 extends Class1 {

  void test() { new Sub(); }
  
  class Sub extends Class1.Sub {
    Sub() { System.out.println("Class2.Sub"); }
  }  
}

public class program {
  public static void main(String[] args) {
    Class1 o = new Class2();
    o.test();
  }
}
2006年05月16日

    文章开头就列举了那么多联系方式,难免会让大家感觉有点AD的意味,但是不容质疑的是,默默的确有那么丁点的表现欲^_^,虽然有时候过于细致会被人说婆妈,但是幸好这种细致对于编程来说,还是蛮有益的!
    从默默自己向别人问怎么学PHP开始,到后来不少人又来问默默怎么学PHP,不管默默是新手,还是老鸟,似乎总是感觉摸不出一条清晰的脉络来,不过,默默既然学会了PHP,那么我走的这条路或多或少的有一定借鉴性。
    PHP的背景恐怕就不用默默赘言了,我相信大家选择一种语言,并不是看它的背景和悠久历史,更重要的是看它的实用性,华而不实的语言哪怕是再辉煌的历史,也毕将步向没落,可喜的是PHP经受住了考验,也因此,它确实是一种值得学习的语言。
    默默一直是听从别人的经验长大的,也因此在前辈们的经验里让默默少走了许多的弯路,更快的步入了正规,在此向那些我至尽不知道其名字的前辈们道声谢谢,在默默的眼里,帮助不分大小,只要是帮助,总会让默默的心里暖融融的,我想,前辈们帮助我,并不是为了得到我的一句谢谢,更多的是出于一种责任感和对默默的期望,所以我想,只有学好PHP,才能对得起前辈们的汗水。
    正如我所说的,默默也终于感觉到了一种责任感,默默不知道自己的经验到底能帮助新手多少,但是默默明白,现在到了履行责任的时候了,我有必要把自己的经验告诉给所有希望学好PHP的人,只有这样才能让中国的PHP不断的进步,不断的发展,在世界上占据一席之地。
    默默学习PHP的这段期间,感觉国内的PHP环境越来越成熟,规范也在逐渐的健全,PHPCHINA的成立,标志着与官方直接挂钩的PHP机构在中国正式落户了,在此献上迟到的掌声!
    好的,切入正题:
    我想在讲述自己的学习方式前,对那些期望能从我的文章中获得有用信息的人说一句心里话:
    默默的文章不会对您的学习起到实质性的作用,您能否成功,还得靠自己的,坚持,坚持,再坚持,就是步入成功的不二法门。
    我先把我自己学习PHP的过程做一下概括:
        (1)熟悉HTML/CSS/JS等网页基本元素,完成阶段可自行制作完整的网页,对元素属性达到熟悉程度
        (2)理解动态语言的概念,运做机制,熟悉PHP语法
        (3)学习如何将PHP与HTML结合起来完成简单动态页面
        (4)接触MYSQL,开始设计数据库程序
        (5)不断巩固,摸透大部分PHP常用函数,并可理解OOP,MYSQL优化,以及模板
        (6)完成一个功能齐全的动态站点
    我的这套线路可能跟许多学习PHP的爱好者不谋而合,这也算是一个循序渐进的学习过程,不过新手不要看到上面的概括就以为学习蛮简单的,默默在此不得不对您稍微泼一下冷水,任何东西其实都不简单,即使是小吃部的烧饼也不是一下子就会做成的。
    我先解释一下我的学习思路。
    首先,理解网站这一概念之后不难看出,任何网站都是由网页组成的,也就是说想完成网站,必须先学会做网页,因此必须要掌握了HTML,才能为今后制作网站打下基础。
    在学习HTML中我想边学边做是最有效的方式,当然这一方式对于学习PHP同样是最有效的。
    HTML中的任何元素都要亲自实践,只有明白了什么元素会起到什么效果之后,你才会记忆深刻,而一味的啃书,绝对是不行的,我想大部分新手之所以觉得概念难学,大部分是一个字“懒”,懒是阻止进步的最大敌人,所以克服掉懒的习惯,才能更快的学好一样东西。
    也许您在学习PHP的时候只想尽快的开发一个网站,也就会想我做网站,干嘛要学什么网页这些小儿科?不难看出,眼高手低的新手不在少数,这种思想无疑于建造空中楼阁,你不建地基,何来的房顶呢?
    OK,掌握静态网页的制作技术是学习开发网站的先决条件,这一点就讲到这里,因为这篇文章不是教程文章,也就不对技术进行深入的刨析了。
    我假设你目前已经可以完成一个静态页面了,当然,做的好看难看是另外一说,默默的第一个网页也没好看到哪去,但是“孩子”再丑,咱们做“爹妈”的也不能嫌弃不是?这毕竟是咱的成果。
    那么咱们就开始学习动态语言的概念吧,刚一接触动态语言,可能很多人都会蒙了,怎么这乱七八糟的东西,在网页里显示的时候却是另外一码事?其实这并不算乱七八糟,你写的HTML代码不也一样是一堆堆的字符吗?毕竟,代码并不是作为直接输出的,而是经过处理的,说白了,HTML是经过HTML解析器,而PHP当然也就通过PHP解析器了,跟学习HTML一样的道理,想让任何的解析器完成操作,就必须使用它们专用的语法结构,所以PHP长相奇怪也就不足为奇了。
    对于PHP的理解是新手最难迈过的一道门槛,不过你应该感到幸运的是PHP已经最大极限的为了新手而努力了,如果你学过其他的语言,也许会觉得PHP的确相当的简单,但是如果你之前什么都没学过,那么阿弥陀佛,硬着头皮琢磨吧。
    书过三遍自然熟,这个简单的道理告诉我们,即使你理解不了PHP,但是也必须先跟它混个脸熟,看,一遍遍的看,看的同时一边琢磨,一边按照它所教的打代码,即使你搞不清楚那些代码到底是干嘛的,但是起码你应该找找感觉。
    在一段挣扎之后,聪明的你,显然已经逐渐的开悟了,慢慢的理解了编程的概念,那么祝贺你,你已经迈出了成功的第一步。
    搞清楚HTML和PHP的概念,那么PHP和HTML混合编程应该不成问题,在这期间,你完全可以让PHP给你算算 一加一等于几,然后在浏览器输出,不要觉得幼稚,这的确是跟阿波罗登月一样,你打的是一小段代码,但是对于你的编程之路,可是迈出了一大步啊!兴奋吧?但是不得不再给你泼点冷水,您还是菜鸟一个。
    高兴一段时间就必须继续努力了,接下来就是学习数据库了,MYSQL可算是PHP的黄金搭档了,不过,虽然话是这么说,你也可能恨不得把MYSQL给生吞活剥了,因为这一行一列的东东简直让自己头晕目眩。
    头晕归头晕,目眩归目眩,你不可能吃饭的时候咬了自己一下舌头就从此不吃饭了不是?放下畏惧,继续努力,咱们是来征服它的,而不是被它征服的,振奋起来吧同志。
    在一番搏斗之后,你终于理解了数据库的概念,而且让你兴奋不已的是你终于可以通过PHP来连接数据库了,这期间你是怎么学会的,我们不去考证了,但是事实证明,你已经可以了。
    学会了PHP和数据库的你,无疑是左手拿着MOTOLOLA右手拿着NOKIA,要多潇洒,有多潇洒,哈哈,终于学会了,但是可能这个时候,又会有人不经意的拍拍肩膀对你说:哥们,别高兴的太早,你还是菜鸟,离学会还差着一大截呢!
    等到你发奋努力的学会了用PHP成功的插入,删除,更新数据的时候,显然,你已经距离成功指日可待了。
    这个时候的你也许是这种状态:
        你会HTML吗?会,我能编好几个大表格排板的网页啦!
        你会PHP吗?会,我会把一加一的运算写在函数里,然后调用啦!
        你会MYSQL吗?会,我会把我的信息在数据库里插入删除啦
    那,接下来你该怎么做呢?我觉得,小试一下身手,大概是没问题了,那么交给你个任务,做个留言本吧,这和HELLO WORLD有一比啊!^_^,同是新手面临的第一道关。
    花了一段时间,你终于学会把表单的数据插入数据库,然后显示出来了,应该说一个程序的雏形已经诞生了。
    但是,你可能瞅瞅东,看看西,人家这个编论坛,那个CMS,还有那啥CRM,我啥时候写一个呢?
    不要急,可以说你的马步已经扎的差不多了,接下来就要开始练把势的时候了,如果有条件的话,用笔或者打印一个简易的PHP手册在身上,时不时的摸出来看看,记得,去WC也不能放过(^2^)。
    再有条件的话,买本书看看吧,《PHP+MYSQL WEB开发(第三版)》号称圣经级,(也许是个不错的选择(声明:作者没给我啥好处费,我也不是书托,隔着大老远,我连他老兄的面都没见过的说-_-)
    巩固了自己的知识,熟悉了PHP和MYSQL开发的要领之后,再回头看你写的那个留言本,你也许会怀疑那真的是你写的吗?当然,如果屋里还有鬼的话,也许是它写的-_-
    这个时候,你的留言本应该加入注册以及分页功能了,而如果你更强的话,UI(用户界面)也可以加强,完成之后,感觉是不是特有成就感?不管怎么样,咱好歹是写了一个动态网站程序了,放在自己的网站上耍耍吧,让好朋友来看看,嘿,看咱写的多棒,然后再在网上宣传一下。
    几天之后你再打开留言本,哎?哇,一下弹出N多页面!很明显,你的留言本并没有做好安全防范,被人用JS代码小小的耍了一下,我很同情你这个时候的感受,但是没有别的办法了,继续努力吧!
    你发奋努力,熟悉了安全方面的问题,然后又设计了一些程序,感觉还不错。
    那么接下来,这就算学会啦?NO,NO,NO,还早呢,你至尽还没碰过OOP之类的吧?模板呢?
    恩,学!加紧学呀学,学会了这些之后,你又学会了生成静态网页,现在你应该接触一下XML了,恩,XML也了解了,那么AJAX你也得接触接触吧?AJAX完了….然后…
    总而言之,你绝对不会发现你全部都学会了,一些真正的强人总会搞出新玩意来丢给你,你不学就落后了,也印证了前人的经验,果然是学无止境啊!
    我想通过我的一番YY,你也应该大致熟悉了一些学习过程,也许我的过程和你的有些出路,但是不管怎么样是殊途同归,我写这么多,也只是给大家一个借鉴的机会,至于好与不好,默默不敢打包票^0^
    看完之后你发现,罗嗦这么多,对我一点用处没有啊,我知道该怎么学,但是我想如何才能更快的学,一周速成,啊不,24小时速成那种,默默你有没?
    我…….我没有,但是2分钟之内把你扁进医院里,我倒是有把握-_-
    学东西,永远不要妄想有速成这一说,告诉你了一个方式,但是缺少努力这一环节,那也是白搭。
但是有一点我可以给你保证的就是,你学会了PHP,那么学其他的语言,肯定速成,反过来也一样,如果你之前学过其他的语言,那么学PHP肯定快。
    不过语法好学,但是怎么用语法来实现每个人都有每个人的方式,几乎是各有千秋。然而借鉴别人成功的代码,绝对是有益无害,因此,多看那些经过千锤百炼凝出来的经典代码,是进阶的最好方法。
    讲了这么多,无非是想说:学习PHP不仅要掌握方法,更多的是付出汗水,我不希望看到中途放弃的人,相信自己,相信自己的选择,更要相信自己的能力,如果自己想放弃,暴力一点的话,就自己抽自己一个嘴巴,然后大吼:别人可以,我为什么就不可以?(是不是有点阎罗教练的味道,默默的确是电影看多了,抽嘴巴是会痛的,各位其实明白这个道理了就行了)
    另外要叮嘱各位的是,抵御诱惑,ASP/PHP/JSP/.NET的对比也许会让你无所适从,你也许学了一半PHP,又开始打C#的主意,或者有人说JAVA很强,这个时候的你绝对不能动摇,哪怕你真想学,也得学会了PHP。然后再学,见异思迁是最不可取的,狗熊掰玉米就是这个道理,如果经常中途放弃,只能是一无所获,还浪费了N多的时间和经历,得不偿失,最重要的是,你会被别人瞧不起,没有人会喜欢和见异思迁的人交朋友,因为这种人太不安分,太不可靠,因此,你必须要强迫自己完成自己的目标,哪怕可能会很难受,也得坚持,毅力就是这么锻炼出来的。
说了这么多,可能大家嫌我烦了,但是默默属于那种平常很沉默,一旦进入状态之后就变的很兴奋,我想尽可能的把我所想的表达出来,但是可惜自己的文字功底有限,效果可能不尽如人意,但是我感觉,把自己的经验分享出来之后感觉很轻松,如释重负的感觉。
    最后,我还想说一下,有很多的国人不自信,说过诸如什么语言到了中国就变味,什么中国人不团结,没有团队精神之类的,我反倒觉得那些人鼠目寸光,可悲,可叹,那些人总是把一切的责任推卸的一干二净,却不从自身出发,以身表率,来改变这一状况,反而悲观的叹息,只期望那些人早点醒悟,只有人人都努力,才能进步,而自卑自叹,只会越搞越糟。
    其实无论是PHP还是其他任何东西,咱们不学则已,学就要搞出个名堂来,一个人的力量也许微不足道,但是大家都努力,齐心协力,中国人有什么不可以的?咱们不但要赶上,更要超越,要让世界都使用“中国标准”,也许我这么说有人说我痴心妄想,也有人说我只会喊口号,这都无所谓,但是重要的是,我终于把心里的话说了出来,说白了,咱们中国人不缺实力,就缺野心,野心并不是贬义,这里所指的野心,正是指中国人敢于争世界第一的志气。
    说了这么多,又跑题了^_^,其实就是鼓励咱们学习PHP的新手,努力吧,中国的发展靠咱们!(把话说大了,各位看官不要见怪!斗胆而言^_^)

2006年05月14日

直面现实,坚守理想(by 李建忠 ,原文链接
《CSDN开发高手》的编辑约我写这篇文章,缘于前不久我在个人blog(http://blog.dreambrook.com/jzli/)上写的《认清现实,才能找回理想》一文。当时写那篇文章是在阅读孟岩先生blog(http://blog.csdn.net/myan/)上的《放弃理想,未必能成就现实》一文之后有感而发所得。孟岩先生在《放弃》一文中的主旨是在说由于通用软件的高复用性,而国际研发巨头在上面有巨大的投入和优势,所以劝我们大家放弃这条陡峭的道路,而专攻行业信息化。这种思路有"田忌赛马"的影子,但也不完全相同,因为我们在行业信息化方面只能说差距不像通用软件那么大,而决然不敢说有什么突出的优势。同时中国作为一个世界大国,要想在数字时代成为一个世界强国,前提是必须成为一个软件强国,那么我们的发展策略就不能是偏执一隅,而应该全面出击。中国软件业的薄弱不是由于我们没有在合适的时间找到合适的方向–可以说在这个创新的时代遍地都是方向,任何一个方向上我们都可以也应该有所作为,我们真正的问题出在了软件人才的成长环节上–"摇篮、自学、提高"三个阶段,对应着"教育、出版、培训"这三块产业短板,我看到太多太多的程序员在这上面栽跟头。这也是我在《认清》一文中的观点。

两篇文章出来之后引起了很多朋友和网友的共鸣和讨论。可惜的是我在《认清》一文中的"破坏性"批判较多,而鲜有建设性的意见。大家的讨论也让我对这一问题有了更进一步的思考和认识。但是在本文中我不拟再谈教育、出版、培训等体制和环境,而只是想谈一谈单个的程序员个体如何来应对这样的现实环境。我之所以只想谈谈程序员个体的应对策略,是因为中国的教育、出版、培训等体制和环境的恶劣问题由来已久,也有很多有识之士对此有过深刻的思考和积极的建议,但要解决它们绝非一日之功。比如著名学者李开复老师在他的《给李岚清副总理的信》一文中(可参见开复老师专为中国学生建设的网站http://www.kaifulee.com/)对中国大学教育的问题已经写得非常清楚,其中提的建设性意见也非常好。这封信写于2001年10月,但是如我们所知,几年过去了,开复老师在信中提到的诸多问题依旧存在,而且有些问题还更加严重。比如我们天天喊"科教兴国",可我们的教育投入长时间不及联合国教科文组织建议的一半,列在全世界倒数的几位,比非洲穷国乌干达还低,匡论我们教育投入中严重的结构不合理现象。再比如北大法学院的"甘朱事件",广西招生丑闻中北航计算机系教授的恶劣行为等等个案。中国大学教育的问题多如牛毛,IT作为一门发展迅速的高科技学科呈现的问题尤甚。现实的困境大家都看在眼里,记在心里,问题是面对这样的困境我们怎么办。

中国的现实是你不能去等这个社会改造好了之后来坐享其成。改造社会做不了,我们恐怕也等不到遥遥无期的文艺复兴,但是我们可以从自身的改造做起。"达则兼济天下,穷则独善其身",这是千百年来中国知识分子群体对国家社稷和个人发展所秉持的信条。开复老师有"兼济天下"之达–虽然这样的"兼济"效果并不如想象的那么好,但毕竟有些许推动作用–而我们只有"独善其身"之穷。孟岩先生说的好"现实总是别人的,只有理想是我们自己的"。 作为一位对软件业深怀感情的学长,我乐意和大家在这里分享一些个人的体会和思考,谈一谈作为目前的IT在校生以及刚入门的程序员如何来克服这些现实的困难,从而在入门阶段就认清现实,打好基础,不要再走很多前辈所经历的无谓的曲折道路。

我在《认清》一文中曾经描述了当下大部分IT人的成长道路,相信很多人都深有感触,可以从中找出许多自己的影子。这当然不是IT人的宿命,如果能对现实有清醒的认识,不为现实所迷惑,及时调整自己,激流勇进,未尝不能成就自己的人生。

比如大部分学生刚刚从繁重的高中学习中解脱出来进入大学校园,一般对大学都充满了神秘感,尤其对大学老师更是充满了敬仰和膜拜。尊师是应该的,但是盲目信师、甚至崇师就不好了。由于现实的教育投入严重不足,我们的师资本来就非常匮乏,再加上现代IT技术日新月异、纷扰繁杂,在浮躁的学风下愿意在课堂上耕耘奉献的优秀教师是少之又少,即便是想遇到一个合格的老师也并不容易。君不见有多少课程是四平八稳的照搬陈述?有多少技术的真知被很多老师一带而过?有多少本来应该碰撞的火花被死气沉沉的课堂所淹没?很多学生听了一段这样的课程之后往往就开始失去了对课程的兴趣,并进而怀疑自己。事实是大家的能力不够、智力太差吗?完全不是!既然大家已经进入了大学,那么掌握这些课程的智力水平绝对不成问题,这个起码的自信一定要有。是的,也许很多老师的水平比你高,但是他/她并没有达到足以教授你知识,提高你学养,洞开你技术之门的水平。

那么遇到这种情况应该怎么办呢?要勇敢地否定掉这些不合格的老师,自信地走出课堂,利用一切自己能够利用的资源(比如图书馆,互联网,计算机协会等)来提高自己。不提倡崇师,是指不要迷信那些不合格的老师,但决不等于鼓励大家放弃该老师所讲授的那门课程。我们现在的IT教育课程固然设置的不够合理,但是基本上来讲,很多IT基础课的学习对于一个IT人的成长还是很有必要、不可或缺的,切不可因为对老师的否定而嫁祸于相关的课程。写这篇文章的时候,正好在电视上看到一个访谈节目在讲著名物理学家杨振宁先生到清华大学去为大一本科生讲授物理课的事情,深为杨先生的高瞻远瞩而生敬意,也为清华大学这样的举措叫好。不过具有讽刺意味的是杨先生这么做的动因却是现在很多教授不愿意"屈尊"为本科生上课,耐人深思,也是不争的事实。

否定掉不合格的老师容易,但是真正走出课堂之后如何建立起一套适应自己的自学体系并不容易。自学是我们提高自己的一个很重要的环节,更是IT人的一项基本素质。事实上,即便你遇到优秀的老师,甚至是名师,你同样需要培养自学能力。自学的方式方法有很多,许多也因人而异,但是其中有一条很关键的线索深刻影响着自学的效果,这就是图书。为什么说图书是一条很重要的线索呢?很多人认为计算机是一门注重实践性的技术学科,所以非常重视动手能力。是的,计算机的学习是一项实践性很强的活动,动手能力非常重要。但"实践性"和"动手能力"并不意味着天天编写一些花里胡哨的软件就是履行了其实践性了–不得不承认这是一部分IT在校生和初学者的误区。实际上,每一项技术都有着深刻的理论渊源,技术背景,协议规范。没有通过阅读而得来的有素的理论提升,而陷身于代码的汪洋大海中,不说得不偿失,也至少是进阶缓慢。事实上,在计算机学科方面,如果一个人有了理论提升,想让他不动手都难–很简单,计算机学科的魅力之一就是你可以用自己的双手立即来验证自己学到的理论。所以动手实践在计算机的学习方面并不是一个大的问题,阅读图书才是其中一项重中之重的活动。

明白上述道理并不难,但是只明白这个道理远远不够,甚至会从此而跳进无底的陷阱。为什么这么说呢?因为由于传统教育的问题,大多数学生对书籍达到了盲目相信,嗜书如命的地步。似乎只要是和自己领域相关的书,都是富含知识的宝藏,都一定要读个遍。此乃大错特错,正好掉进了出版商编制的陷阱里。君不问你手中拿的花花绿绿的图书都是怎么产生出来的?你手中拿的图书的作译者是否真的参透了书中的内容,甚至在这个领域是否懂得比你多?你手中拿的图书讲了大段大段的条条框框,但是否真正触及到了技术的本源?在没有搞清楚这些问题之前,千万不要动任何念头去购买、阅读它,即使有人白送也要坚定地拒绝。一本书的市价几何?读一本平庸乃至劣质书所得到的损失几何?孰轻孰重,需要仔细掂量。

事实上,对于一门课程或者技术的学习,如果能够遇到该领域的经典之作往往事半功倍;否则事倍功半,甚至彻底葬送掉一个人对某门学科的信心。假如你读了10本C++方面的书,仍然不知道default constructor、copy constructor、assignment operator等这些基本的技术存在,那么你以后基本上再提起C++便会望而生畏。但如果你有幸读到一本C++领域的经典之作(比如C++ Primer),那么你在C++上的基础和信心便会彻底得到夯实。一本好书和一本平庸之作,给人带来的价值差距乃天壤之别。

可惜的是在国内目前的出版环境下,技术图书的陷阱非常大,不负责任的劣质之作充斥着图书馆和书店,平庸书籍更是无计其数,"遇"到一本经典之作的概率非常之低。不要迷信"主流是好的"这句中国式的太极语法,中国技术图书界恰恰就是"主流是差的"。那么这时候,选书的功夫就异常重要。事实上,选书是成为优秀IT人才非常重要的一项素质,我甚至认为在中国当下的出版环境中懂得选书比读书更为重要–哪个优秀的IT人没有读过几本经典的书籍?没有几个敬仰的技术作家?决无可能。

那么怎么才能挑选到优秀的图书呢?结合自己的经验笔者总结了以下几种方法:

第一、读书评。优秀、尤其是经典书籍一般都会有该领域的专业人士在期刊杂志上为其撰写书评。这样的书评一般会有图书内容、定位、特点、作译者的介绍和评价等等,比较客观,也比较专业,对于选择优秀图书非常有帮助。但是时下也有部分出版社看到大家对书评的重视,故意雇用一些写手来为某些平庸书籍造势,所以除了读书评外还要结合下面几种方法。

第二、看网络。现在各大技术图书网站都很注重网络书评的建设,因为它的互动性非常好,很便捷。不足之处是缺乏引导,评书很多时候变成了吵架,失去了书评的客观性和公正性。还有一些技术社区,也经常性地有一些图书评介。这些对于发现优秀图书也很有帮助。因此如果是中文图书,那么大家在购买阅读它之前是不是能到China-pub、Dearbook等技术网络书店先看看书评。如果是外文书或者翻译作品,是不是也能到Amazon网站上看看它的书评(Amazon网站的书评机制非常好,通常比较客观)。

第三、问师长。有时候主动请教身边的师长,过来人,以及技术好的同道介绍一些该领域的经典之作也是非常有必要的。实际上你会发现有时候你在一个师长那里学到的东西并不多,但有可能就是某天他给你推荐的某本好书彻底改变了你,让你感谢一辈子。这一点都不奇怪,阅读好书本身就是一个请名师上门的过程。

第四、看作译者及序言。作译者是图书信誉的最终保障。无法追溯责任的出版社可以拿自己的信誉开玩笑,但是一个优秀的作译者断然不可能拿他的信誉开玩笑。至今来讲,我好像还没看到哪个被我认定的优秀的作译者出来的作品下降至平庸之辈,虽然偶有下滑,只不过是有时候我们的期望过高,但很难掉出优秀之列。优秀的作译者还有一个特点就是他们都喜欢为自己的书写一段序言来概括图书的内容,为读者点出一些和图书相关的重要事宜(比如阅读方法、术语交待等)。一个劣质的作译者是断然不敢写序言的。理由很简单,他们并不懂自己产出的作品,根本写不出来这样的序言。基本上到了一定层次,大家只需看作译者及其序言就可以判断一本书的含量。

希望大家能够记住上述几种选择技术图书的方法,让这些方法成为大家日后选择任何一本技术图书时的一种惯性。断不可因为书皮的质量、封面的设计、书本的厚度来选择图书!有了好书,怎么读就是一个等而下之的问题了–好书的特点是你随便怎么读它收获都很大:)

关于培训方面,其实问题和教育一样多,但它又披了一层市场的外衣。以个人的经验来看,应该对一些所谓的技术认证保持基本的冷静,而应注重自己真正内功的提高。IT类技术证书目前在国内技术圈早已是臭名昭著,有了这样的一纸证书有时候反倒让人怀疑你的道德诚信问题,根本证明不了你有多高的技术能力,它们对于一个正常的IT专业出来的本科生来说几乎无任何裨益。即便在某一项技术领域确实需要短期内提高自己,需要参加培训,那么在选择上要慎之又慎。就像选择图书时要重点看图书的作译者一样,个人以为选择培训时主要的关注点应该是讲课的老师。

说了这么多,简单归纳起来就是:尊师,而勿崇师;选书,而勿嗜书;认人,而勿认证。直面现实,坚守理想,希望广大IT人走出自己人生的光辉道路。

提纲:
1、 什么是反射
2、 命名空间与装配件的关系
3、 运行期得到类型信息有什么用
4、 如何使用反射获取类型
5、 如何根据类型来动态创建对象
6、 如何获取方法以及动态调用方法
7、 动态创建委托

1、什么是反射
        Reflection,中文翻译为反射。
        这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如:

        Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。
Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。
MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。
诸如此类,还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。

2、命名空间与装配件的关系
        很多人对这个概念可能还是很不清晰,对于合格的.Net程序员,有必要对这点进行澄清。
        命名空间类似与Java的包,但又不完全等同,因为Java的包必须按照目录结构来放置,命名空间则不需要。

        装配件是.Net应用程序执行的最小单位,编译出来的.dll、.exe都是装配件。

        装配件和命名空间的关系不是一一对应,也不互相包含,一个装配件里面可以有多个命名空间,一个命名空间也可以在多个装配件中存在,这样说可能有点模糊,举个例子:
装配件A:
namespace   N1
{
      public   class   AC1   {…}
      public   class   AC2   {…}
}
namespace   N2
{
      public   class   AC3   {…}
      public   class   AC4{…}
}
装配件B:
namespace   N1
{
      public   class   BC1   {…}
      public   class   BC2   {…}
}
namespace   N2
{
      public   class   BC3   {…}
      public   class   BC4{…}
}

        这两个装配件中都有N1和N2两个命名空间,而且各声明了两个类,这样是完全可以的,然后我们在一个应用程序中引用装配件A,那么在这个应用程序中,我们能看到N1下面的类为AC1和AC2,N2下面的类为AC3和AC4。
        接着我们去掉对A的引用,加上对B的引用,那么我们在这个应用程序下能看到的N1下面的类变成了BC1和BC2,N2下面也一样。
        如果我们同时引用这两个装配件,那么N1下面我们就能看到四个类:AC1、AC2、BC1和BC2。

        到这里,我们可以清楚一个概念了,命名空间只是说明一个类型是那个族的,比如有人是汉族、有人是回族;而装配件表明一个类型住在哪里,比如有人住在北京、有人住在上海;那么北京有汉族人,也有回族人,上海有汉族人,也有回族人,这是不矛盾的。

        上面我们说了,装配件是一个类型居住的地方,那么在一个程序中要使用一个类,就必须告诉编译器这个类住在哪儿,编译器才能找到它,也就是说必须引用该装配件。
        那么如果在编写程序的时候,也许不确定这个类在哪里,仅仅只是知道它的名称,就不能使用了吗?答案是可以,这就是反射了,就是在程序运行的时候提供该类型的地址,而去找到它。
有兴趣的话,接着往下看吧。

3、运行期得到类型信息有什么用
        有人也许疑问,既然在开发时就能够写好代码,干嘛还放到运行期去做,不光繁琐,而且效率也受影响。
这就是个见仁见智的问题了,就跟早绑定和晚绑定一样,应用到不同的场合。有的人反对晚绑定,理由是损耗效率,但是很多人在享受虚函数带来的好处的时侯还没有意识到他已经用上了晚绑定。这个问题说开去,不是三言两语能讲清楚的,所以就点到为止了。
        我的看法是,晚绑定能够带来很多设计上的便利,合适的使用能够大大提高程序的复用性和灵活性,但是任何东西都有两面性,使用的时侯,需要再三衡量。

接着说,运行期得到类型信息到底有什么用呢?
还是举个例子来说明,很多软件开发者喜欢在自己的软件中留下一些接口,其他人可以编写一些插件来扩充软件的功能,比如我有一个媒体播放器,我希望以后可以很方便的扩展识别的格式,那么我声明一个接口:
public   interface   IMediaFormat
{
string   Extension   {get;}
Decoder   GetDecoder();
}
这个接口中包含一个Extension属性,这个属性返回支持的扩展名,另一个方法返回一个解码器的对象(这里我假设了一个Decoder的类,这个类提供把文件流解码的功能,扩展插件可以派生之),通过解码器对象我就可以解释文件流。
那么我规定所有的解码插件都必须派生一个解码器,并且实现这个接口,在GetDecoder方法中返回解码器对象,并且将其类型的名称配置到我的配置文件里面。
这样的话,我就不需要在开发播放器的时侯知道将来扩展的格式的类型,只需要从配置文件中获取现在所有解码器的类型名称,而动态的创建媒体格式的对象,将其转换为IMediaFormat接口来使用。

这就是一个反射的典型应用。


4、如何使用反射获取类型
        首先我们来看如何获得类型信息。
        获得类型信息有两种方法,一种是得到实例对象
        这个时侯我仅仅是得到这个实例对象,得到的方式也许是一个object的引用,也许是一个接口的引用,但是我并不知道它的确切类型,我需要了解,那么就可以通过调用System.Object上声明的方法GetType来获取实例对象的类型对象,比如在某个方法内,我需要判断传递进来的参数是否实现了某个接口,如果实现了,则调用该接口的一个方法:

public   void   Process(   object   processObj   )
{
Type   t   =   processsObj.GetType();
if(   t.GetInterface(“ITest”)   !=null   )
                    …
}

        另外一种获取类型的方法是通过Type.GetType以及Assembly.GetType方法,如:
              Type   t   =   Type.GetType(“System.String”);
        需要注意的是,前面我们讲到了命名空间和装配件的关系,要查找一个类,必须指定它所在的装配件,或者在已经获得的Assembly实例上面调用GetType。
        本装配件中类型可以只写类型名称,另一个例外是mscorlib.dll,这个装配件中声明的类型也可以省略装配件名称(.Net装配件编译的时候,默认都引用了mscorlib.dll,除非在编译的时候明确指定不引用它),比如:
          System.String是在mscorlib.dll中声明的,上面的Type   t   =   Type.GetType(“System.String”)是正确的
          System.Data.DataTable是在System.Data.dll中声明的,那么:
Type.GetType(“System.Data.DataTable”)就只能得到空引用。
          必须:
Type   t   =   Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0,   Culture=neutral,   PublicKeyToken=b77a5c561934e089");
          这样才可以,大家可以看下面这个帖子:
                http://expert.csdn.net/Expert/topic/2210/2210762.xml?temp=.1919977
          qqchen的回答很精彩


5、如何根据类型来动态创建对象
        System.Activator提供了方法来根据类型动态创建对象,比如创建一个DataTable:

Type   t   =   Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0,   Culture=neutral,   PublicKeyToken=b77a5c561934e089");

DataTable   table   =   (DataTable)Activator.CreateInstance(t);

    例二:根据有参数的构造器创建对象
namespace   TestSpace   {
public   class   TestClass
{
      private   string   _value;
      public   TestClass(string   value)   {
_value=value;
      }
}
}

Type   t   =   Type.GetType(“TestSpace.TestClass”);
Object[]   constructParms   =   new   object[]   {“hello”};   //构造器参数
TestClass   obj   =   (TestClass)Activator.CreateInstance(t,constructParms);

把参数按照顺序放入一个Object数组中即可


6、如何获取方法以及动态调用方法
namespace   TestSpace
{
      public   class   TestClass   {
          private   string   _value;
          public   TestClass()   {
          }
          public   TestClass(string   value)   {
_value   =   value;
          }

          public   string   GetValue(   string   prefix   )   {
if(   _value==null   )
        return   "NULL";
else
        return   prefix+"   :   "+_value;
            }

            public   string   Value   {
set   {
_value=value;
}
get   {
if(   _value==null   )
return   "NULL";
else
return   _value;
}
            }
      }
}

        上面是一个简单的类,包含一个有参数的构造器,一个GetValue的方法,一个Value属性,我们可以通过方法的名称来得到方法并且调用之,如:

//获取类型信息
Type   t   =   Type.GetType("TestSpace.TestClass");
//构造器的参数
object[]   constuctParms   =   new   object[]{"timmy"};
//根据类型创建对象
object   dObj   =   Activator.CreateInstance(t,constuctParms);
//获取方法的信息
MethodInfo   method   =   t.GetMethod("GetValue");
//调用方法的一些标志位,这里的含义是Public并且是实例方法,这也是默认的值
BindingFlags   flag   =   BindingFlags.Public   |   BindingFlags.Instance;
//GetValue方法的参数
object[]   parameters   =   new   object[]{"Hello"};
//调用方法,用一个object接收返回值
object   returnValue   =   method.Invoke(dObj,flag,Type.DefaultBinder,parameters,null);

        属性与方法的调用大同小异,大家也可以参考MSDN

7、动态创建委托
        委托是C#中实现事件的基础,有时候不可避免的要动态的创建委托,实际上委托也是一种类型:System.Delegate,所有的委托都是从这个类派生的
        System.Delegate提供了一些静态方法来动态创建一个委托,比如一个委托:

namespace   TestSpace   {
      delegate   string   TestDelegate(string   value);
      public   class   TestClass   {
public   TestClass()   {
                  }
                  public   void   GetValue(string   value)   {
                          return   value;
                  }
        }
}

使用示例:
TestClass   obj   =   new   TestClass();

//获取类型,实际上这里也可以直接用typeof来获取类型
Type   t   =   Type.GetType(“TestSpace.TestClass”);
//创建代理,传入类型、创建代理的对象以及方法名称
TestDelegate   method   =   (TestDelegate)Delegate.CreateDelegate(t,obj,”GetValue”);

String   returnValue   =   method(“hello”);

 

        到这里,我们简单的讲述了反射的作用以及一些基本的用法,还有很多方面没有涉及到,有兴趣的朋友可以参考MSDN。

2006年05月07日

   通用类型系统(Common Type System,以下简称CTS,有的地方也叫公共类型系统),算是个老话题了,但对于部分.NET程序员来说,可能还是需要这方面的知识。加上CTS.NET里各种语言的基础,为了更好的进一步学习各种语言特性以及.NET Framework,也不得不了解CTS。我近期也在准备着一些.NET入门到提高学习资料,所以顺便整理下相关资料贴出来供大家参考(有可能的话,会按我整理的从入门到提高的系列一篇篇贴出来,不过就是要等待些时间),哪怕你对其中一点有心领神会的感觉,那也就够了。OK,转入正题。

一、CTS简介

我们都知道,编程语言发展至今已经有几百种,面对这么多的编程语言,经常有人陷入类似生死抉择的痛苦,但是有点体会的程序员总会说:语言是互通的,只要你掌握了编程思想,遵循相应编程规范,就不用怕今天又出现了多少新语言。我想这句话确实是有道理的。在这众多语言的背后,也隐藏着某种共性,不仅在语言本身,也在使用语言的方法上。就象大家常理解程序=算法+数据结构一样,我们也可以这样理解语言的共性,即语言=语法+语义。单纯字面上理解(不涉及编译等具体技术),语言实际上就是用各种事先定义好的关键字以一种特定组合方式起来的表现形式,用以定义数据以及描述对数据的操作。
   
了解了语言的概念,让我们思考下,是否存在一种东西(请原谅我用东西这个词)满足所有语言的共性?假如众多语言有了一种共性,是否可以进一步让众多语言之间实现互操作?再者,结合面向对象里的单根对象层次观点让每个对象最终都有相同的基类型,是否就又有了类型的统一功能?接着在统一类型基础上,又有了统一的调试方式、统一的体系框架、统一的开发手段等等呢?好了,再说下去都成完美神话了。上面的东西,实际上就是CTS。也正因为有了CTS,才有了.NET统一的诸多特性,把CTS说成CLRCommon Language Runtime 通用语言运行库)的核心,编程人员必须掌握的特性一点也不为过。
   
下面引用MSDN里的CTS定义让大家有更清楚了解CTS,毕竟MSDN是权威:

通用类型系统定义了如何在运行库中声明、使用和管理类型,同时也是运行库支持跨语言集成的一个重要组成部分。通用类型系统执行以下功能:

  • 建立一个支持跨语言集成、类型安全和高性能代码执行的框架。

  • 提供一个支持完整实现多种编程语言的面向对象的模型。

  • 定义各语言必须遵守的规则,有助于确保用不同语言编写的对象能够交互作用。

清楚了CTS的概念与优点后,我们更进一步来了解CTS。按照上面所讲的语言与CTS的关系,我们可以把CTS看成是.NET是所有语言的超集,也就是说各种符合CTS的语言,实际上都是CTS的一个子集。用数学的集合观点理解就是如下图:

    从上图还可以看出在各种语言之间还有个交集——通用语言规范(Common Language Specification,简称CLS),这里也简单说下。我理解中,CTS是一套类型系统,而CLS则是事先定义好的一种规则,遵循这种规则编写的任何代码都可以实现跨语言的互操作性。事实上,通用语言基础结构(Common Language Infrastructure, 简称CLI,是CLR的子集。CLRCLI的关系类似CTSCLS的关系。)正是使用CLS来让各种不同的语言使用.Net框架各种资源。在实际应用上,对于CLS,如果我们项目里都只用一种语言,例如C#,那就没必要过度关心CLS。相对地,如果是多种语言结合开发,或者希望你开发的类库能让各种不同语言调用时,那么CLS绝对是你需要进一步了解的内容。

二、了解CTS前的准备

1、 首先,说说堆(heap)与栈(stack,也叫堆栈),理解了最起码的堆栈概念,对于理解CTS绝对是必要的,而在最近给公司新员工培训时,发现他们根本不了解这块,所以我也觉得有必要单独再稍微说明下。对于heapstack,简单的来讲,stack上分配的内存系统自动释放,heap上分配的内存,系统不释放,哪怕程序退出,那一块内存还是在那里。stack一般是静态分配内存,而heap上一般是动态分配内存。也正因为它们这种特性,在.NET里,对于托管堆(managed heap)采取了通过垃圾回收器(Garbage Collection,简称GC)进行自动回收内存操作。下面是stackheap的对比说明,可以让你更好理解它们:

l      Stack:堆栈本身就具有回收特性,每当程序执行到区块的结束点时,该区块的内存就自动被释放(Last-In-First-Out,简称LIFO,后进先出),这让我们没必要担心程序里使用了多少变量,因为变量在区块外立即自动被释放,保证了一直有足够内存空间。堆栈虽好,但可惜的是并不是所有信息都可以存储在堆栈中。例如我们经常使用的string类型变量,之所以把string变量存储在Heap中,是因为字符串的长度是可变的,无法直接在堆栈中分配内存空间,因为堆栈是静态分配内存的。

l      Heap:堆上的变量可以在任何地方被创建,在.NET里托管堆通过GC进行回收,至于回收方式,GC是通过标志方式进行回收的,即若有堆中的变量被标志为待回收,则未来GC会释放该内存空间。当然GC的回收远远不是这么简单,这里略过(有兴趣可以查看SDK文档或《深入理解.NET内存回收机制》一文)。

2、接着看下C#预定义类型:

运行时类型

C#预定义类型

描述

System.SByte

sbyte

8位有符号整数

System.Byte

byte

8位无符号整数

System.Int16

short

16位有符号整数

System.UInt16

ushort

16位无符号整数

System.Int32

int

32位有符号整数

System.UInt32

uint

32位无符号整数

System.Int64

long

64位有符号整数

System.UInt64

ulong

64位无符号整数

System.Char

char

一个16Unicode字符

System.Single

float

单精度32位浮点数

System.Double

double

双精度64位浮点数

System.Boolean

bool

布尔值 true/false

System.Decimal

decimal

128位数据类型

System.Object

object

根类

System.String

string

Unicode字符串

.NET里,所有类型都是对象,而且都直接或间接继承自System.Object类。因此,例如我们声明并创建一个 int 类型时,实际创建了System.Int32的一个实例。例如:

1

public int i = 999;

2

public int j;

上面第1行代码,表示声明了一个 int 类型的变量 i,并赋值999。其实际是创建了 System.Int32 的一个实例,在堆栈Stack(为什么不是在heap堆上创建呢?这个后面会讲到是因为值类型和引用类型的区别)上分配一个内存空间存储了值999

而第2行代码,只是声明了j,而没有进行赋值操作。在C#里,跟C++等不同的是,当你仅仅进行声明变量时,所发生的事就仅仅是“声明”,而没有进行实例的创建,也就没有进行内存空间的分配。例如,你声明了如下语句:

1

System.Web.UI.Page page;

上面仅仅就是个声明而已,没有真正创建page实例,也就是说没有在内存空间里实际进行内存的分配。而你要进行创建实例,并进行内存分配,有且仅只有通过

1

System.Web.UI.Page page = new System.Web.UI.Page();

进行创建。也正因为这样,我们可以通过ILDASM看到上面声明的 j 实际是没有进行内存分配存储的。这也算是C#的一个新特征。这里额外也说了下。

下面是三个概念的理解:

l      Managed Heap: 托管堆。是由CLR动态分配的连续的内存空间,在执行阶段由GC负责管理。整个进程共用一个托管堆。

l      Call Stack: 这是由CLR在执行时自动管理的内存空间,每个线程都有自己的Call Stack。每调用一次方法,Call Stack就会多一次记录,调用完毕该记录随即被丢弃。

l      Evaluation Stack:这是由CLR在执行时自动管理的内存空间,每个线程都有自己的Evaluation Stack。事实上,我们在CTS里常说的线程堆栈指的就是它。

2006年05月05日

  这次我们首先讲解一下类型转换,我们在写程序时经常用到类型转换,而且特别多的规则。我在这里粗略的讲解一下。
  隐式转换是系统默认的、不需要加以声明即可进行的转换。
  1.隐式数值转换
  隐式数值转换实际上就是从低精度的数值类型转换到高精度的数值类型的转换。byte x=255;ushort y=x;y=65535;float z=y;//均从低精度到高精度,反之会产生溢出

  隐式数值转换的类型太多,我就不多介绍,记住上面的原则就可以了。详细规则可查看msdn
  2.隐式枚举转换
  隐式枚举转换用于把十进制整数0转换成任何枚举类型,对应的其他整数则不存在这种转换。

using System;
enum Color
{
    Red,Green,Blue
};
class MikeCat
{
     static void Main()
     {
          Color c;//声明Color的变量c;
          c=0;//将0转换为Red;
          Console.WriteLine("c的值是{0}",c);//结果:c的值是Red;如果将c=0改成c=1,则编译器会给出错误。
      }
}

  3.隐式引用转换
    从任何引用类型到object的转换。
    从类类型A到类类型B的转换,其中类A从类B派生得到。
    从类类型A到接口类型B的转换,其中类A实现了接口B。
    从接口类型A到接口类型B的转换,其中接口A是从接口B派生。
    从任何数组类型到System.Array的转换。
    从任何委托类型到System.Delegate的转换。
    从任何数组类型或委托类型到System.ICloneable的转换。
    从空类型(null)到任何引用类型的转换。
  显示转换也称为强制转换,它需要用户明确地指定转换的类型。

char c=(char)65;//A
int i=(int)

namespace CBL

            public class c1
            {
                     public c1()
                     {
                     }
                     public int c11 = 0;
             }

            public class c2:c1
             {
                     public c2()
                      {
                      }
                     public int c21 = 1;
                     public static void Main(string[] args)
                     {
                               c2 a = new c2();
                               c1 b = a; //b引用了a对象变量在堆中的内存地址 派生类向父类进行隐式转换
                               Console.WriteLine(a.GetType()); //显示a的类型为c2
                               Console.WriteLine(b.GetType()); //显示b的类型为c2
           
                               a.c11=99; //在堆中改变a变量c11字段的值
                               Console.WriteLine(a.c11); //输出99
                               Console.WriteLine(b.c11); //b引用a的内存地址 同样输出99

                               b = new c1(); //在内存堆中为b新建了一段c1类型的对象实例值的地址 此时b不在引用a的内存地址
                               Console.WriteLine(a.GetType()); //显示a的类型为c2
                               Console.WriteLine(b.GetType()); //显示b的类型为c1
           
                               a.c11=99; //在堆中改变a变量c11字段的值
                               Console.WriteLine(a.c11); //输出99
                               Console.WriteLine(b.c11); //b不引用a的内存地址 输出0
                      }
             }
}