c#中类和对象详解

1.1 类和对象

(class) 是最基础的 C# 类型。类是一个数据结构,将状态(字段)和操做(方法和其余函数成员)组合在一个单元中。类为动态建立的类实例 (instance) 提供了定义,实例也称为对象 (object)。类支持继承 (inheritance) 和多态性 (polymorphism),这是派生类 (derived class) 可用来扩展和专用化基类 (base class) 的机制。算法

使用类声明能够建立新的类。类声明以一个声明头开始,其组成方式以下:先指定类的属性和修饰符,而后是类的名称,接着是基类(若有)以及该类实现的接口。声明头后面跟着类体,它由一组位于一对大括号 { 和 } 之间的成员声明组成。数组

下面是一个名为 Point 的简单类的声明:数据结构

public class Point
{
public int x, y;ide

public Point(int x, int y) {
     this.x = x;
     this.y = y;
}
}函数

类的实例使用 new 运算符建立,该运算符为新的实例分配内存,调用构造函数初始化该实例,并返回对该实例的引用。下面的语句建立两个 Point 对象,并将对这两个对象的引用存储在两个变量中:this

Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);lua

当再也不使用对象时,该对象占用的内存将自动收回。在 C# 中,没有必要也不可能显式释放分配给对象的内存。spa

1.1.1 成员

类的成员或者是静态成员 (static member),或者是实例成员 (instance member)。静态成员属于类,实例成员属于对象(类的实例)。.net

下表提供了类所能包含的成员种类的概述。线程

 

成员

说明

常量

与类关联的常数值

字段

类的变量

方法

类可执行的计算和操做

属性

与读写类的命名属性相关联的操做

索引器

与以数组方式索引类的实例相关联的操做

事件

可由类生成的通知

运算符

类所支持的转换和表达式运算符

构造函数

初始化类的实例或类自己所需的操做

析构函数

在永久丢弃类的实例以前执行的操做

类型

类所声明的嵌套类型

 

1.1.2 可访问性

类的每一个成员都有关联的可访问性,它控制可以访问该成员的程序文本区域。有五种可能的可访问性形式。下表概述了这些可访问性。

 

可访问性

含义

public

访问不受限制

protected

访问仅限于此类和今后类派生的类

internal

访问仅限于此程序

protected internal

访问仅限于此程序和今后类派生的类

private

访问仅限于此类

 

1.1.3 基类

类声明可经过在类名称后面加上一个冒号和基类的名称来指定一个基类。省略基类的指定等同于从类型 object 派生。在下面的示例中,Point3D 的基类为 Point,而 Point 的基类为 object:

public class Point
{
public int x, y;

public Point(int x, int y) {
     this.x = x;
     this.y = y;
}
}

public class Point3D: Point
{
public int z;

public Point3D(int x, int y, int z): Point(x, y) {
     this.z = z;
}
}

一个类继承它的基类的成员。继承意味着一个类隐式地包含其基类的全部成员,但基类的构造函数除外。派生类可以在继承基类的基础上添加新的成员,可是它不能移除继承成员的定义。在前面的示例中,Point3D 类从 Point 类继承了 x 字段和 y 字段,每一个 Point3D 实例都包含三个字段 x、y 和 z。

从某个类类型到它的任何基类类型存在隐式的转换。所以,类类型的变量能够引用该类的实例或任何派生类的实例。例如,对于前面给定的类声明,Point 类型的变量既能够引用 Point 也能够引用 Point3D:

Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);

1.1.4 字段

字段是与类或类的实例关联的变量。

使用 static 修饰符声明的字段定义了一个静态字段 (static field)。一个静态字段只标识一个存储位置。对一个类不管建立了多少个实例,它的静态字段永远都只有一个副本。

不使用 static 修饰符声明的字段定义了一个实例字段 (instance field)。类的每一个实例都包含了该类的全部实例字段的一个单独副本。

在下面的示例中,Color 类的每一个实例都有实例字段 r、g 和 b 的单独副本,可是 Black、White、Red、Green 和 Blue 静态字段只存在一个副本:

public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);

