转载:Microsoft.NET框架程序设计要点笔记

Microsoft.NET框架程序设计


第一章:Microsoft.NET框架开发平台体系架构

  1. 在C#中使用internal将使得它仅对于其定义的程序集中的代码可见。(这在C#中也是默认的)
  2. .NET中的的DLL特指程序集文件的一种形式.
  3. IL有时也被称作托管代码.除了产生IL外,所有面向LCR的编译器都要为托管模块产生完整的元数据.简单的说,元数据就是一个数据表的集合.
  4.  CLR直接和程序集打交道,程序集是一个或多个托管模块,以及一些资源文件的逻辑组合
  5. 定义一个类型的完整方法:
    using System;
    class Test{
        class TestChild{}  //嵌套类
        public Test(){}  //构造器
        static Test(){}  //类型构造器
        ~Test(){}   //析构器
        public static Boolean operator == (Test t1,Test t2){}  //重载操作符
        public static Boolean operator !=(Test t1,Test t2){}    //重载了==号就必须重载!=
        public String AProperty{     //属性
              get{ return null; }
              set{}
        }
        public String this[Int32 x]{       //索引器
            get{return null;}
            set{}
        }
        event EventHandler AnEvent;       //事件
    }

第二章:生成,打包,部署及管理应用程序与类型
  1. 类型生成模块,模块组合成程序集.
  2. 程序集是包含一个或多个类型定义文件和资源文件的集合.在程序集包含的所有文件中,有一个文件用于保存清单.
  3. 程序集是CLR操作的对象.程序集是一个可重用,可实施版本策略和安全策略的单元.
  4. 私有部署程序集仅仅需要简单的复制和删除文件即可以.

 

第三章:共享程序集

  1. 强命名程序集有一个发布者的公钥/私钥对签名,基中的公钥/私钥对惟一的标识了程序集的发布者。一个程序集有两种部署方式:即私有方式和全局方式。非强命名程序集只能进行私有方式的部署。
  2. 我们可以调用GACUtil.exe并指定/i命令行开关来将一个程序集安装到GAC中。也可以用GACUtil.exe的/u命令行开关来将一个程序集从GAC中卸载掉。注意不能将一个非强命名程序集安装到GAC中。

 

第四章:类型基础

  1. 将对象转换为它的任何派生类型时,C#要求进行显式转型。(eg: object --> int).而将类型转换为它的基类型时,可以隐式转换.(eg:int-->object)
  2. System.Object的公有方法
    公有方法
    描述
    Equals
    如果两个对象具有相同的,方法将返回true.
    GetHashCode
    方法返回对象的散列码,如果一个对象被用作散列表的一个键值,那么该对象应该重写GetHashCode方法.该方法应该为类型的不同对象之间提供一个良好的区分.
    ToString
    默认情况下,方法返回类型的全名(this.GetType().FullName.ToString())  一般都会重写.
    GetType
    方法返回一个类型为继承自Type的对象实例,标识了该方法所属对象的类型.返回Type对象可以和反射类一起使用来获得类型的元数据信息.

  3. is和as运算符都不会抛出异常:如果对象为null那么is运算符总是返回false,而as将返回null.除了不会抛出异常外,as操作符所做的和通常的转型操作没什么不同,如果对象不能顺利转型,as操作符结果将为null.
  4. CLR实际上对命名空间一无所知。当我们访问一个类型时,CLR需要知道该类型的完整名称。所以有可能发现不同的命名空间中相同的类名的情况,这种情况唯一的解决办法就是提供完整名称。
  5. 尽量不要使用只区别大小写的字段,虽然CLR可以区别它们,C#也能区分它们,但有的语言如VB不区分大小写,这样有可能会引用误解。
  6. 命名空间和程序集之间并非必然相关的关系.

 

