存档

2009年11月 的存档

csharp对象和类的学习笔记

2009年11月26日 邵 明博 没有评论

StructVsClass 150x107 csharp对象和类的学习笔记如左图所示(点击查看大图),首先这一章需要理清的一对关系是,结构和类。这里尤其需要说明的是:

  1. 虽然结构本身不支持继承,但隐藏在他背后的还是有一条鲜为人知的继承链:Struct继承于System.ValueType,而System.ValueType继承与System.Object。除此“一脉相传”之外,你不允许给他指定任何基类,也不允许从任何结构继承点什么。在我看来,这样的框架设计也正好符合了“万事万物皆对象”的原则。
  2. 结构的默认构造函数会将其申明的字段初始化为标准的默认值,直接对字段进行赋值初始化会遭到编译器的拒绝。但,重载构造函数可以绕过这个限制。(c# professional 2008 P155 Constructors for Structs在这一点上貌似提的有些问题)

ClassMembers1 150x150 csharp对象和类的学习笔记

在我早先的知识结构中,编程这块主要是C和Java。所以对于下面提到的一些问题会有点陌生:

函数成员->方法->传递参数->out关键字。与之相对的有一个ref关键字。ref挺好理解的,就是按引用传递,貌似c中的按地址传递参数。c#中,无论是值传递,还是引用类型传递,都需要先进行初始化。但真正在编程的过程中,初始化的数据往往是不重要的,甚至有的时候函数就没用过那些被初始化的数据,自然的不论是客户还是programer都希望看到的是计算后的那些amazing的,有意义的符号。out关键字,使得程序员能够摆脱这一初始化的僵局,当在方法参数中申明一个out变量时,编译器自动对其进行初始化,该变量通过引用传送。另外,在函数体内,如果未对out参数分配一个值的话,那编译器是不会通过验证的。当然,在调用该方法时,也需要继续指明out关键字。

函数成员->方法->方法重载(帅哥说咱这个阶段暂时只用记住2点:)

  • 两个方法不能仅仅只在返回类型上有区别。
  • 两个方法不能仅仅只在参数申明为out还是ref来区别。

函数成员->构造函数

有一个默默奉献的默认构造函数,不论你是否重构构造函数,还是压根没有动手描述它,它总是会隐式的自动调用,并为我们将字段初始化为标准的默认值,其功能也仅限于此。不过,下述情况是个意外。

把构造函数private化(也就是传说中的对象实例化的类代理),直接实例化是不可能实现的,因为在外部,new是没有办法使用的。一般来说,这样做是为了:

  • 作为储存静态成员或属性的容器。
  • 仅通过调用某个静态函数成员来实例化(可以保证在程序中只有一个该类的实例,需加锁)demo如下,注意观看getAtestClassInstance方法。
    class Program
    {
        static void Main(string[] args)
        {
            testClass t = testClass.getAtestClassInstance(100);
            Console.WriteLine(t.b);
            Console.WriteLine(t.c);
        }

        class testClass
        {
            private static int a;
            public int b;
            public int c;
            private static testClass t;
            private static object locker = new object();
            private testClass(int a)
            {
                testClass.a = a;
                c = a;
            }

            public static testClass getAtestClassInstance(int a)
            {
                if (t == null)
                {
                    lock(locker)
                    {
                        t = new testClass(a);

                    }
                }
                return t;
            }
        }
    }

静态构造函数,又是第一次遇到。我想事到如今,或许我们可以总结为:only one吗?请用static。如上述的getAtestClassInstance方法,保证只有一个实例运行。静态构造函数一般有这样几点值得注意和总结:

  • 静态构造函数保证至多运行1次,在代码引用类之前,demo如下。
  • 静态构造函数没有访问修饰符,没有其他的c#代码访问它,永远只有.net自己调用。
  • 静态构造函数不能带参数,一个类只能有一个静态构造函数。
  • 无参的实例构造函数与静态构造函数安全共存,因为二者的运行时间不同。
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(testClass.a);
            new testClass();
            Console.WriteLine(testClass.a);
        }

        class testClass
        {
            public static int a;
            public int b;
            static testClass()
            {
                System.Random r = new Random();
                a=r.Next(10);
            }

        }
    }

只读字段的使用规则是,只能在声明时,或构造函数中进行初始化。

