第4章 基础类型

4.1 所有类型都从System.Object派生

“运行时”要求每个类型最终都从System.Object 类型派生,所以可以保证每个类型的每个对象都有一组最基本的方法。

重写(override):继承时发生,在子类中重新定义父类中的方法,子类中的方法和父类的方法是一样的(即方法名,参数,返回值都相同),由 override 声明重写的方法称为重写基方法。

例如:基类方法中声明为virtual,派生类中使用override申明此方法的重写。

重写override一般用于接口实现和继承类的方法改写,要注意:

v  覆盖的方法的标志必须要和被覆盖的方法的名字和参数完全匹配,才能达到覆盖的效果;

v  覆盖的方法的返回值必须和被覆盖的方法的返回一致;

v  覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;

v  被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

v  不能重写非虚方法或静态方法。重写的基方法必须是 virtual、abstract 或 override 的。

namespace 方法重写

{

    class TestOverride

    {

        public class Employee

        {

            public string name;

            // Basepay is defined as protected, so that it may be accessed only by this class and derived classes.

            protected decimal basepay;

            // Constructor to set the name and basepay values.

            public Employee(string name, decimal basepay)

            {

                this.name = name;

                this.basepay = basepay;

            }

            // Declared virtual so it can be overridden.

            public virtual decimal CalculatePay()

            {

                return basepay;

            }

        }

        // Derive a new class from Employee.

        public class SalesEmployee : Employee

        {

            // New field that will affect the base pay.

            private decimal salesbonus;

            // The constructor calls the base-class version, and initializes the salesbonus field.

            public SalesEmployee(string name, decimal basepay,

                      decimal salesbonus)

                : base(name, basepay)

            {

                this.salesbonus = salesbonus;

            }

            // Override the CalculatePay method to take bonus into account.

            public override decimal CalculatePay()

            {

                return basepay + salesbonus;

            }

        }

        static void Main()

        {

            // Create some new employees.

            SalesEmployee employee1 = new SalesEmployee("Alice",

                          1000, 500);

            Employee employee2 = new Employee("Bob", 1200);

            Console.WriteLine("Employee4 " + employee1.name +

                      " earned: " + employee1.CalculatePay());

            Console.WriteLine("Employee4 " + employee2.name +

                      " earned: " + employee2.CalculatePay());

        }

    }

    /*

        Output:

        Employee4 Alice earned: 1500

        Employee4 Bob earned: 1200

    */

}

System.Object的公共方法:

  • Equals: 如果两个对象具有相同的值,就返回true。
  • GetHashCode: 返回对象的值的一个哈希码。如果某个类型的对象要在一个哈希表集合中作为Key使用,该类型应该重写这个方法。
  • ToString:  该方法默认返回类型的完整名称(this.GetType ().FullName)。然而,我们经常需要重写这个方法,使它返回一个String对象。
  • GetType: 返回从Type派生的一个对象的实例,指出调用GetType的那个对象是什么类型。返回的Type对象可以和反射类配合使用,从而获取与对象的类型有关的元数据信息。GetType方法是非虚方法,这样可以防止一个类重写该方法,并隐瞒其类型,从而破坏类型安全性。

System.Object的受保护的方法:

  • MemberwiseClone: 这个非虚方法能创建类型的一个新实例。
  • Finalize: 在垃圾回收器判断对象应该被作为垃圾回收之后,在对象的内存被实际回收之前,会调用这个虚方法。简单的说,虚方法就是可以被子类重写(override)的方法,如果子类重写了虚方法,那么运行时将使用重写后的逻辑,如果没有重写,则使用父类中虚方法的逻辑。

内存格局通常分为四个区:

  • 全局数据区:存放全局变量,静态数据,常量
  • 代码区:存放所有的程序代码
  • 栈区:存放为运行而分配的局部变量,参数,返回数据,返回地址等
  • 堆区:即自由存储区

线程堆栈(Thread Stack)和托管堆(Managed Heap)

