首页 > 代码库 > 类和结构(二)

类和结构(二)

二.匿名类型
  var关键字用于表示隐式类型化得变量。var和new关键字一起使用时,可以创建匿名类型。
  匿名类型只是一个继承自Object且没有名称的类。
  var caption = new {FirstName = "John",LastName="Doe"};
  这会生成一个包含FirstName,LastName属性的对象。
  创建另一个对象:
  var doctor = new {FirstName = "James",LastName="Mc"};
  caption和doctor的类型就相同,可以设置caption = doctor

  如果设置的值来自于另一个对象,就可以简化初始化器。
  var doctor = new {caption.FirstName,caption.LastName};

  这些对象的类型名未知。编译器为类型“伪造”了一个名称,但只有编译器才能使用它。

三.结构(struct)
  如果仅需要一个小的数据结构,此时类提供的功能多余我们需要的功能,由于性能原因,最好使用结构。
  结构是值类型,它们存储在栈中或存储为内联(inline)(如果它们是存储在堆中的另一个对象的一部分),其生存期的限制与简单的数据类型一样。
  *结构不支持继承。
  *对于结构,构造函数的方式与类有一些区别。编译器总是提供一个无参数的默认构造函数,它是不允许替换的。
  *使用结构可以指定字段如何在内存中布局(后面详细介绍)

  结构实际上是把数据项组合在一起,有时大多数字段都声明为public。严格来说,这与编写.net代码的规则相反(字段应总是私有的(除const字段外),并由公有属性封装)。但是,对于简单的结构,公有字段是可以接受的编程方式。

四.类和结构的区别
  1.结构是值类型
  虽然结构是值类型,但在语法上可以把它当作类来处理。

    struct PhoneCusStruct
    {
        public const string DaySend = "Mon";
        public int CusId=0;
    }

    PhoneCusStruct phoneCusStruct = new PhoneCusStruct();
    phoneCusStruct.CusId=3;    


  因为结构是值类型,所以new运算符与类和其它引用类型的工作方式不同。new运算符并不分配堆中的内存,而只是调用相应的构造函数,根据传送给它的参数,初始化所有的字段。
  对于结构编写下面的代码是合法的:
  PhoneCusStruct phoneCusStruct;
  phoneCusStruct.CusId=3;

  结构遵循其它数据类型都遵循的规则:在使用前所有的元素都必须进行初始化。在结构上调用new运算符,或者给所有的字段分别赋值,结构就完全初始化了。
  如果结构定义为类的成员字段,在初始化包含的对象时,该结构会自动初始化为0.
  结构是会影响性能的值类型,但根据使用结构的方式,这种影响可能是正面的,也可能是负面的。正面的影响是为结构分配内存时,速度很快,因为它们将内联或保存在栈中。在结构超出了作用域被删除时,速度也很快,不需要等待垃圾回收。负面影响是,只要把结构作为参数来传递或者把一个结构赋予另一个结构,结构的内容就会被复制,而对于类只复制引用。这样就会有性能损失,根据结构的大小,性能损失也不同。
  注意,结构主要用于小的数据结构。当把结构作为参数传递给方法时,应把它作为ref参数传递,以避免性能损失(这样只传递了结构在内存中的地址)。

  2.结构和继承
  结构不能从一个结构中继承。唯一的例外是对应的结构(和其它类型一样)最终派生于类System.Object。因此结构也可以访问Object的方法。
  在结构中也可以重写Object中的方法——如ToString()方法。
  结构的继承链是:每个结构派生于System.ValueType类,System.ValueType类有派生于System.Object。ValueType并没有给Object添加任何成员,但提供了一些更适合结构的实现方法。
  注意,不能为结构提供其它基类。

  3.结构的构造函数
  为结构定义构造函数的方式与类的方式相同,但不允许定义无参数的构造函数。因为在一些罕见的情况下,.NET运行库不能调用用户提供的自定义无参数构造函数,因此Microsoft干脆采用禁止在C#的结构内使用无参数的构造函数。
  默认构造函数会隐式的把字段初始化,即使提供了其它带参数的构造函数,也会先调用它。提供字段的初始值也不能绕过默认构造函数。下面代码会编译错误:
  struct PhoneCusStruct
  {
    public int CusId =0;
  }
  如果PhoneCusStruct声明为一个类,就不会报错了。
  另外,可以像类那样为结构提供Close()或Dispose()方法。

五.弱引用
  在应用程序代码内实例化一个类或结构时,只要有代码引用这个对象,就会形成强引用。这意味着垃圾回收器不会清理这个对象使用的内存,一般而言这是好事,因为可能需要引用这个对象,但是如果这个对象很大,而且不经常访问。这个时候可以创建对象的弱引用。
  弱引用允许创建和使用对象,但在垃圾回收器运行时,就会回收对象并释放内存。由于存在潜在的Bug和性能问题,一般不会这么做,但在特定情况下使用是合理的。
  弱引用使用WeakReference类创建。因为对象可能在任意时刻被回收,所以引用该对象前必须确认它的存在。

    