扩展方法是一个静态方法,没有放在类的源代码中。虽然是静态方法,但必须使用实例来调用。如果原类中包含与扩展方法同名的方法,则扩展方法无效。

分类: .NET 编程 标签: , , ,

cSharp编程基础的学习笔记

2009年11月25日 邵 明博 2 条评论

在上次写完了《垃圾回收机制的剖析》之后,就感觉这种做笔记的方法挺有意思的。于是打算把这作为一种习惯,坚持下来。下面记录的是今天看书的一点点收获。
编译运行:

  • Main方法要么没有返回值Void,要么返回一个整数(int)。
  • 修饰符static表示不能在类的实例上执行,因此不必先实例化类再调用(使用类本身的名称)。要访问一个实例字段,就需要使用this关键字。

变量->变量的初始化:

  • 变量为类或结构的字段,如未初始化,创建这些变量时,其值就默认是0
  • 方法的局部变量必须在代码中显式初始化,否则编译通不过。

常量->常量的特征:

  • 常量总是静态的,不必也不允许在声明时包含修饰符static。
  • 不能从一个变量提取值来初始化常量。如需相同的效果,可以使用只读变量

预定义数据类型:

c#中把数据类型分为2种,一种是值类型,另一种是引用类型。值类型储存在堆栈中,而引用类型储存在托管堆中。引用类型与值类型的区别参考以下代码:

            //引用类型给出的demo
            int[] x, y;
            x=new int[10];
            x[0] = 10;
            y = x;
            y[0] = 20;
            Console.WriteLine(x[0]);
            //值类型给出的demo
            bool a, b;
            a = true;
            b = a;
            b = false;
            Console.WriteLine(a);

这个例子的输出结果是20和True.它说明,这2个引用变量虽然进行了赋值,但托管堆中只保留了一个引用,都指向了同一块区域。而值类型则不同,当一个布尔类型变量a赋值为true之后,再将a的值赋值给b,堆栈中就会有2个布尔值

c#认可的基本预定义类型没有内置于c#语言中,而是内置于.net Framework中。当声明一个int类型的数据时,实际上是声明了一个System.Int32的一个实例。目前看来这样的做法,有这样的好处:

  • 为多语言的交互铺路,形成了一个Common Type System。
  • 使得基本的数据类型可以看做是支持某些方法的类,其声明出来的数据是支持这些方法的实例。如:
            int i = 10;
            string str = i.ToString();
            Console.WriteLine(str);

c#有15个预定义类型,13个是值类型,2个引用类型:string,object:

  • 所有的整数类型的变量都可以赋予十进制或十六进制的值。
  • 对一个整数是int,uint,long或ulong没有显示的声明,都默认为int。(显示的声明可以在数字后面加上L或UL)
  • 代码中没有对某个非整数类型硬编码,则默认为一个double类型。
  • decimal不是基本类型,所以在计算时使用会有性能的损失。
  • char包含16位。一部分原因是不允许char和byte进行隐式的转换;另外,尽管8位足够编码英语中的每个字符和数字0~9,但不能够编码更大的符号系统中的每个字符(如:中文)。为了面向全世界,计算机行业正从8位字符集向16位的Unicode模式转变。
            string a = "a string";
            string b = a;
            Console.WriteLine("a is "+a);
            Console.WriteLine("b is "+b);
            a = "another string";
            Console.WriteLine("a is "+a);
            Console.WriteLine("b is "+b);

类型 150x150 cSharp编程基础的学习笔记这是我总结的c#预定义类型的mindmap,大致囊括了各个类型的基本信息。其中string类型稍微有点特殊,这里单独抽出来谈谈,尤其注意到string是预定义类型中为数不多的引用类型,代码如上。挺有意思的是,按照之前引用类型与数值类型比较的例子得出的结论,最后一条应该输出“b is another string”。可事实并不是我们想象的那样。虽然string对象保留在堆上,但string的一些常用操作会与其他引用类型有所区别:修改一个字符串,就会创建一个全新的string对象。修改a的值,不是替换了原先的,而是在堆上重新分配了一个新的对象。——这是运算符重载造成的。

流控制:
foreach循环不能修改集合中各项数据的值。虽然这个规则并不是知识体系中的漏洞,但作为之前接触不多的循环结构,还是要总结出来提醒自己。

.net垃圾回收的剖析

2009年11月23日 邵 明博 没有评论