private byte r, g, b;

public Color(byte r, byte g, byte b) {
     this.r = r;
     this.g = g;
     this.b = b;
}
}

如上面的示例所示,可使用 readonly 修饰符声明只读字段 (read-only field)。给 readonly 字段的赋值只能做为字段声明的组成部分出现,或在同一类中的实例构造函数或静态构造函数中出现。

1.1.5 方法

方法 (method) 是一种用于实现能够由对象或类执行的计算或操做的成员。静态方法 (static method) 经过类来访问。实例方法 (instance method) 经过类的实例来访问。

方法具备一个参数 (parameter) 列表(可能为空),表示传递给该方法的值或变量引用;方法还具备一个返回类型 (return type),指定该方法计算和返回的值的类型。若是方法不返回值,则其返回类型为 void。

方法的签名 (signature) 在声明该方法的类中必须惟一。方法的签名由方法的名称及其参数的数目、修饰符和类型组成。方法的签名不包含返回类型。

1.1.5.1 参数

参数用于向方法传递值或变量引用。方法的参数从方法被调用时指定的实参 (argument) 获取它们的实际值。有四种类型的参数:值参数、引用参数、输出参数和参数数组。

值参数 (value parameter) 用于输入参数的传递。一个值参数至关于一个局部变量,只是它的初始值来自为该形参传递的实参。对值参数的修改不影响为该形参传递的实参。

引用参数 (reference parameter) 用于输入和输出参数传递。为引用参数传递的实参必须是变量,而且在方法执行期间,引用参数与实参变量表示同一存储位置。引用参数使用 ref 修饰符声明。下面的示例演示 ref 参数的使用。

using System;

class Test
{
static void Swap(ref int x, ref int y) {
     int temp = x;
     x = y;
     y = temp;
}

static void Main() {
     int i = 1, j = 2;
     Swap(ref i, ref j);
     Console.WriteLine("{0} {1}", i, j);           // Outputs "2 1"
}
}

输出参数 (output parameter) 用于输出参数的传递。对于输出参数来讲,调用方提供的实参的初始值并不重要,除此以外,输出参数与引用参数相似。输出参数是用 out 修饰符声明的。下面的示例演示 out 参数的使用。

using System;

class Test
{
static void Divide(int x, int y, out int result, out int remainder) {
     result = x / y;
     remainder = x % y;
}

static void Main() {
     int res, rem;
     Divide(10, 3, out res, out rem);
     Console.WriteLine("{0} {1}", res, rem);   // Outputs "3 1"
}
}

参数数组 (parameter array) 容许向方法传递可变数量的实参。参数数组使用 params 修饰符声明。只有方法的最后一个参数才能够是参数数组,而且参数数组的类型必须是一维数组类型。System.Console 类的 Write 和 WriteLine 方法就是参数数组用法的很好示例。它们的声明以下。

public class Console
{
public static void Write(string fmt, params object[] args) {...}

public static void WriteLine(string fmt, params object[] args) {...}

...
}

在使用参数数组的方法中,参数数组的行为彻底就像常规的数组类型参数。可是,在具备参数数组的方法的调用中,既能够传递参数数组类型的单个实参,也能够传递参数数组的元素类型的任意数目的实参。在后一种状况下,将自动建立一个数组实例,并使用给定的实参对它进行初始化。示例:

Console.WriteLine("x={0} y={1} z={2}", x, y, z);

等价于写下面的语句。

object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine("x={0} y={1} z={2}", args);

1.1.5.2 方法体和局部变量

方法体指定了在该方法被调用时将执行的语句。

方法体能够声明仅用在该方法调用中的变量。这样的变量称为局部变量 (local variable)。局部变量声明指定了类型名称、变量名称,还可指定初始值。下面的示例声明一个初始值为零的局部变量 i 和一个没有初始值的变量 j。

using System;

class Squares
{
static void Main() {
     int i = 0;
     int j;
     while (i < 10) {
        j = i * i;
        Console.WriteLine("{0} x {0} = {1}", i, j);
        i = i + 1;
     }
}
}

