C# 常用的对象操作

1.对象的类型判断

   任何一个对象它都具有双重类型的身份,即声明类型和实际类型,正因为对象有了双重的类型身份,因此也出现了类型兼容的概念。同时也因为对象的引用性质,也使它在判断相等的概念上具有多重意义(即存在引用相等和值相等)。

     首先我们来看类型兼容,看一段代码:

?
using  System;
class  Fruit
{
}
class  Apple:Fruit
{
}
class  Test
{
      public  static  void  Main()
      {
         Fruit f1= new  Fruit();
         Fruit f2= new  Apple();
             //类型通过虚函数表中GetType()函数获得
         Type t1=f1.GetType();  //Fruit
         Type t2=f2.GetType();  //Apple
         Console.WriteLine(t1);
         Console.WriteLine(t2);
         Console.WriteLine(t1==t2);
         if  (f1 is  Apple)
         {
          Apple a1=(Apple)f1;
      
      }
}

在这里我们定义了两个类型Fruit和Apple,我们让Apple继承自Fruit。在客户端我们实例化了两个对象f1,f2,他们的声明类型相同,但是实际类型是否也相同了,这里我们不能只凭表面上的判断就说它们不相等,我们的做法是,先获得f1和f2的实际类型,在比较它们的类型是否相等。在这里GetType()方法就是用来获取一个对象的实际类型,这里额外说一下,对象的GetType()方法是一个从Object继承下来的虚方法,存储于对象虚函数指针所指向的虚函数表中,通过这个方法获取类型信息以后,就可以判断它们是否相等了。注意,在这里我们判断相等都是判断对象的实际类型。

下面我们看一下is和as操作符,它们都是判断类型是否兼容的。再看上面的代码,如果我们不写if 语句,直接写进行转型操作,那么C#编译器会抛出类型安全的异常,也就是说在进行转型之前,C#编译器会自动进行类型安全的检查,判断对象是否能够被转型,即判断被转对象的类型与目标类型是否兼容,那么在这里C#编译器自动帮我们进行了类型的兼容性检查,这是在C#中一个独有的特性,以往的C++不会进行类型兼容性的检查,默认的就可以进行转型。那么为了提高系统的性能,C#给我们提供了两种进行类型兼容性判断的操作符is和as,我们看上面if语句中的代码,它是什么意思了,如果f1的类型是f1的实际类型或者是该实际类型的父类,那么返回true,否则返回false,即兼容是一种向上的兼容。只有满足了兼容性,才能进行安全的类型转换。那么我们上面的代码就进行了两次兼容性的判断。

    as操作符的与is操作符稍有不同,不同之处在于判断兼容性以后的返回值不同,如果兼容,则返回转型后的对象,如果不兼容,就返回null值。下面看一段代码:

Apple a2=f1 as Apple;

if (a2!=null)

{

  Console.WriteLine(a2);

}

在这里我们只进行了一次兼容性的判断,因为在判断兼容性的同时就可以返回转型后的对象,而使用is操作符的时候一次手动的兼容性的判断,还有一次类型转换时默认的类型兼容判断。那么as操作符了is操作符分别用于什么场合了,如果只需要进行类型兼容的检查,那么就用is操作符,如果在兼容型检查的时候同时需要进行类型转换,那么就使用as操作符。在实际编程的过程中,这些做法都是不被鼓励的,一旦做了类型判断,就是在用一种分解的思维来解决问题,那么就抛弃了多态的思维。

2.对象的相等判断

判断对象的相等有三种方式,操作符:==,!=,虚方法(重写后值相等):Object.Equals,静态方法:Object.Equals、Object.referenceEquals

1.操作符==,!=,预定义引用相等,重载后值相等。

2.Object.Equals虚方法,重写后值相等

3.Object.Equals静态方法,属于Object类,当虚方法Object.Equals被重写以后,可以判断值相等。

4.Object.referenceEquals静态方法,属于Object类,判断引用相等。

重写顺序:先重写虚方法objA.Equals(ObjB)(缺点:无法保证objA不为null),但此时静态方法Object.Equals(ObjA,ObjB)却能够判断值相等且可以保证objA不为null(根据微软对这个方法的定义),因此我们可以利用静态方法Object.Equals(ObjA,ObjB)继续进行操作符的重载。

这些方法都是用于进行引用相等的判断的,如果要进行值相等的判断,必须进行重写。下面我们看一段代码:

?
using  System;
class  Point
{
      public  int  x;
      public  int  y;
      public  Point( int  x, int  y)
      {
          this .x=x;
          this .y=y;
         }
     }
     class  Test
     {
         public  static  void  Main()
         {
             Point p1= new  Point(100,200);
             Point p2= new  Point(100,200);
             Point p3= new  Point(200,400);
             Point p4=p1;
             //针对引用类型判断的是地址相等
             Console.WriteLine(p1==p2); //false
             Console.WriteLine(p1==p3); //false
             Console.WriteLine(p1==p4); //true
             }
         }