第五章:基元类型、引用类型与值类型

  1. 基元类型与相映射的FCL类型名是完全一样的,没有任何区别的。(推荐完全使用FCL类型名而不是基元类型名?但是C#编程规范推荐使用关键字的!)
  2. 使用引用类型名而不是基元类型名。例如:使用Int32而不是int
  3. 运 算可能发生溢出时应该显式的使用checked或unchecked。对于没有使用checked和unchecked的运算,表明在应用程序的开发阶段 希望出现溢出时能够抛出异常,而在应用程序发布以后,便不希望再做溢出检查。(开发程序时,应该使用编译器的/checked+命令行开关,在程序发布以 后,应该使用编译器的/checked-命令行开关。)
  4. 因为Checked操作符和语句只影响加,减,乘及转换IL指令产生的版本,在checked操作符或语句调用一个方法并不会对该方法产生任何影响.
    checked{
       SomeMethod(500);        //这个方法里发生的溢出将检测不到.
    }
  5. 当将一个值类型变量赋值给另一个值类型变量时,会进行一个“字段对字段”的拷贝。但当将一个引用类型变量赋值给另一个引用类型变量时,只会拷贝内存地址。一个是地址,一个是值.
  6. 装箱操作:
    1).从托管推中为新生成的引用类型对象分配内存.
    2).将值类型的字段拷贝到托管推上新分配对象的内存中.
    3).返回托管堆中新分配对象的地址.
  7. 拆箱操作一个陷阱示例:
    static void Main(){
        Int32 x=5;
        Object o = x;
        Int16 y = (Int16) o; //注意:此处会引发一个InvalidCastException异常!  应改为:Int16 y =(Int16)(Int32) o;//先拆箱为正确的类型,再转换
    }

第六章:通用对象操作
  1. 引用类型继承的Equals判断的是引用相等(如果是同一个对象的二份copy也会返回False),而值类型继承的Equals判断的是值相等。
  2. Int32 x=5;
    Object.ReferenceEquals(x,x);  //返回False,因为ReferenceEquals接受Object参数,所以x被执行了二次装箱操作,分别为两个不同的引用类型.
  3. 如果希望实现深copy,应该实现ICloneable接口,实现它的Clone()方法.


 

第七章:类型成员及其访问限定

  1. 属性仍是一种方法,它以一种简单的、类似字段的方式实现了设置、或者查询一个类型或对象的状态,与此同时它又可以很好地保护它们的状态不会被破坏。
  2. 类型构造器也是一种特殊的方法,它用来将一个类型的静态字段初始化到正常的初始状态.
  3. readonly字段仅可以在构造器方法中被赋值。而静态字段static是类型状态,而非对象状态的一部分。
  4. 字段的预定义特性readonly使字段仅可以在构造器方法中被赋值!
  5. 静态方法不能访问类型中的实例字段或实例方法,因为静态方法对对象的状态一无所知。
  6. 虚方法被调用时,无论对象是否被转换为其基类型,都只有位于对象继承链最未端的方法实现会被调用。仅能用于实例(非静态)方法。
  7. 在子类中重写虚方法时,使用new表示将基类中的方法隐藏起来;override表达将基类中的方法重写。使用new时将对象转换为基类型还可以调用基类的方法,而override不能!

 

第八章:常数与字段

  1. 常数必须能够在编译时确定它的值,意味着常数类型只能是那些编译器认为的基元类型。
  2. 常数与静态字段有什么区别?(静态字段可以在方法中重新赋值)
  3. 在构造器的内部只读字段却可以被多次赋值,另外:静态只读字段则只能在静态构造器内赋值,静态构造器在该类型初次被引用时执行.

 