C# 要求在对局部变量明确赋值 (definitely assigned) 以后才能获取其值。例如,若是前面的 i 的声明未包括初始值,则编译器将对随后对 i 的使用报告错误,由于 i 在程序中的该位置尚未明确赋值。

方法可使用 return 语句将控制返回到它的调用方。在返回 void 的方法中,return 语句不能指定表达式。在返回非 void 的方法中,return 语句必须含有一个计算返回值的表达式。

1.1.5.3 静态方法和实例方法

使用 static 修饰符声明的方法为静态方法 (static method)。静态方法不对特定实例进行操做,而且只能访问静态成员。

不使用 static 修饰符声明的方法为实例方法 (instance method)。实例方法对特定实例进行操做,而且可以访问静态成员和实例成员。在调用实例方法的实例上,能够经过 this 显式地访问该实例。而在静态方法中引用 this 是错误的。

下面的 Entity 类具备静态成员和实例成员。

class Entity
{
static int nextSerialNo;

int serialNo;

public Entity() {
     serialNo = nextSerialNo++;
}

public int GetSerialNo() {
     return serialNo;
}

public static int GetNextSerialNo() {
     return nextSerialNo;
}

public static void SetNextSerialNo(int value) {
     nextSerialNo = value;
}
}

每一个 Entity 实例都包含一个序号(而且假定这里省略了一些其余信息)。Entity 构造函数(相似于实例方法)使用下一个可用的序号初始化新的实例。因为该构造函数是一个实例成员,它既能够访问 serialNo 实例字段,也能够访问 nextSerialNo 静态字段。

GetNextSerialNo 和 SetNextSerialNo 静态方法能够访问 nextSerialNo 静态字段,可是若是访问 serialNo 实例字段就会产生错误。

下面的示例演示 Entity 类的使用。

using System;

class Test
{
static void Main() {
     Entity.SetNextSerialNo(1000);

     Entity e1 = new Entity();
     Entity e2 = new Entity();

     Console.WriteLine(e1.GetSerialNo());             // Outputs "1000"
     Console.WriteLine(e2.GetSerialNo());             // Outputs "1001"
     Console.WriteLine(Entity.GetNextSerialNo());     // Outputs "1002"
}
}

注意:SetNextSerialNo 和 GetNextSerialNo 静态方法是在类上调用的,而 GetSerialNo 实例方法是在该类的实例上调用的。

1.1.5.4 虚方法、重写方法和抽象方法

若一个实例方法的声明中含有 virtual 修饰符,则称该方法为虚方法 (virtual method)。若其中没有 virtual 修饰符,则称该方法为非虚方法 (non-virtual method)

在调用一个虚方法时,该调用所涉及的那个实例的运行时类型 (runtime type) 肯定了要被调用的到底是该方法的哪个实现。在非虚方法调用中,实例的编译时类型 (compile-time type) 是决定性因素。

虚方法能够在派生类中重写 (override)。当某个实例方法声明包括 override 修饰符时,该方法将重写所继承的具备相同签名的虚方法。虚方法声明用于引入新方法,而重写方法声明则用于使现有的继承虚方法专用化(经过提供该方法的新实现)。

抽象 (abstract) 方法是没有实现的虚方法。抽象方法使用 abstract 修饰符进行声明,而且只有在一样被声明为 abstract 的类中才容许出现。抽象方法必须在每一个非抽象派生类中重写。

下面的示例声明一个抽象类 Expression,它表示一个表达式树节点;它有三个派生类 Constant、VariableReference 和 Operation,它们分别实现了常量、变量引用和算术运算的表达式树节点。

using System;
using System.Collections;

public abstract class Expression
{
public abstract double Evaluate(Hashtable vars);
}

public class Constant: Expression
{
double value;

public Constant(double value) {
     this.value = value;
}

public override double Evaluate(Hashtable vars) {
     return value;
}
}

public class VariableReference: Expression
{
string name;

public VariableReference(string name) {
     this.name = name;
}

public override double Evaluate(Hashtable vars) {
     object value = vars[name];
     if (value == null) {
        throw new Exception("Unknown variable: " + name);
     }
     return Convert.ToDouble(value);
}
}