 因为==操作符默认为判断引用相等,因此结果为false,false,true.如果我们需要判断值相等了,就需要重写上面的一些虚方法。

?
using  System;
class  Point
{
      public  int  x;
      public  int  y;
      public  Point( int  x, int  y)
      {
          this .x=x;
          this .y=y;
         }
         public  override  bool  Equals(Object obj)   //重写虚函数,提供值相等的比较
         {
                 if  (obj== null )
                  {
              return  false ;
              }
             
              if  (obj== this ) //判断是否为同一对象
              {      
                 return  true ;
              }
               if  ( this .GetType()!=obj.GetType())  //判断类型是否相同
              {         
                 return  false ;
              }          
              Point other=(Point)obj;
              if  ( this .x==other.x && this .y==other.y)
              {          
                 return  true
              }
              else
              {             
                   return  false ;
              }              
             }  
     }
     class  Test
     {
         public  static  void  Main()
         {
             Point p1= new  Point(100,200);
             Point p2= new  Point(100,200);
             Point p3= new  Point(200,400);
             Point p4=p1;
             Console.WriteLine(p1.Equals(p2)); //true
             Console.WriteLine(p1.Equals(p3)); //false
             Console.WriteLine(p1.Equals(p4)); //true
             }
         }  

因为不同类型的内部实现不一样,其对象在判断相等时依据也不一样,因此值相等的判断方法需要根据具体的类而重写。因此C#语言并未提供值相等的比较方法。那么我们在这里为了提供值相等的比较方法,我们重写了虚函数Equals,这个虚函数的参数固定为Object型,对于这个虚函数,如果不进行重写,它进行的是引用相等。关于这个方法的重写,这里我们一点一点来分析,首先,我们判断了比较的对象是否为空,如果为空,则直接返回false。如果没有这一步且obj为空时,则程序会抛出异常。其次我们判断了相比较的两个对象是否为同一对象,如果是,就直接返回true,不需要再进行内部细节的比较,这一做法完全是为了提高比较效率,可以省略。接下来我们判断了两个对象的类型是否相等(不是兼容),因为类型相同是对象进行值相等比较的前提条件,如果类型不相同,就没有必要在进行值相等的比较了。

判断到这里,我们开始进行内部细节的比较,因为已经判断了类型的相等,因此不必要再用as操作符来进行类型兼容检查的类型转换,直接进行转换即可,转换以后就可以进行内部细节的比较了。

讲到这里,我们可能会注意到前面的一句话,Object.Equals静态方法,我们一定想知道为什么当虚方Object.Equals被重写以后它就可以进行值相等的判断了,下面我先给出这个方法的实现代码:

复制代码
public static bool Equals(Object obj1,Object obj2)  //静态方法
            {
                 if (obj1==obj2)
                 {
                     return true;
                 }
                 if (obj1==null||obj2==null)
                 {
                         return false;
                 }
                 return obj1.Equals(obj2);  //调用虚方法
                }
复制代码

这个静态方法在Object类中,是微软已经写好了的,在这个方法里面我们最后调用了虚方法Object.Equals,如果该方法没有被重写,那么它判断的是引用相等,导致该静态方法也是进行引用相等的判断,如果被重写了,那么虚方法和静态方法都是判断值相等。可能有人要问,为什么有了一个虚方法,还需要一个静态的方法了,这个方法解决了调用该方法的对象为空的问题,在下面操作符的重载中我们会遇到这个问题。