第九章:方法

  1. 实际上,即使实现了值类型无参的默认构造器,很多编译器也不会自动产生代码来调用它。要使一个值类型的无参构造器执行,开发人员必须显式的调用它(C#中不允许值类型的无参构造器)。
  2. 严格的讲,只有在值类型字段被嵌入到一个引用类型中时,CLR才保证值类型字段被初始化为0或NULL。基于堆栈的值类型字段一般不能保证得到0或null。
  3. C#不允许值类型有无参的构造器。但是值类型有允许有带参的构造器,在带参的构造器中所有的字段必须被完全赋值
  4. 默认情况下,一个类型中没有定义类型构造器(静态构造器),如果要定义,也只能定义一个,并且,类型构造不能有任何参数。
  5. 类型构造器不应该调用其基类型的类型构造器,不需要这样做是因为基类型中的静态字段并没有被派生的类型所继承。
  6. 当编译器为类型构造器产生IL代码时,它首先会产生初始化静态字段所需的代码,然后才是类型构造器方法中显式包含的代码转换而得的IL指令。 因此:class SomeType{
       static Int32 x=5;
       static SomeType(){ x=10; }
      }    该构造器首先将x初始化为5,然后又将其改写为10
  7. 引用参数中关键字out和ref的不同之处在于那个方法负责初始化参数。如果一个方法的参数被标识为out那么调用代码在调用该方法之前可以不初始化。
  8. CLR允许根据out和ref参数来重载方法,例如,上面代码就合法:
    class Point{
    static void Add(Point p) { ... }
    static void Add(ref Point p) { ... }
    } 但是仅通过区分out和ref来重载方法又是不合法的
  9. 可变数据参数:只有方法的最后一个参数才可以用params关键字(即ParamArrayAttribute特性)来标记。该参数必须为一个一维数组,但类型可以任意。
  10. 仅返回值的不同是不能够区分两个方法的。
  11. 使用new关键字告诉编译器,这个方法与基类的方法没有关联。

 

第十章:属性

  1. 属性允许源代码以一种简化的语法来调用方法,CLR提供了两种属性,无参属性和含参属性。无参属性在C#中称为索引器。
  2. 设计类型时,将所有的字段访问限制都设为私有方法,或者最少是保护方法--永远不要设为公有方法,然后再以方法的形式让用户读取或者设置对象的状态信息。
  3. C#支持虚属性.

 

第十一章:事件

  1. 发布事件:
    1.  定义一个事件参数类型来传递事件的参数
    2.  定义一下委托来定义必须实现的回调方法原型
    3.  定义一个事件成员
    4.  定义一个受保护的虚方法通知事件的登记对象
  2. 在方法前加上[MetodImplAttribute(MethodImplOptions.Synchronized)]将方法定义为同步方法是线程安全的。
  3. 显式控制事件注册
    public event EventHandler _event{
                add { Delegate.Combine(); }          
               remove{ Delegate.Remove();  }
            }
  4. 一个类型中定义多个事件,应该使用一个散列表来保存一个事件/委托的集合.这时应该遵循特定的设计模式!见书要详细描述,不过应该使用机会特别少吧.


第十二章:文本处理

  1. 由于存在着字符串驻留技术,所以
    String s="Hello";
    Console.WriteLine(Object.ReferenceEquals("Hello",s));
    将返回true因为在建立第二次"Hello"字符串时,系统会使用第一次建立的那个字符串以提高性能.
  2. 任何能够解析一个字符串的类型都提供有一个名为Parse的公有静态方法.该方法接受一个String,并返回一个类型的实例.


第十三章:枚举类型与位标记

  1. 枚举类型是强类型的,所以应该尽量多使用枚举类型的。
  2. System.Enum类型的静态方法GetValues(),该方法允许我们获取一个枚举类型中定义的所有符号。同时还有一个静态的方法GetNames()
  3. Enum的静态方法Parse()可以将一个文本符号转换成一个枚举类型的实例.同时还有一个IsDefined的静态方法.
  4. 虽然枚举类型和位标记有些类似,但是它们具有不同的语义.例如,枚举类型表示单个的数值,而位标记表示一组标记,其中有些位为真,有些位为假.
  5. 位标记的定义
    [Flag]    //定义为位标记也可以使用[FlagAttribute]
    enum Actions{
        Read=0x0001,
        Write=0x0002,
        Delete=0x0004,
        Query=0x0008,
        Sync=0x00010
    }
  6. 位标记的使用
    Actions actions=Actions.Red | Actions.Write;    //0x0003
    Console.WriteLine(actions.ToString());    //"Read,Write"
  7. 可以将位标记看当特殊的枚举,所以Enum的那些静态方法位标记也可以使用.对于位标记,Parse方法中的字符串系统会自动拆开后进行位运算.

 

第十四章:数组

  1. 交错数组也就是数组的数组.
  2. 尽量使用0基数组,因为0基数组的性能被优化的最好.
  3. Array的静态方法CreateInstance()方法可以动态的创建数组.

 

第十五章:接口

  1. 接口意味着一个对象可以属于多个类型,这样会非常有用的.实现了多个接口的类型允许我们将它的对象看作这些接口中的任何一个.
  2. 类型必须实现所继承的接口中的所有的方法.记住,是所有的.每一个方法的实现都必须使其签名和接口的方法签名完全匹配.
  3. 选择设计一个基类型,还是一个接口类型:
    IS-A与CAN-DO的关系;基类型比接口稍微容易实现一些;在多版本的冲突上基类型比接口要更好一些,新版本的基类型不需要其派生类型重新编译源代码,而接口则要求所有的派生类型必须修改它们的源代码并重新编译.
    接口主要适用于操作比较一致,但是可重用代码并不多的地方.
  4. 当使用跨程序集的类型时,应该需要对程序集的版本问题给予足够的重视.
  5. 实现多个有相同方法的接口,这种时候使用显式的实现接口.C#编译器在辨析接口成员实现时,会按照"先完全限定接口成员,后非完全限定接口成员"的顺序进行辨析.
  6. 显式实现接口:
    struct SomeValueType : IComparable{
        private Int32 x;
        public SomeValueType(Int32 x) {this.x=x;}
        public Int32 CompareTo(SomeValueType other){
            return (x-other.x);
        }
        //注意:该方法没有被标记public / protected        这里如果加上访问修饰符,C#编译器会产生错误的
        Int32 IComparable.CompareTo(Object other){
            return CompareTo((SomeValueType) other);
        }
    }
    这种实现接口时限定了CompareTo只接受SomeValueType型的参数,保证了类型安全.
  7. 显示实现的接口方法不能被标记为public protected是因为在SomeValueType时该方法是私有的,而在将其转换成IComparable后该方法又变成是public的.
  8. 谨慎使用显式接口方法实现.

 

第十六章:定制特性

  1.  关于定制特性,首先知道的应该是它们仅仅是为目标元素提供关联附加信息的一种方式.编译器的工作只是将这些附加的信息存 放在托管模块的元数据中而已.大多数特性对编译器来说没有任何的意义,编译器只是检测源代码中的定制特性,然后产生相应的元数据.我们应该在自己的程序方 法中检测定制特性并执行不同的方法.
    定制特性只是一个标记,仅些而已!
  2. C#只允许将特性应用于定义了以下构造的源代码中:程序集,模块,类型,字段,方法,方法参数,方法返回值,属性,以及事件.
  3. 定制特性必须直接或间接继承自System.Attribute
  4. 定义一个定制特性的代码:
    namespace System{
         public class FlagsAttribute : System.Attribute{
              public FlagsArrtibute(){
              }
         }
    }
  5. 一个特性其实就是类型的一个实例,该类型必须有一个公有的构造器来创建它的实例.
  6. [DllInport("Kernel32", CharSet=CharSet.Auto, SetLastError=true)]这种语法允许在DllImortAttribute对象构造之后设置它的公有字段或属性.这种设置字段或属性的"参数"被称为命名参数.
  7. 在一个目标上应用多个特性时,顺序可以任意.绝大多数特性不允许重复使用.
  8. 所有的非抽象特性都必须具有public访问限制.名称都应该以Attribute后缀,都必须包含至少一个公有构造器.
  9. 定义自己的特性类型时,可以定义一个带有参数的构造器,这些参数是开发人员在应用这些特性实例时必须指定的.另外可以定义非静态的公有字段和属性,它们标识一些开发人员在应用特性实例时可以有选择地指定的一些设置.
  10. 定 义一个特性类型的实例构造器,字段和属性时,它们的类型被严格的限制为:Boolend,Char,Btye,SByte,Int16,UInt16, Int32,UInt32,Int64,UInt64,Single,Double,String,Type,Object或枚举.
  11. 在对定制特性完成初始化后,编译器会将其序列化到目标元素的元数据表内对应的条目中.
    定制特性可以看作是一些被序列化为字节流的类型实例,这些字节流在编译时被存储在生成模块的元数据中.到了运行时,CLR便通过对元数据中的字节流执行反序列化操作来构造特性类型的实例.
  12. System.Attribute 类型提供了三个方法来检查是否使用了某个特性:IsDefined, GetCustomAttributes, GetCustomAttribute.其中IsDefined的效率最高,如果只需要检查是否应用了某个特性,则应该使用IsDefined.因为它不 会构造特性对象,而其它的二个方法会构造特性对象.
    GetCustomAttributes通常用于将AllowMultiple设定为true的特性,GetCustomAttribute通常用于将AllowMultiple设定为false的特性.
  13. 使用Attribute.GetCustomAttribute()可以得到定制特性的一个实例.再对个实例进行特性类中定义的全部操作.
  14. 伪定制特性是一种以特殊的方式保存在元数据中的特性.IsDefined,GetCustomAttribute,GetCustomAttributes不适用于伪定制特性.

第十七章:委托
  1. 委托确保类型安全,集成了按序调用多个方法的能力,并同时支持静态方法和实例方法.
  2. 委托表示一个回调方法的签名.
  3. 编译器在后台会被委托转化成类,编译器实质上是在处理类.
  4. public delegate void Feedback(Object value, Int32 item, Int32 numItems);
    将被转化为:
    public class Feedback : System.MulticastDelegate{
        public Feedback(Object target, Int32 methodPtr);
        public void virtual Invoke(Object value, Int32 item, Int32 numItems);
        public virtual IAsyncResult BeginInvoke(Object value, Int32 item, Int32 numItems, AsyncCallback callback, Object object);
        public virtual void EndInvoke(IAsyncResult result);
    }
  5. 类和委托都是可以嵌套的.
  6. 每个委托对象实际上是对方法及其调用时操作的对象的一个封装.MulticastDelegate类定义了两个只读公有实例属性:Target和Method.
    Target属性返回一个方法回调时操作的对象的引用.如果是静态方法,Target返回null.
    Method属性返回一个标识回调方法的System.Reflection.MethodInfo对象.
  7. 字段
    类型
    描述
    _target
    System.Object
    指向回调函数被调用时应该被操作的对象,该字段用于实例方法的调用.
    _methodPtr
    System.Int32
    一个内部的数值(System.IntPtr)CLR用它来标识回调方法.
    _prev
    System.MulticastDelegate
    指向另一个委托对象,该字段通常为null
  8. 当调用委托时,实际上是在调用产生的委托类的Invoke方法,VB可以显式调用,C#不允许显式调用.
  9. 委托基类重写了Equals方法及==,!=的运算符重载,只要二个委托指向的是同一个方法,将返回true.而不是象引用类型一个只要不是同一个对象即返回false.
    实现机制:在比较_target和_methodPtr这二个字段相同后,比较_prev字段不为null且指示的键表有相同的长度,那么返回true否则返回false.
  10. 调用一个委托会导致它前面的委托首先被调用.应该程序代码只保留了从链表头部的那个委托调用(最后一次调用)中返回的值.
  11. MulticastDelegate类提供了一个实例方法GetInvocationList,这个方法可以得到委托链表上的每一个委托对象.这样我们可以使用自己的方法显式的调用它们.
    这样做的一些用途包括(得到委托链上所有回调方法的值、异常处理、异步处理、特殊的调用顺序等...)
  12. 在不知道回调方法的参数信息时也是可以使用委托的,System.Delegate提供了几个方法允许在编译时不知道参数信息时也能创建并调用委托.(具体怎么实现没看懂,需要用到的时候再仔细研究吧)

第十八章:异常
  1. 在Catch()块中尽管可以改变所捕获的异常对象,但是并不应该这么做,而应该把它当成是一个只读对象.
  2. 在catch块的未尾,一般有三种选择:
    1)重新抛出所捕获的异常,向更高一层的调用堆栈中的代码通知该异常的发生
    2)抛出一个不一样的异常,向更高一层的调用堆栈提供更多的有关异常的信息(我们重新抛出的异常应该是一个更具体的异常)
    3)让线程从catch块的底部退出
  3. finally块中的代码一般是清理代码,它们只应该撤销try块中发生的操作.应该避免把那些可能抛出异常的代码放在finally中.
  4. 异常的经典定义 : 异常是对程序接口隐含假设的一种违反
  5. 在设计一个类型时,我们应该首先假设类型最觉的使用方式,然后设计其接口使之能够很好地处理这种情况.最后再考虑接口带来的隐含假设,并且当任何这些假设被违反时便抛出异常.
  6. CLR抛出的一些异常在应用程序中是无法捕获的.例如OutOfMemoryException,StackOverflowException,ExecutionEngineException
    若是应用程序抛出的OutOfMemoryException,StackOverflowException是可以捕获的,任何捕获到的StackOverflowException都应该重新抛出以中断进程.
  7. 异常的层次结构的好处就是我们的代码可以方便的捕获一组相关的异常类型.
  8. 我们自己在程序中抛出的异常应该尽可能的详细.应该抛出一个具体的异常类型.
  9. 所有的异常类型都应该是可序列化的.这样才能在跨越应用程序域时或机器边界时得到封送处理,并在客户端重新抛出.
    如果异常类继承的基类实现了ISerializable接口,而本身又添加了新的字段时,就必须实现ISeriaizable接口的GetObjectData方法和一个特殊的受保护的构造器.
    以下是完整的,正确的定义了一个异常类:
    using System;
    using System.IO;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Formatters;
    [Serializable]  //可序列化的
    sealed class DiskFullException : Exception, ISerializable  //定义为密封类
    {   //三个公有的构造器
        public DiskFullException() : base() { }     //调用基类的构造器
        public DiskFullException(String message) : base(message) { }        //调用基类的构造器
        public DiskFullException(String message, Exception innerException) : base(message, innerException) { }  //调用基类的构造器
        private String diskPath;    //定义了一个私有字段
        public String DiskPath { get { return diskPath; } }     //封装了一个公有属性
        public override string Message{     //重写公有属性Message,将新加入的字段包含在内(属性也是可以重写的)
            get{
                String msg = base.Message;
                if (diskPath != null) msg += Environment.NewLine + "Disk Path:" + diskPath;
                return msg;
            }
        }
        //定义了至少一个新字段,所以要定义一个特殊的构造器用作反序列化.如不是密封类,应该定义为保护方式,这个特殊的构造器的参数类型是固定的.
        private DiskFullException(SerializationInfo info, StreamingContext context) : base(info, context) {
            diskPath = info.GetString("DiskPath");
        }
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { //实现接口,定义序列化方法
            info.AddValue("DiskPath", diskPath);
            base.GetObjectData(info, context);
        }
        public DiskFullException(String message, String diskpath) : this(message) { //定义构造器设置新字段
            this.diskPath = diskPath;
        }
        public DiskFullException(String message, String diskpath, Exception innerException) : this(message,innerException) {
            this.diskPath = diskPath;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            DiskFullException e = new DiskFullException("The disk volume is full", @"c:\");
            FileStream fs = new FileStream(@"Test", FileMode.Create);
            IFormatter f = new SoapFormatter();
            f.Serialize(fs, e);   //序列化
            fs.Close();
            fs = new FileStream(@"Test", FileMode.Open);
            e = (DiskFullException)f.Deserialize(fs);   //反序列化
            fs.Close();
            Console.WriteLine("Type:{1}{0} Diskpath: {2}{0}Message:{3}", Environment.NewLine, e.GetType(), e.DiskPath, e.Message);
        }
    }
  10. 编写类库时不应该捕获任何异常,应该留给调用这个类的程序去捕获.
  11. C#提供了lock和using语句以简便的方法实现了try()...finally()快.也就是说,使用lock和using时,即使内部产生了异常,也会释放资源.这只是编译器的一种简写形式.
  12. 永远不要去捕获Exception异常,特别是在程序的开发阶段.即使一定要捕获,也应该在做完相应的处理之后重新抛出.
  13. throw会将捕获的类型原样抛出.
  14. 重新抛出异常时,应该将原来的异常设置为抛出异常的内部异常,即innerException属性.因为这样不会丢失导致异常的真正原因.
  15. AppDomin.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(SomeMethodToDo)
    这是在当前的应用程序域上注册SomeMethodToDo,在应用程序出现未处理的异常时,这个方法将会得到调用.
  16. UnhandledExceptionEventArgs错误类型的IsTermination属性告诉我们CLR是否会因为这个未处理的异常而中断应用程序.通常,对手工线程,线程池线程,终止化器线程 CLR会忽略任何未处理的异常.
  17. System.Diagnostics.Debugger的静态方法Launch是启动调试器对当前的异常进行调试.
  18. Appllcationi类的ThreadException也处理CLS兼容的异常(WinForm程序).
  19. ASP.NET Web 窗体的未处理异常:
    1) 在某个特定的Web页面处理未处理的异常.登记一下System.Web.UI.TemplateControl类的Error的方法. TemplateControl类是System.Web.UI.Page和System.Web.UI.UserControl的基类.
    2) 应用程序级别的登记未处理异常的方法. 在Global.asax文件中登记System.Web.HTTPApplication类的Error事件.
  20. StackTrace属性可以方便的得到程序从异常抛出时到接受处理时的所有堆栈路径.