public class Operation: Expression
{
Expression left;
char op;
Expression right;

public Operation(Expression left, char op, Expression right) {
     this.left = left;
     this.op = op;
     this.right = right;
}

public override double Evaluate(Hashtable vars) {
     double x = left.Evaluate(vars);
     double y = right.Evaluate(vars);
     switch (op) {
        case '+': return x + y;
        case '-': return x - y;
        case '*': return x * y;
        case '/': return x / y;
     }
     throw new Exception("Unknown operator");
}
}

上面的四个类可用于为算术表达式建模。例如,使用这些类的实例,表达式 x + 3 可以下表示。

Expression e = new Operation(
new VariableReference("x"),
'+',
new Constant(3));

Expression 实例的 Evaluate 方法将被调用,以计算给定的表达式的值,从而产生一个 double 值。该方法接受一个包含变量名称(做为哈希表项的键)和值(做为项的值)的 Hashtable 做为参数。Evaluate 方法是一个虚抽象方法,意味着非抽象派生类必须重写该方法以提供具体的实现。

Constant 的 Evaluate 实现只是返回所存储的常量。VariableReference 的实如今哈希表中查找变量名称,并返回产生的值。Operation 的实现先对左操做数和右操做数求值(经过递归调用它们的 Evaluate 方法),而后执行给定的算术运算。

下面的程序使用 Expression 类,对于不一样的 x 和 y 值,计算表达式 x * (y + 2) 的值。

using System;
using System.Collections;

class Test
{
static void Main() {

     Expression e = new Operation(
        new VariableReference("x"),
        '*',
        new Operation(
            new VariableReference("y"),
            '+',
            new Constant(2)
        )
     );

     Hashtable vars = new Hashtable();

     vars["x"] = 3;
     vars["y"] = 5;
     Console.WriteLine(e.Evaluate(vars));      // Outputs "21"

    vars["x"] = 1.5;
     vars["y"] = 9;
     Console.WriteLine(e.Evaluate(vars));      // Outputs "16.5"
   }
}

1.1.5.5 方法重载

方法重载 (overloading) 容许同一类中的多个方法具备相同名称,条件是这些方法具备惟一的签名。在编译一个重载方法的调用时,编译器使用重载决策 (overload resolution) 肯定要调用的特定方法。重载决策将查找与参数最佳匹配的方法,若是没有找到任何最佳匹配的方法则报告错误信息。下面的示例演示重载决策的工做机制。Main 方法中的每一个调用的注释代表实际被调用的方法。

class Test
{
static void F() {...}
     Console.WriteLine("F()");
}

static void F(object x) {
     Console.WriteLine("F(object)");
}

static void F(int x) {
     Console.WriteLine("F(int)");
}

static void F(double x) {
     Console.WriteLine("F(double)");
}

static void F(double x, double y) {
     Console.WriteLine("F(double, double)");
}

static void Main() {
     F();                 // Invokes F()
     F(1);                // Invokes F(int)
     F(1.0);              // Invokes F(double)
     F("abc");         // Invokes F(object)
     F((double)1);     // Invokes F(double)
     F((object)1);     // Invokes F(object)
     F(1, 1);             // Invokes F(double, double)
}
}

正如该示例所示,老是经过显式地将实参强制转换为确切的形参类型,来选择一个特定的方法。

1.1.6 其余函数成员

包含可执行代码的成员统称为类的函数成员 (function member)。前一节描述的方法是函数成员的主要类型。本节描述 C# 支持的其余种类的函数成员:构造函数、属性、索引器、事件、运算符和析构函数。

下表演示一个名为 List 的类,它实现一个可增加的对象列表。该类包含了几种最多见的函数成员的示例。

 

public class List
{

    const int defaultCapacity = 4;

常量

    object[] items;
    int count;

字段

    public List(): List(defaultCapacity) {}

    public List(int capacity) {
       items = new object[capacity];
    }

构造函数

    public int Count {
       get { return count; }
    }