   操作符重载:所有的重载操作符都是静态的,为了能让==号也能进行值相等的判断,下面我们对==操作符进行重载(重载而不是重写),首先我们看一段操作符重载代码:

   

public static bool operator==(Point p1,Point p2)  //操作符重载
             {
                  return p1.Equals(p2);
             }    

因为我们的虚方法Equals已经进行了重写,可以用于进行值相等的判断了,而且在这里可以保证p2不为空,但是并不能保证p1也不为空,因此这种做法是不可取的,在这里我们就需要使用静态方法Object.Equals,它可以保证p1和p2均不为空。

 

public static bool operator==(Point p1,Point p2)  //操作符重载
             {
                  return Object.Equals(p1,p2);
             }    

重载了==操作符,同时也要重载!=号操作符,这是一个规则。

 

public static bool operator!=(Point p1,Point p2)  //!=操作符重载
             {
                  return !Object.Equals(p1,p2);                    
             }    

下面我贴出完整的代码:

 

复制代码
using System;
class Point 
{
     public int x;
     public int y;
     public Point(int x,int y)
     {
          this.x=x;
          this.y=y;
         }
         public override bool Equals(Object obj)   //重写虚函数,提供值相等的比较
         {
            if (obj==null)
            {
                return false;
              }
            
              if (obj==this) //判断是否为同一对象
              {          
                  return true;
              }
               if (this.GetType()!=obj.GetType())  //判断类型是否相同
              {              
                  return false;
              }               
              Point other=(Point)obj;
              if (this.x==other.x && this.y==other.y)
              {              
                  return true
              } 
              else 
              {                  
                  return false;
                  }                  
             } 
             //由于虚方法Equals已经进行了重写,因此该静态方法可以用于值相等的判断。
             public static bool operator==(Point p1,Point p2)  //==操作符重载
             {
                  return Object.Equals(p1,p2);                    
             }    
             public static bool operator!=(Point p1,Point p2)  //!=操作符重载
             {
                  return !Object.Equals(p1,p2);                    
             }    
                      
    }
    class Test
    {
        public static void Main()
        {
            Point p1=new Point(100,200);
            Point p2=new Point(100,200);
            Point p3=new Point(200,400);
            Point p4=p1;
            Console.WriteLine(p1.Equals(p2)); //true
            Console.WriteLine(p1.Equals(p3)); //false
            Console.WriteLine(p1.Equals(p4)); //true
            }
        }
/*  微软已经写好的两个静态方法
   public static bool Equals(Object obj1,Object obj2)  //静态方法
    {
     if (obj1==obj2)
      {
        return true;
      }
    if (obj1==null||obj2==null)
      {
        return false;
      }
        return obj1.Equals(obj2);  //调用虚方法
      }
   public static bool referenceEquals(Object obj1,Object obj2)
     {
       retrun obj1==obj2;
     }
 */
        
复制代码

如果我们要进行引用相等的判断,现在只能用静态方法referenceEquals,它永远是进行引用相等的判断。对于referenceEquals静态方法的实现,我们可能会感到疑惑,如果==号操作符被重载了,那么是不是就进行的值相等的判断了?其实不然,这里仍然进行的是引用相等的判断,原因是:==重载符是基于编译时绑定的(JIT编译),他需要根据==两边的声明类型来判断是进行值相等的判断还是引用相等的判断,在上面的代码中,我们是基于Point类型来进行==操作符的重载,因此当编译时编译器就会对referenceEquals方法传入参数的声明类型进行判断,如果为Point就进行值相等的判断,否则就进行引用相等的判断。

关于referenceEquals方法对值类型变引用判断,下面我们看一段代码:

 

复制代码
class Test
{
  public static void Main()
  {           
    int data=100;                 
    Console.WriteLine(Object.referenceEquals(data,data));            
  }
}
复制代码

很多人可能会觉得它的结果应该是true,因为它们是同一对象,其实不然,因为referenceEquals方法所接受的两个参数都是Object类型,那么在将两个int型参数传给该方法的时候,会进行两次装箱操作,这样会在托管堆上生成两个相同的对象,因此这里判断为false.

3.对象的克隆(Clone)

深克隆:对象克隆之后完全相等,无共享成分,即栈上和堆上的内存均进行克隆。

浅克隆:对象克隆之后完全相等,有共享成分,即只复制栈上的内存。

我们先看一段代码:

 

复制代码
using System;
class point 
{
     private int x;
     private int y;
     public Point(int x,int y)
     {
          this.x=x;
          this.y=y;
         }
    }
class Test
{
     public static void Main()
     {
          Point p1=new Point(10,20);
          Point p2=p1;
         }
    }
复制代码

这段代码中,p1和p2指向了堆上相同的对象,那么如果我们想让p1和p2分别指向两个相同的对象,我们应该如何做了,这里就需要用到克隆。首先我们需要被克隆的对象的类实现了克隆接口,提供给外部克隆的方法,该方法要求返回值必须是Object.下面我们给出代码:

复制代码
 
 
using System;
class point : ICloneable
{
     private int x;
     private int y;
     public Point(int x,int y)
     {
          this.x=x;
          this.y=y;
         }
         public object Clone()
         {
              Point p=new Point(this.x,this.y)
              return p;
             }
    }

class Test
{
     public static void Main()
     {
          Point p1=new Point(10,20);
          Point p2=(Point)p1.Clone()
         }
    }
复制代码

说到这里我们需要介绍一个方法MemberwiseClone(),这个方法会进行按成员拷贝,即把对象的第一层内存完全拷贝,包括指针,因此按成员拷贝值适用于对象成员只有值类型的情况(如果有引用类型,则只会拷贝指针,这样会出现共享,不安全。),而上面的Point类正好符合要求,因此上面的代码中的拷贝行为我们可以这样实现:

 

复制代码
using System;
class point : ICloneable
{
     private int x;
     private int y;
     public Point(int x,int y)
     {
          this.x=x;
          this.y=y;
         }
         public object Clone()
         {
              return this.MemberwiseClone();
             }
    }

class Test
{
     public static void Main()