每个正在运行的程序都对应着一个进程(process),在一个进程内部,可以有一个或多个线程(thread),每个线程都拥有一块“自留地”,称为“线程堆栈”,大小为1M,用于保存自身的一些数据,比如函数中定义的局部变量、函数调用时传送的参数值等,这部分内存区域的分配与回收不需要程序员干涉,主要由操作系统管理。所有值类型的变量都是在线程堆栈中分配的。

另一块内存区域称为“堆(heap)”,在.NET 这种托管环境下,堆由CLR 进行管理,所以又称为“托管堆(managed heap)。

托管堆是CLR中自动内存管理的基础。初始化新进程时,运行时会为进程保留一个连续的地址空间区域。这个保留的地址空间被称为托管堆。托管堆维护着一个指针,用它指向将在堆中分配的下一个对象的地址。最初,该指针设置为指向托管堆的基址。

CLR要求所有类型对象都用new操作符来创建:Employee emp = new Employee (“ConstructorParam1”);

用new 关键字创建类的对象时,分配给对象的内存单元就位于托管堆中。在程序中我们可以随意地使用new 关键字创建多个对象,因此,托管堆中的内存资源是可以动态申请并使用的,当然用完了必须归还。

声明一个Employee的引用emp,在线程堆栈上给这个引用分配存储空间,这仅仅只是一个引用,不是实际的Employee对象。假定emp占4个字节的空间,包含了存储Employee的引用地址。接着分配托管堆上的内存来存储Employee对象的实例,假定Employee对象的实例是32字节,为了在托管堆上找到一个存储Employee对象的存储位置,.Net运行库在托管堆中搜索第一个从未使用的,32字节的连续块来存储Employee对象的实例,然后把分配给Employee对象实例的地址赋给emp变量。new执行了以上所有这些操作之后,会返回指向新建对象的一个引用。在前面的示例代码中,这个引用会保存到变量emp中。

以下是new操作符所做的事情:

  • 它计算类型中定义的所有实例字段需要的字节数。堆上的每个对象都需要一些额外的成员“类型对象指针”和“同步块索引”。
  • 它从托管堆中分配指定类型要求的字节数,从而分配对象的内存,分配的所有字节都设为0。
  • 它初始化对象的“类型对象指针”和“同步块索引”。
  • 调用类型的实例构造器,向其传入在对new的调用中指定的任何实参。
class SampleClass

{

    public string name;

    public int id;

    public SampleClass() { }

    public SampleClass(int id, string name)

    {

        this.id = id;

        this.name = name;

    }

}

class ProgramClass

{

    static void Main()

    {

        SampleClass Employee2 = new SampleClass(1234, "Cristina Potra");

    }

}

没有和new操作符对应的一个delete操作符。换言之,没有办法显示释放 为一个对象分配的内存。CLR采用了垃圾回收机制,能自动检测到一个对象不再被使用或访问,并自动释放对象的内存。

4.2类型转换

CLR最重要的特性之一就是类型安全性。在运行时,CLR总是知道一个对象是什么类型。调用GetType方法,总是知道一个对象确切的类型是什么。

CLR允许将一个对象转换为它的实际类型或者它的任何基类型。

C#不要求特殊语法即可将一个对象转换为它的任何基类型,因为向基类型转换被认为是安全的隐式转换。然而,将对象转换为它的某个派生类型时,C#要求开发人员只能进行显式转换。

隐式转换不需要在代码中指定转换类型,例如:int intNumber = 10; double doubleNumber = intNumber; intNumber会被隐式转换成double类型。

显式转换则相反,需要指定转换类型,例如:double doubleNumber = 10.1; int intNumber = (int)doubleNumber;

对于表示数值的基本数据类型来说,数值范围小的数据类型转换成数值范围大的数据类型可以进行隐式转换,而反过来则必须进行显示转换。

就像上面的两个例子一样。 对于类类型来说,子类转换成父类可以进行隐式转换,而反过来则必须进行显式转换,例如:string str1 = "abc";object obj = str1;  //子类转换成父类,隐式转。                string str2 = (string)obj;  //父类转换成子类,显式转换 如果两个类之间没有继承关系,则不能进行隐式转换或显式转换,此时必须在被转换的类中定义一个隐式转换方法或显式转换方法。