class MainEntryPoint
{
static void Main()
{
// Instantiate a weak reference to MathTest object
WeakReference mathReference = new WeakReference(new MathTest()); 
MathTest math;
if(mathReference.IsAlive)
{
math = mathReference.Target as MathTest;
math.Value = 30;
Console.WriteLine(
"Value field of math variable contains " + math.Value);
Console.WriteLine("Square of 30 is " + math.GetSquare());

}
else
{
Console.WriteLine("Reference is not available.");
}

GC.Collect();

if(mathReference.IsAlive)
{
math = mathReference.Target as MathTest;
}
else
{
Console.WriteLine("Reference is not available.");
}
}
}

// Define a class named MathTest on which we will call a method
class MathTest
{
    public int Value;

    public int GetSquare()
    {
        return Value*Value;
    }

    public static int GetSquareOf(int x)
    {
        return x*x;
    }

    public static double GetPi()
    {
        return 3.14159;
    }
}

 



六.部分类
  partial关键字允许把类,结构,接口放在多个文件中。
  partial关键字的用法:把partial放在class,struct,interface前面即可。
  如果声明类时使用了下面的关键字,这些关键字就必须应用于同一个类的所有部分:
    public,private,protected,internal,abstract,sealed,new,一般约束
  在把部分类编译后,类的成员和继承等会合并。

七.静态类
  如果类只包含静态的方法和属性,该类就是静态的。静态类在功能上与使用私有静态构造函数创建的类相同。都不能创建静态类的实例。
  使用static关键字,编译器可以检查用户是否给该类添加了实例成员。如果是,就会生成一个编译错误。这可以确保不创建静态类的实例。
  static class PhoneCusStruct
  {
    public static void GetPhene()
    {

    }

  }
  调用:PhoneCusStruct.GetPhene();

八.Object类
  前面提到,所有的.NET类都派生自System.Object类.实际上,如果在定义类的时候没有指定基类,编译器就会自动假定这个类派生自Object类。其实际意义在于,除了自己定义的方法和属性等外,还可以访问Object定义的许多公有的和受保护的成员方法。这些方法可用于自己定义的其它类中。

  System.Object的方法:
  1.GetHashCode():如果对象放在名为映射(散列表或字典)的数据结构中,就可以使用这个方法。处理这些结构的类使用该方法可以确定把对象放在结构的什么地方。如果希望把类用作字典的一个键,就需要重写这个方法。(后面介绍字典时会详细介绍)
  2.Equals()和ReferenceEquals()方法:后面会详细介绍。
  3.Finalize()方法:在引用对象作为垃圾被回收以清理资源时调用它。Object中实现的Finalize()方法实际上什么也没有做,因而被垃圾回收器忽略。如果对象拥有对未托管资源的引用,则在该对象被删除时,就需要删除这些引用,此时一般要重写Finalize()方法。垃圾收集器不能直接删除这些未托管资源的引用,因为它只负责托管的资源,于是它只能依赖用户提供的Finalize()方法。垃圾收集器不能直接删除这些未托管资源的引用,因为它只负责托管的资源,于是它只能依赖用户提供的Finalize方法。(后面会详细介绍)
  4.GetType()方法:这个方法返回从System.Type派生的类的一个实例。这个对象可以提供对象成员所属类的很多信息。System.Type还提供了.NET的反射技术的入口。(后面详细介绍)
  5.MemberwiseClose()方法:这个方法复制对象,并返回对副本的一个引用(对于值类型,就是一个装箱的引用)。得到的副本是一个浅表复制,即它复制了类中的所有值类型。如果类包含内嵌的引用,就只复制引用,而不复制引用的对象。这个方法是受保护的,所以不能用于复制外部的对象(可以复制父类的对象)。该方法不是虚方法,所以不能重写。
  6.ToString()方法:是获取对象的字符串表示的一种快捷方式。当只需要快速获取对象的内容,以进行调试时,就可以使用这个方法。在数据的格式化方面,它几乎没有提供选择,比如:在原则上日期可以表示为许多不同格式,但DateTime.ToString()没有在这方面提供任何选择。这个方法是虚方法,可以重写这个方法以返回这些类型的正确字符串表示。

 

九.扩展方法
  如果有类的源码,继承就可以给对象添加方法。但如果没有源代码,则可以使用扩展方法,它允许改变一个类,但不需要该类的源代码。
  扩展方法是静态方法,它是类的一部分,但实际上没有放在类的源代码中。假定PhoneCusStruct类需要一个Add()方法,但不能修改源代码,就可以创建一个静态类,把Add()方法添加为一个静态方法:
  public static class PhoneExtension
  {
    public static void Add(this PhoneCusStruct phoneCusStruct,string phone)
    {
    //
    }
  }

  注意扩展方法的第一个参数是要扩展的类型,它放在this关键字的后面。这告诉编译器,这个方法是PhoneCusStruct类型的一部分。在这个例子中,PhoneCusStruct是要扩展的类型。在扩展方法中,可以访问所扩展类型的所有公有方法和属性。
  调用:PhoneCusStruct p =new PhoneCusStruct();
      p.Add();//即使方法是静态方法,也需要使用实例方法的语法。

  如果扩展方法与类中的某个方法同名,就不会调用扩展方法。类中已有的任何实例方法优先。

类和结构(二)