第十九章:自动内存管理(垃圾收集)
  1. 垃圾收集器是负责处理托管堆上的引用类型,而值类型是在所在方法的执行结束时,随着堆栈空间的消亡而自动消亡,也就无所谓回收了.
  2. CLR并不保证对象在一个方法的整个生存期内都能一直存活.但是在还有引用它之前它是一定存活的.所以可以添加特性阻止垃圾收集器在代码的引用类型对象的作用范围内运行时对它们进行垃圾收集(这点主要是调试时使用).
  3. 当垃圾收集器判定一个对象为可收集的垃圾时,它便会调用该对象的Finalize方法(如果存在的话).
  4. 因为System.Object中已经定义了一个Finalize虚方法,任何需要释放非托管资源的类型都要重写这个Finalize方法.该方法首先执行必要的清理操作,然后调用基类型的Finalize方法以使基类型也能够有机会释放其内的非托管资源.
  5. 因为很容易在Finalize中忘记调用其类的Finalize方法,所以C#有一个简单的方式实现即:
    ~OSHandel(){
         CloseHandle(handel);   //这种写法会确保进行了异常处理以及调用基类的析构方法.
    }
  6. Finalize方法只有在垃圾收集执行时才会被调用,它不会在一个方法退出或者对象超出作用范围时被调用,所以它的执行时间是不确定的!
  7. 我们应该尽可能的避免使用Finalize方法. 如果实现一个Finalize的方法最常见的原因便是释放对象所占有的非托管资源.
  8. CLR不对Finalize方法的执行顺序作任何保证.另外,在Finalize中的未处理异常,CLR会忽略它.
  9. 要提供显式释放或者关闭对象的能力,一个类型通常是实现IDisposable接口.
  10. 弱引用是一种很不错的特性,不过这种特性似乎在WinForm程序中使用的比较多一些,对Web程序应该不怎么使用吧.
    void SomeMethod()
    {
     3  Object o = new Object();
       WeakReference wr = new WeakReference(0);
       o = null;
       o=wr.Target;
       if (o == null)
       {    //出现过垃圾收集   }
       else {   //未出现垃圾收集   }
    }
    主要用于一些重新暂时不用,可以放弃,重新构造又需要比较长时间的对象.这样,如果在不需要的时间内,系统资源不紧张,下次返回时又可以重新引用.如果系统资源紧张,垃圾收集器也可以将其收回.下次重新建立.
  11.  ... 剩下的一些没有看了..感觉不是太重要,而且也很麻烦....  如果需要,以后再仔细看完这一章吧!!!!!!!!!!!!!!!!

第二十章:CLR寄宿、应用程序域、反射
  1. 在指定CLR的版本加载后,加载的也不一定是指定的版本,但是与这个版本兼容的版本.MSCorEE.dll会查看托管EXE文件,并提取当初生成和测试应用程序时使用的CLR的版本信息.但也可以通过XML配置文件来覆盖这一默认设置.
  2. CLR会在做初始化工作的时候创建一个应用程序域(AppDomain).
  3. 可以使用AppDomain的SetAppDomainPolicy方法来在应用程序域上应用某种安全策略.
  4. 对于中立域方式加载的程序集来说.CLR会为它们维护一个特殊的加载器堆.以中立域方式加载的程序集只有在进程中断时才会被卸载.
    中立域中的程序集可以被几个应用程序域所共用.如MsCorlib
  5. 对于以全传值方式进行运程传送的对象来说.对象的类型必须应用有System.Serizlizabel的定制特性.
  6. 线程和应用程序域没有一对一的关系,当一个应用程序域中的线程调用另一个应用程序域中的方法时,线程会在两个应用程序域间跳转.意味着跨越应用程序域边界的方法调用会被同步地执行.但在任何一个时刻,一个线程都被认为只存在一个应用程序域中.
  7. 应用程序域有很多事件可以使用:AssemblyLoad,DomainUnload,ProcessExit,UnhandledException,AssemblyResove,ResourveResove,TypeResove.
  8. 控 制台程序有WinForm程序进行时,先检查应该运行的CLR的版本,再检查程序的入口点.接着CLR调用入口方法.当引用到包含在其他程序集中的类型 时,CLR会定位必要的程序集,并将勘加载到同一个应用程序域中.任何另外间接被引用的程序集也会被加载到同一个应用程序域中.
  9. System.Environment的静态方法Exit是中断一个进程的最佳方式.
  10. 当客户端请求一个由ASP.NET ISAP DLL处理的URL时,ASP.NET会创建一个工作者进程(worker process)(ASPNet_wp.exe).工作者进程是一个寄宿有CLR COM服务器的Windows进程.
  11. 当客户端向一个Web应用程序发出一个请求时,ASP.NET会判断该请求是否为第一次,如果是,ASP.NET会告诉CLR为该Web应用程序创建一个新的应用程序域.
  12. 当更多的客户端向一个已经运行的Web应用程序发出请求时,ASP.NET不会再去创建新应用程序,相反,它会使用现有的应用程序域,创建一个新的Web应用程序类型的实例,并调用其中的方法.
  13. 不同的Web应用程序将加载于不同的应用程序域上.但是这些不同的应用程序域都在同一个Windows进程中,即ASPNet_wp.exe
  14. .net框架安装的MEME会让IE也变成一个CLR的宿主.同一个Web站点的所有程序集都被加载到属于自己的应用程序域中.
  15. 元数据的本质就是一大堆的表.因为它只是一堆表,所以完全可以读取出来的.
  16. 先绑定类型,然后调用方法的方式通常被称作晚绑定,而早绑定指一个应用程序使用的类型和方法在编译时就已确定.
  17. CLR加载一个程序集的时间是在一个方法被调用时,CLR会检查该方法的IL指令看其中引用了哪些类型,然后CLR会加载所有这些被引用类型所处的程序集.不会重复加载.
  18. AppDomain.CurrentDomain.GetAssemblies();  //当前应用程序域中的所有程序集
    Assembly.GetExecutingAssembly();  //得到当前正在运行的程序集
    Assembly.GetModules();  //实例方法,程序集的所有模块
    Module.GetType();   //实例方法,模块的所有类型
    Type.GetMembers();   //实例方法,类型的所有方法,有几个重载版本.
    Assembly.Load()   .LoadForm()  .LoadWithPartialName()  //显式加载程序集,  推荐Load()效率高 ,LoadForm将程序集当作数据文件处理(所以有可能会加载在不同路径的相同的程序集,Load()不会)
  19. 当应用程序域中的类型第一次被访问时,CLR会构造一个RuntimeType的实例.并初始化该实例字段来Reflect类型的信息.
  20. 由于应用程序域中的每个类型都只有一个对应的RuntimeType对象,所以我们可以使用判等和判异两个操作符来比较两个对象的类型是否相同:
    Boolean AreObjectsTehSameType(Object o1, Object o2){
         return o1.GetType()==o2.GetType();
    }    如果GetType在调用的程序集中没有指定的类型,GetType方法将检查定义在MSCorlib.dll中的类型.如果再没找到,返回null或抛出异常.
  21. 在C#中使用typeof()操作符代替GetType()方法.
  22. Type对象有很多属性有来判断这个对象的特征,如IsPublic,IsSealed,IsAbstract,IsClass,IsValueType等.还可以通过查询BaseType属性来获取类型的基类型.
  23. 获取一个Type对象来标识一个指向类型的引用.  Type stringType=Type.GetType("System.String&");
  24. 一个类型的成员可以是字段,构造器,方法,属性事件以及嵌套类型!
  25. 反射调用一个方法最方便的做法就是调用Type类型的InvokeMember方法.
  26. C#不允许一个类中不同种类的成员具有相同的名称,也就是说在一个类中定义同名的方法和字段将会出现编译错误.
  27. 要获得一个类型所实现的接口集合,可以调用Type的FindInterface,GetInterface,或GetInterfaces方法.
  28. 最后,尽量少使用反射!

posted on 2008-08-25 15:46  dinglin2006  阅读(849)  评论(0编辑  收藏  举报

导航