在Main方法中,会构造一个Manager对象,并将其传给PromoteEmployee。这些代码能成功编译并运行,因为Manager最终从Object派生的,而PromoteEmployee期待的正是Object。在PromoteEmployee内部,CLR核实o引用的是一个Employee对象,或者是从Employee派生的一个类型的对象。由于Manager是从Employee派生的,所以CLR执行类型转换,运行PromoteEmployee继续执行。

PromoteEmployee返回之后,Main继续构造一个DateTime对象,并将其传给PromoteEmployee。同样的,DateTime是从Object派生的,所以编译器会顺利编译调用

PromoteEmployee的代码。但在PromoteEmployee内部,CLR会检查类型转换,发现o引用的是一个DateTime对象,它既不是一个Employee,也不是从Employee派生的任何类型。所以CLR会禁止转型,并抛出一个System.InvalidCastException异常。

声明PromoteEmployee方法的正确方式是将参数类型指定Employee,而不是Object。

v  使用C#的is和as操作符来转型

is检查一个对象是否兼容于指定类型,并返还一个Boolean值:true或false。is操作符永远不会抛出异常。

如果对象引用为null,is操作符总会返还false,因为没有可检查其类型的对象。

as操作符的工作方式与强制类型转换一样,只是它永远不会抛出一个异常。

检查最终生成的引用是否为null。

4.3命名空间和程序集

命名空间用于对相关的类型进行逻辑性分组,开发人员使用命名空间来方便地定位一个类型。

密封类的修饰符,用了这个修饰符的类就不能被其他类继承了。

应该有一种简单的方式来直接引用FileStream和StringBuilder类型。C#编译器通过using指令来提供这种机制。

C#的using指令指示编译器尝试为一个类型附加不同的前缀,直到找到一个匹配项。

using指令允许为一个类型或命名空间创建别名。

在C#中namespace指令的作用:只是告诉编译器为源代码中出现的每个类型名称附加命名空间名称前缀,减少程序员的打字量。

命名空间和程序集不一定是相关的。

同一个命名空间的各个类型可能在不同的程序集中实现。例如:System.IO.FileStream类型是在MSCorLib.dll程序集中实现,而System.IO.FileSystemWatcher类型是在System.dll程序集中实现的。

4.4运行时的相互关系

参考http://www.cnblogs.com/MeteorSeed/archive/2012/01/24/2325575.html

本节将解释类型、对象、线程堆栈和托管堆在运行时的相互关系,以及调用静态方法、实例方法和虚方法的区别。

进程是指在系统中正在运行的一个应用程序;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。

当系统加载一个CLR的进程,进程里面可能有多个线程,这时候系统会给这个进程创建一个大小为1M的线程堆栈。这个线程堆栈用来存放方法调用的实参,和方法内部定义的局部变量。

时间: 2024-11-05 13:40:30

第4章 基础类型的相关文章

HttpClient 4.3.6教程 第1章 基础 【翻译】

第1章基础 1.1请求执行 HttpClient大部分的基础函数都是在执行HTTP的方法.一个HTTP方法执行包括一个或多个HTTP请求/HTTP应答交换,通常这已经在HttpClient内部操作了. 用户提供一个请求对象,HttpClient将这个请求传输给目标服务器,服务器返回一个相应的应答结构,如果不成功则抛出一个异常. 很自然地,了解HttpClientAPI的切入点是上面阐述里规定的HttpClient接口. 这是一个最简单请求执行例子: CloseableHttpClienthttp

Java基础知识二次学习-- 第二章 基础语法与递归补充

第二章 基础语法与递归补充   时间:2017年4月24日10:39:18 章节:02章_01节,02章_02节 视频长度:49:21 + 15:45 内容:标识符,关键字与数据类型 心得:由字母,下划线,$,数字组成,应该由字母,下划线$开头,同时应该避开java保留字符 变量是内存中的一小块区域,使用变量名来访问这块区域 执行过程中的内存管理(疑问:这里的内存和Jvm的一样吗?) code segment 存放代码 data segment 静态变量 字符串常量 stack 栈 局部变量 h