     {
          Point p1=new Point(10,20);
          Point p2=(Point)p1.Clone()
         }
    }
复制代码

下面我们来看一个稍微复杂一点的。

 

复制代码
using System;
class point : ICloneable //Point类
{
     private int x;
     private int y;
     public Point(int x,int y)
     {
          this.x=x;
          this.y=y;
         }
         public object Clone() //实现ICloneable接口
         {
              Point p=new Point(this.x,this.y)
              return p;
             }
    }
class Rectangle:ICloneable  //Rectangle类
{
     private int width;
     private int height;
     Point p;
     public Rectangle(int width,int height,int x,int y)
     {
          this.width=width;
          this.height=height;
          this.p=new Point(x,y);
         }
         
         public object Clone()   //实现ICloneable接口
         {
              Rectangle r=new Rectangle();
              r.width=this.width;
              r.height=this.height;
              r.p=this.p;
              return r;
             }
    }
class Test
{
     public static void Main()
     {
          Rectangle r1=new Rectangle(10,20,30,40);
          Rectangle r2=(Rectangle)r1.Clone();
         }
    }
复制代码

此时,r1克隆以后得到r2,注意,这里是浅克隆,r1和r2有共享的部分,浅克隆在很多时候是不正确的。如果r1被修改,那么r2与r1共享的部分也会被修改(string除外)。那么我们应该如何实现深克隆了,在这里我们修改上面的代码:

 

复制代码
using System;
class point : ICloneable //Point类
{
     private int x;
     private int y;
     public Point(int x,int y)
     {
          this.x=x;
          this.y=y;
         }
         public object Clone() //实现ICloneable接口
         {
              Point p=new Point(this.x,this.y)
              return p;
             }
    }
class Rectangle:ICloneable  //Rectangle类
{
     private int width;
     private int height;
     Point p;
     public Rectangle(int width,int height,int x,int y)
     {
          this.width=width;
          this.height=height;
          this.p=new Point(x,y);
         }
         
         public object Clone()   //实现ICloneable接口
         {
              Rectangle r=new Rectangle();
              r.width=this.width;
              r.height=this.height;
              if (this.p!=null)
              {
                r.p=(Point)this.p.Clone();
              }
              return r;
             }
    }
class Test
{
     public static void Main()
     {
          Rectangle r1=new Rectangle(10,20,30,40);
          Rectangle r2=(Rectangle)r1.Clone();
         }
    }
复制代码

上面的代码中我们修改了对p对象的拷贝方法,实现了深克隆,但前提是Point类也必须实现了ICloneable接口,且Point类实现的是深克隆。在这里由于Point类只包含x和y两个字段,均为值类型,因此拷贝以后无共享成分。

4.数组与集合对象克隆

 这种克隆仅克隆集合中的元素,不克隆元素所引用的字段,即是一种浅克隆,数组默认有一个克隆函数,不需要实现接口。下面我们看一段代码:

 

复制代码
using System;
class Point
{
     private int x;
     private int y;
     public Point(int x,int y)
     {
          this.x=x;
          this.y=y;
         }
    }
    class Test
    {
         public static void Main()
         { 
               Point[] points=new Point[10];    
               for(int i=0;i<points.Length;i++)
               {
                    Point[i]=new Point(i*10,i*20);
               }    
                 Point[] points2=(Point[])points.Clone();    
         }
        }
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值