看了好几本.net专著,每一本都会提到.net的垃圾回收机制,而且,每一本都会不约而同的告诉你:”垃圾回收的一个重要方面是它的不确定性。换而言之,不能保证什么时候会调用垃圾收集器…”。看到这里,你会很自然的对这个“不确定性”感兴趣——既然不确定,.net又是怎么保证垃圾在很大程度上被回收的呢?这个垃圾回收机制能否有一个更加完善的版本呢?

在.net之前,windows平台已经用了2种技术来释放进程向系统动态请求的内存:

  • 完全手工的方式。
  • 让对象维护引用计数。

完全手工的方式的一个经典例子,便是大名鼎鼎的c++.它能够很有效的释放内存,但缺点是容易频繁的出错,内存泄露时有发生。尽管现代的开发环境提供了内存泄露检测工具,但它们很难跟踪错误,因为直到内存已大量泄露使得windows拒绝服务时,它们才能发挥作用;让对象维护引用计数,是COM对象采用的一种技术。每个COM对象都保留一个客户机对自己的引用计数。当这个计数下降到0的时候,组件就会自己删除自己,并释放相应的内存和资源。但,如果客户机没有通知成功,那么COM对象便一直停留在内存。这一点比起C++内存泄露更为可怕。因为,一旦C++的进程被迫结束时也会释放相应的内存和资源,而COM对象占用的内存可能一直都不会被系统删除,直到你重新启动。

.net采用的垃圾回收就很好的规避了上述的缺点。

和所有语言一样,所有动态请求的内存都会分布在堆上,而.net是放到自己的CLR托管堆上。为了便于理解,我们先讨论一个简化的垃圾回收模型:

Garbage Collector Basics and Performance Hints .net垃圾回收的剖析

这个模型遵循以下规则:

  • 所有的对象都分配到一个连续的内存空间。
  • 堆被分割成了若干个等级,以便于我们能够只观察某一等级的堆,就能最大可能的清除最多的垃圾。
  • 存储在同一等级的堆中的对象,大致上具有相同的生命周期。
  • 等级越高的堆中,所存放的对象越稳定。
  • 周期性的移动对象,以扩展未使用区域的内存。
  • 对象在内存的顺序与创建的它们的顺序是相同的。

这里需要解释的是:.net垃圾回收器将堆分为了3个等级(generation0,1,2),对应的托管堆的初始化大小是256k,2M,10M。最近被分配的内存空间的对象被放置在第0代,一般存储于二级缓存中,所以能对第0代中的对象实现快速存取。经过一轮垃圾回收后,仍然保留在第0代中的对象则被移进第1代中,再经过一轮垃圾回收后,仍然保留再第1代中的对象则被移进第2代中,第2代中包含了生存期较长的对象,这些对象至少经过了两轮回收。

从这样等级制度中,我们不难给出下面的假设:

生命周期越长的对象需要释放的可能性越小, 通过几轮垃圾回收后,它们被存放到了Gen 2里,相比起刚分配内存的那些对象而言,G2的对象不太可能是下一个被释放的人选。因此,大多数情况下,我们收集垃圾时,忽略G2的内存堆,而搜索最近被分配的对象集合有助于花费最少的工作来释放尽可能多的空闲内存空间。

当然,假设永远是一个假设。我们不能保证G2中没有对象不需要释放的。因此.net回收机制设置了一个card table&wrtie barrier的算法来尽力的保证回收的有效性。CT你可以理解为一段连续的内存,比如128byte.每当程序需要写入一块内存时,他会通过WB计算哪一块可以被写,就可以发现被修改了的G2对象,然后在ct中进行设置。通过这样的机制,当我们处理完等级为0的堆后,就可以在CT中寻找已经被修改了的对象。从而有效的完成垃圾回收。

但,事无完美,一旦CT内存被大量的G2对象占满时,则上述机制运行无效。但绝大多数情况下,G2对象是稳定的。这样,也能够解释,大多数的.net书籍会那么介绍垃圾回收机制了。当然是否有更好的机制,那得看聪明的您了。

参考资料:

一张壁纸

2009年11月10日 邵 明博 6 条评论

昨天闲来无事,做了一张给自己本本用的壁纸1280*800,hoho,爱不释手的就放到博客上来了:

壁纸 1024x640 一张壁纸
喜欢的可以点击这里下载:Google美女壁纸 (68)

分类: 东扯西拉 标签: ,