前端开发工程师 - 03.DOM编程艺术 - 第1章.基础篇(上)

第1章.基础篇(上) DOM (Document Object Model) - 文档对象模型 以对象的方式来表示对应的html,它有一系列的规范 i.e. 在浏览器中,DOM是通过JS实现的. DOM: DOM Core:核心结构.API的定义 DOM HTML: 定义HTML如何转化成对象(HTML对应的对象)-- 操作节点 DOM Style:样式转换成对象 -- 操作样式 DOM Event:事件对象的模型 -- 响应用户的操作 文档树 HTML -> DOM树 节点遍历 node.pa

第十五章 枚举类型和位标志

1. 概述 本章内容包括 枚举类型.位标志 以及 为枚举类型添加方法. 2. 主要内容 2.1 枚举类型 枚举类型定义了一组“符号名称/值”配对. 枚举类型的好处包括: ① 可以使程序更容易编写.阅读和维护. ② 枚举类型是强类型的,有编译器检测. 编译枚举类型时,C#编译器会把每个符号转换成类型的一个常量字段. System.Enum类型有一个名为GetUnderlyingType的静态方法,返回用于容纳一个枚举类型的值的基础类型. Enum.GetUnderlyingType(typeof(

前端开发工程师 - 02.JavaScript程序设计 - 第1章.基础篇

第1章--基础篇 JS介绍 html 网页的内容:css 网页的样式:javascript 网页的行为 i.e. hello world <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script type="text/javascrip

Kubernetes 第一章 基础知识

Kubernetes 第一章 基础知识 Kubernetes是一个开源容器编排引擎,用于自动化容器化应用程序的部署,扩展和管理.开源项目由Cloud Native Computing Foundation(CNCF)托管. Kubernetes是一个可移植,可扩展的开源平台,用于管理容器化工作负载和服务,有助于声明性配置和自动化.它拥有庞大,快速发展的生态系统.Kubernetes服务,具有广泛的工具和支持可用. 发展历程 传统部署时代: 早期,组织在物理服务器上运行应用程序.无法为物理服务器中

Typescript学习笔记(一)基础类型

为了面向ng2和前端未来,开始搞向ts,ts是微软出的一枚语言,作为es6的超集,他出的一些特性还是蛮好用的(略坑).对于我等纯前端(从开始就接触javascript)的人来说,真想说,这特么什么鬼.部分特性同es6(个人对es6还是蛮有好感的).ts同期的coffeescript,将js python化,ts也把js搞的向其他语言靠拢.. 中文学习,这里是英文学习.如果不想看这些东西,那看我笔记学吧.哈哈. ts的基础类型,布尔值,数字,字符串,数组,元组(Tuple),枚举,任意值,空值.后

第四章 复合类型

第四章  复合类型 4.1  数组 4.1.1  数组简介 数组(array)是一种数据格式,能够存储多个同类型的值. 声明数组的通用格式如下: typeName arrayName[arraySize]; 表达式arraySize指定数组的元素数目,它只能是以下三种情况之一: 1)        整型常数(如10,枚举值也可以): 2)        const值 3)        常量表达式(如8 * sizeof(int)) 注意:使用数组要注意下标的正确.编译器不会检查使用的下标是否有

wpf(第一章 基础知识)

wpf第一章基础知识:通过vs2015创建wpf程序会在引用里面多出3个核心程序集PresentationCore.PresentationFramework.WindowsBase.并且会在解决方案中生成如下的结构: 1.程序起始相关的资源:2.与整个wpf相关的后台代码:3.窗体界面:4.窗体界面的后台代码. 在app.xaml中 1.Application的后台类:2.启动窗体:3.系统资源区域 除此之外可以在MainWindow.xaml中自定义窗体设置属性,拖拉控件在里面.