    public string Capacity {
       get {
           return items.Length;
       }
       set {
           if (value < count) value = count;
           if (value != items.Length) {
              object[] newItems = new object[value];
              Array.Copy(items, 0, newItems, 0, count);
              items = newItems;
           }
       }
    }

属性

    public object this[int index] {
       get {
           return items[index];
       }
       set {
           items[index] = value;
           OnListChange();
       }
    }

索引器

    public void Add(object item) {
       if (count == Capacity) Capacity = count * 2;
       items[count] = item;
       count++;
       OnChanged();
    }

    protected virtual void OnChanged() {
       if (Changed != null) Changed(this, EventArgs.Empty);
    }

    public override bool Equals(object other) {
       return Equals(this, other as List);
    }

    static bool Equals(List a, List b) {
       if (a == null) return b == null;
       if (b == null || a.count != b.count) return false;
       for (int i = 0; i < a.count; i++) {
           if (!object.Equals(a.items[i], b.items[i])) {
              return false;
           }
       }
      return true;
    }

方法

    public event EventHandler Changed;

事件

    public static bool operator ==(List a, List b) {
       return Equals(a, b);
    }

    public static bool operator !=(List a, List b) {
       return !Equals(a, b);
    }

运算符

}

 

1.1.6.1 构造函数

C# 支持两种构造函数:实例构造函数和静态构造函数。实例构造函数 (instance constructor) 是实现初始化类实例所需操做的成员。静态构造函数 (static constructor) 是一种用于在第一次加载类自己时实现其初始化所需操做的成员。

构造函数的声明如同方法同样,不过它没有返回类型,而且它的名称与其所属的类的名称相同。若是构造函数声明包含 static 修饰符,则它声明了一个静态构造函数。不然,它声明的是一个实例构造函数。

实例构造函数能够被重载。例如,List 类声明了两个实例构造函数,一个无参数,另外一个接受一个 int 参数。实例构造函数使用 new 运算符进行调用。下面的语句分别使用 List 类的每一个构造函数分配两个 List 实例。

List list1 = new List();
List list2 = new List(10);

实例构造函数不一样于其余成员,它是不能被继承的。一个类除了其中实际声明的实例构造函数外,没有其余的实例构造函数。若是没有为某个类提供任何实例构造函数,则将自动提供一个不带参数的空的实例构造函数。

1.1.6.2 属性

属性 (propery) 是字段的天然扩展。属性和字段都是命名的成员,都具备相关的类型,且用于访问字段和属性的语法也相同。然而,与字段不一样,属性不表示存储位置。相反,属性有访问器 (accessor),这些访问器指定在它们的值被读取或写入时需执行的语句。

属性的声明与字段相似,不一样的是属性声明以位于定界符 { 和 } 之间的一个 get 访问器和/或一个 set 访问器结束,而不是以分号结束。同时具备 get 访问器和 set 访问器的属性是读写属性 (read-write property),只有 get 访问器的属性是只读属性 (read-only property),只有 set 访问器的属性是只写属性 (write-only property)

get 访问器至关于一个具备属性类型返回值的无参数方法。除了做为赋值的目标,当在表达式中引用属性时,将调用该属性的 get 访问器以计算该属性的值。

set 访问器至关于具备一个名为 value 的参数而且没有返回类型的方法。当某个属性做为赋值的目标被引用,或者做为 ++ 或 -- 的操做数被引用时,将调用 set 访问器,并传入提供新值的实参。

List 类声明了两个属性 Count 和 Capacity,它们分别是只读属性和读写属性。下面是这些属性的使用示例。

List names = new List();
names.Capacity = 100;        // Invokes set accessor
int i = names.Count;             // Invokes get accessor
int j = names.Capacity;          // Invokes get accessor

与字段和方法类似,C# 同时支持实例属性和静态属性。静态属性使用 static 修饰符声明,而实例属性的声明不带该修饰符。

属性的访问器能够是虚的。当属性声明包括 virtual、abstract 或 override 修饰符时,修饰符应用于该属性的访问器。

1.1.6.3 索引器

