C#锐利体验(李建忠)—04

发布时间:2020-03-13 20:47    浏览次数 :

[返回]

                  C#锐利体验
第四讲 类与对象[/B]

跟小静读CLR via C#(04)- 本是同根生

说起.NET中的类,本是同根生,一点不为过。因为CLR要求所有类都要继承自System.Object。所有对象都必须提供一组通用操作,包括对象的等值性唯一性散列码以及克隆


南京邮电学院 李建忠(cornyfield@263.net)

一、等值性——Equals()方法

有时候我们需要比较两个对象是否相等,比如在一个ArrayList中进行排序查找等操作时。

System.Object提供了Equals()虚方法:

class Object

{

public virtual Boolean Equals(object o)

{

if (this == o) return true;

else return false;

}

}

这种判断方式非常简单:直接比较是两个引用是否指向的是同一对象。但这样比较是不确切的。所以我们需要重写该方法,提供更合适的实现方式。

重写时Equals()四大原则?我们在离散数学中好像学过这个呀:

  • 自反。即x.Equals(x)必须为true。
  • 对称。即x.Equals(y)和y.Equals(x)必须返回同样的值。
  • 可传递。即如果x.Equals(y)和y.Equals(z)都返回true,则x.Equals(z)也返回true。
  • 前后一致。如果两个对象的值没变,那么多次比较的值都应该是相同的。

重写思路

    1. 如果参数obj为null,返回false。      因为在非静态方法中,使用this表示的当前对象肯定不是Null。
    1. 如果this和obj参数指向同一实例对象,返回true。    这样省略字段比对过程,提高性能。
    1. 如果this 和obj参数指向的对象类型不同,则返回false。
    1. 比较this和obj中每个实例字段,如果字段不相等则返回false。
    1. 调用基类的Equals方法,如果调用结果为false,则返回false;
    1. 至此,才能返回true。

 

组件编程不是对传统面向对象的抛弃,相反组件编程正是面向对象编程的深化和发展。类作为面向对象的灵魂在C#语言里有着相当广泛深入的应用,很多非常“Sharp”的组件特性甚至都是直接由类包装而成。对类的深度掌握自然是我们“Sharp XP”重要的一环。

C#的类是一种对包括数据成员,函数成员和嵌套类型进行封装的数据结构。其中数据成员可以是常量,域。函数成员可以是方法,属性,索引器,事件,操作符,实例构建器,静态构建器,析构器。我们将在“第五讲 构造器与析构器”和“第六讲 域 方法 属性与索引器”对这些成员及其特性作详细的剖析。除了某些导入的外部方法,类及其成员在C#中的声明和实现通常要放在一起。
C#用多种修饰符来表达类的不同性质。根据其保护级C#的类有五种不同的限制修饰符:

二、惟一性——ReferenceEquals() 方法

惟一性指两个引用指向同一对象。一旦我们的类重写了Ojbect的Equals方法,我们就不能用它来检测唯一性了。Object提供了另一个静态方法ReferenceEquals()

public class Object {

public static Boolean ReferenceEquals(Object objA, Object objB) {

return (objA == objB);

}

}

但是我们尽量不要在C#中用==操作符来判断唯一性,除非两参数都为Object类型。因为有可能其中一个参数类型会重写==操作符,使他产生了其他语义。

实例体验?

class Animal { };

static void Main(string[] args)
        {
            Animal a1 = new Animal();
            Animal a2 = a1;
            Console.WriteLine(Object.ReferenceEquals(a1, a2));                //true,指向同一对象
            a2 = new Animal();
            Console.WriteLine(Object.ReferenceEquals(a1, a2));                //false,a2重新指向新对象
            int number = 100;
            Console.WriteLine(Object.ReferenceEquals(number, number));//false,值类型Number两次装箱到不同的对象中
            Console.Read();
        }

  1. public可以被任意存取;

  2. protected只可以被本类和其继承子类存取;

  3. internal只可以被本组合体(Assembly)内所有的类存取,组合体是C#语言中类被组合后的逻辑单位和物理单位,其编译后的文件扩展名往往是“.DLL”或“.EXE”。

  4. protected internal唯一的一种组合限制修饰符,它只可以被本组合体内所有的类和这些类的继承子类所存取。

  5. private只可以被本类所存取。

