技术开发 频道

C#学习:方法的基本概念与实例

  【IT168技术】这篇博客的内容基本上是CLR via C#中第八章 “方法”的大致内容。

  一、实例构造器和类

  构造器是允许将类型的实例初始化为良好状态的一种特殊方法,创建一个引用类型的实例时,先为实例的数据字段分配内存,然后初始化对象的附加字段(类型对象指针和同步索引),最后调用构造函数来设置对象的初始状态。构造函数不能被继承,所以不能被virtual、new、override、sealed和abstract修饰,若没有显示定义任何构造函数,编译器将定义一个无参的public构造函数,但若是抽象类,编译器将定义一个无参的protected的构造函数

  创建一个类的实例并不一定非要调用构造函数。

  ①使用Object的MemberwiseClone()方法。他的作用就是创建当前 System.Object 的浅表副本,内部工作机制是分配内存,初始化对象的附加字段(类型对象指针和同步索引),然后将源对象的字节数据复制到新对象中。从下面的代码可以看出MemberwiseClone()实现了对象复制,而不是简单的对象引用。

class Program { 
       public int X;
        static void Main(string[] args)
        {
            Program p = new Program(); 
            p.X = 1;
            Program q = (Program)p.MemberwiseClone(); 
           Program z = p;
            p.X = 2;
            Console.WriteLine("p.X=" + p.X);//输出:p.X=2
            Console.WriteLine("q.X=" + q.X);//输出:q.X=1
            Console.WriteLine("z.X=" + z.X);//输出:z.X=2
            Console.Read();
        }
    }

  ②反序列化。在网络编程的时候,经常将一个对象序列化成二进制,然后传输出去,接收端反序列化成原来的对象,反序列化使用的是类System.Runtime.Serialization.FormatterServices的方法public static object GetUninitializedObject(Type type)或public static object GetSafeUninitializedObject(Type type)分配内存,而在这两个方法内部没有调用要被反序列化对象的构造函数。

  字段的初始化代码会被编译器自动添加到相应的构造函数中,非静态字段的初始化代码会自动加到实例构造函数中,静态字段的初始化代码则添加到静态构造函数中,如果你的代码中有多个字段被初始化,还有多个构造函数的话,初始化代码在每个构造函数中都会有一份,这无疑会让你的生成文件(如DLL,EXE文件)变大。

  证明这一点的代码如下:

public class SomeType
    {
        public  int x=1,y=2,z=3;
        public SomeType() { }
        public SomeType(int x, int y)
        {
            this.x = x;
            this.y = y; 
       }
        public SomeType(int x)
        {
            this.x = x;
        }
}//经过IL反编译,方法SomeType(int x, int y)代码如下
.method public hidebysig specialname rtspecialname
         instance void  .ctor(int32 x,
                           int32 y) cil managed
{
  // 代码大小       45 (0x2d)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldc.i4.1
  IL_0002:  stfld      int32 MyTest.SomeType::x
  IL_0007:  ldarg.0
  IL_0008:  ldc.i4.2
  IL_0009:  stfld      int32 MyTest.SomeType::y
  IL_000e:  ldarg.0
  IL_000f:  ldc.i4.3
  IL_0010:  stfld      int32 MyTest.SomeType::z
  IL_0015:  ldarg.0
  IL_0016:  call       instance void [mscorlib]System.Object::.ctor()
  IL_001b:  nop
  IL_001c:  nop  IL_001d:  ldarg.0
  IL_001e:  ldarg.1
  IL_001f:  stfld      int32 MyTest.SomeType::x
  IL_0024:  ldarg.0
  IL_0025:  ldarg.2
  IL_0026:  stfld      int32 MyTest.SomeType::y
  IL_002b:  nop
  IL_002c:  ret} // end of method SomeType::.ctor

  先执行了初始化代码,其他构造函数都包含了初始化代码,然后在执行构造函数中的赋值代码,要解决这个代码膨胀问题,方法很简单,把初始化代码写在无参构造函数中,让其他构造函数调用。

  二、实例构造器和结构

  值类型的工作方式与引用类型截然不同,值类型其实并不需要定义构造函数,地球人根本阻止不了值类型实例化,编译器根本不会生产默认无参构造函数,如果你显示声明无参构造函数,编译根本通过不了,报错“结构不能包含显式的无参数构造函数”。由于值类型存在栈中,根本不需要对堆中的数据进行引用,所以我们可以在定义的时候就直接赋值,(int i=0;string s=”a”;Point p;p.X=2;)他根本不需要new,new当然是可以的,调用构造函数会初始化所有的字段成相应类型的默认值,实例如下:

        //public int z = 4;//错:结构中不能有实例字段初始值设定项
        //public Point(int x) //在控制返回调用方之前,字段“Point.y”必须被完全赋值,要解决这个问题就手动给y加一个默认值吧
        //你也可以加this=new Point();将所有的字段的值初始化为0或null
        //{
        //    this.x = x;
        //}        public void Test() 
       {
            Point p; 
            p.x = 1;
            p.y = 2;
            Console.WriteLine(p.x+","+p.y);//输出1,2
        }
    }

  结构的特点:

  不能显示定义无参构造函数

  不能在定义字段的时候初始化

  声明有参构造函数的时候,要初始化所有的字段

  三、类型构造器

  实例构造器就是静态构造函数,他的作用是设置类型的初始化状态,静态构造函数只能有一个,且是无参的,不能有访问修饰符修饰,默认就是private,由编译器调用执行。实例如下:

public class SomeType
    {
        
public static int x = 520;        static SomeType()
        {
            x
= 112;
        }
    }
        
//用.NET reflector查看源码,如下    public class SomeType
    {
        
public static int x ;
        
static SomeType()
        {
            x
= 520;
            x
= 0x70;
        }
        
public SomeType() { }
    }

   在定义静态字段并初始化,编译器会自动生成一个类型构造器(静态构造函数),并将静态字段的初始化代码插在类型构造器的前面,从上面的代码可以看出,定义时初始化和在类型构造器中初始化只需要一个即可,还有112的16进制为0x70,所以在代码中看到16进制也不用大惊小怪,根本不涉及性能问题,若定义了静态字段,但没有初始化任何一个,编译器是不会生成类型构造器的。但是静态字段还是会被初始化,其实不管是静态的还是非静态的字段都是会被编译器自动初始化的,int类型的初始化为0;bool:False;string:null,这就是为什么你在实例化实体的时候,有些字段你没有初始化,却不会报错,而且你知道没有初始化的字符串的值就是null,也就是说编译器会帮你初始化你没有初始化的字段,然而在方法中定义的局部变量是需要自己初始化的,如果你没有初始化,会报一个错误“使用了未赋值的局部变量X”。

0
相关文章