索引器 (indexer) 是这样一个成员:它使对象可以用与数组相同的方式进行索引。索引器的声明与属性相似,不一样的是该成员的名称是 this,后跟一个位于定界符 [ 和 ] 之间的参数列表。在索引器的访问器中可使用这些参数。与属性相似,索引器能够是读写、只读和只写的,而且索引器的访问器能够是虚的。

该 List 类声明了单个读写索引器,该索引器接受一个 int 参数。该索引器使得经过 int 值对 List 实例进行索引成为可能。例如

List numbers = new List();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++) {
string s = (string)names[i];
names[i] = s.ToUpper();
}

索引器能够被重载,这意味着一个类能够声明多个索引器,只要它们的参数的数量和类型不一样便可。

1.1.6.4 事件

事件 (event) 是一种使类或对象可以提供通知的成员。事件的声明与字段相似,不一样的是事件的声明包含 event 关键字,而且类型必须是委托类型。

在声明事件成员的类中,事件的行为就像委托类型的字段(前提是该事件不是抽象的而且未声明访问器)。该字段存储对一个委托的引用,该委托表示已添加到该事件的事件处理程序。若是还没有添加事件处理程序,则该字段为 null。

List 类声明了一个名为 Changed 的事件成员,它指示有一个新的项已被添加到列表中。Changed 事件由 OnChanged 虚方法引起,后者先检查该事件是否为 null(代表没有处理程序)。“引起一个事件”与“调用一个由该事件表示的委托”彻底等效,所以没有用于引起事件的特殊语言构造。

客户端经过事件处理程序 (event handler) 来响应事件。事件处理程序使用 += 运算符添加,使用 -= 运算符移除。下面的示例向 List 类的 Changed 事件添加一个事件处理程序。

using System;

class Test
{
static int changeCount;

static void ListChanged(object sender, EventArgs e) {
     changeCount++;
}

static void Main() {
     List names = new List();
     names.Changed += new EventHandler(ListChanged);
     names.Add("Liz");
     names.Add("Martha");
     names.Add("Beth");
     Console.WriteLine(changeCount);    // Outputs "3"
}
}

对于要求控制事件的底层存储的高级情形,事件声明能够显式提供 add 和 remove 访问器,它们在某种程度上相似于属性的 set 访问器。

1.1.6.5 运算符

运算符 (operator) 是一种类成员,它定义了可应用于类实例的特定表达式运算符的含义。能够定义三种运算符:一元运算符、二元运算符和转换运算符。全部运算符都必须声明为 public 和 static。

List 类声明了两个运算符 operator == 和 operator !=,从而为将那些运算符应用于 List 实例的表达式赋予了新的含义。具体而言,上述运算符将两个 List 实例的相等关系定义为逐一比较其中所包含的对象(使用所包含对象的 Equals 方法)。下面的示例使用 == 运算符比较两个 List 实例。

using System;

class Test
{
static void Main() {
     List a = new List();
     a.Add(1);
     a.Add(2);
     List b = new List();
     b.Add(1);
     b.Add(2);
     Console.WriteLine(a == b);      // Outputs "True"
     b.Add(3);
     Console.WriteLine(a == b);      // Outputs "False"
}
}

第一个 Console.WriteLine 输出 True,缘由是两个列表包含的对象数目和值均相同。若是 List 未定义 operator ==,则第一个 Console.WriteLine 将输出 False,缘由是 a 和 b 引用的是不一样的 List 实例。

1.1.6.6 析构函数

析构函数 (destructor) 是一种用于实现销毁类实例所需操做的成员。析构函数不能带参数,不能具备可访问性修饰符,也不能被显式调用。垃圾回收期间会自动调用所涉及实例的析构函数。

垃圾回收器在决定什么时候回收对象和运行析构函数方面容许有普遍的自由度。具体而言,析构函数调用的时机并非肯定的,析构函数可能在任何线程上执行。因为这些以及其余缘由,仅当没有其余可行的解决方案时,才应在类中实现析构函数。

 

转自CSDN:http://blog.csdn.net/llll29550242/article/details/6051812

相关文章
相关标签/搜索