三、散列码——GetHashCode() 方法

System.Object提供了虚方法GetHashCode()从一个对象上得到Int32的散列码。该方法返回的是在整个应用程序域中保证惟一的值,该值在对象整个生存周期内都不会改变。

如果我们重写了类的Equals()方法,那么我们最好也重写GetHashCode()方法,否则编译器可能会产生警告信息。因为System.Collections.Hashtable类要求两个相等的对象具有相同的散列值。

如果不是嵌套的类,命名空间或编译单元内的类只有public和internal两种修饰。
new修饰符只能用于嵌套的类,表示对继承父类同名类型的隐藏。
abstract用来修饰抽象类,表示该类只能作为父类被用于继承,而不能进行对象实例化。抽象类可以包含抽象的成员,但这并非必须。abstract不能和new同时用。下面是抽象类用法的伪码:

四、克隆——ICloneable接口

如果一个类允许实例被拷贝,则继承ICloneable接口。

Public interface ICloneable{ object Clone();}

浅拷贝?

当对象的字段值被拷贝时,字段引用的对象不会被拷贝。实现时可以调用Object的MemberwiseClone方法即可。

深拷贝?

对象实例中字段引用的对象也进行拷贝。深拷贝后,新创建的对象和原对象没有任何公用的东西,改变一个对象时也不会影响另外一个。

abstract class A{   public abstract void F();}abstract class B: A{   public void G() {}}class C: B{   public override void F()    {    //方法F的实现      }}

抽象类A内含一个抽象方法F(),它不能被实例化。类B继承自类A,其内包含了一个实例方法G(),但并没有实现抽象方法F(),所以仍然必须声明为抽象类。类C继承自类B,实现类抽象方法F(),于是可以进行对象实例化。
sealed用来修饰类为密封类,阻止该类被继承。同时对一个类作abstract和sealed的修饰是没有意义的,也是被禁止的。
对象与this关键字
类与对象的区分对我们把握OO编程至关重要。我们说类是对其成员的一种封装,但类的封装设计仅仅是我们编程的第一步,对类进行对象实例化,并在其数据成员上实施操作才是我们完成现实任务的根本。实例化对象采用MyClass myObject=new MyClass()语法,这里的new语义将调用相应的构建器。C#所有的对象都将创建在托管堆上。实例化后的类型我们称之为对象,其核心特征便是拥有了一份自己特有的数据成员拷贝。这些为特有的对象所持有的数据成员我们称之为实例成员。相反那些不为特有的对象所持有的数据成员我们称之为静态成员,在类中用static修饰符声明。仅对静态数据成员实施操作的称为静态函数成员。C#中静态数据成员和函数成员只能通过类名引用获取,看下面的代码:

using System;class A{    public int count;    public void F()    {        Console.WriteLine(this.count);    }    public static string name;    public static void G()    {        Console.WriteLine(name);    }}class Test{    public static void Main()    {        A a1=new A();        A a2=new A();        a1.F();        a1.count=1;        a2.F();        a2.count=2;        A.name="CCW";        A.G();    }}

我们声明了两个A对象a1,a2。对于实例成员count和F(),我们只能通过a1,a2引用。对于静态成员name和G()我们只能通过类型A来引用,而不可以这样a1.name,或a1.G()。
在上面的程序中,我们看到在实例方法F()中我们才用this来引用变量count。这里的this是什么意思呢?this 关键字引用当前对象实例的成员。在实例方法体内我们也可以省略this,直接引用count,实际上两者的语义相同。理所当然的,静态成员函数没有 this 指针。this 关键字一般用于从构造函数、实例方法和实例访问器中访问成员。
在构造函数中this用于限定被相同的名称隐藏的成员,例如:

class Employee{public Employee(string name, string alias) {       this.name = name;       this.alias = alias;    }}

将对象作为参数传递到其他方法时也要用this表达,例如:

下一篇:没有了