二、对象和类型(第三部分)

2.属性

属性(property)的概念是:它是一个方法或一对方法,在客户端代码看来,它(们)是一个字段。例如Windows窗体的Height属性。假定有下面的代码:

                                     mainFrom.Height = 400;

执行这段代码时,窗体的高度设置为400,因此窗口会在屏幕上重新设置大小。在语法上,上面的代码类似与设置一个字段,但实际上是调用了属性访问器,它包含的代码重新设置了窗体的大小。

 

1.只读和只写属性

在属性定义中省略set访问器,就可以创建只读属性。

同样的,在属性定义中省略get访问器,就可以创建只写属性。到那时,这是不好的编程方式,因为这可能会使客户端代码的作者感到迷惑,一般情况下,如果要这么作,最好使用一个方法代替。

2.属性的访问修饰符

C#允许给属性的get和set访问器设置不同的访问修饰符,所以属性可以有公有的get访问器和私有或受保护的set访问器。这有助于控制属性的设置方式或时间。get访问器具有属性的访问级别。在get和set访问器中,必须有一个具备属性的访问级别。如果get访问器的访问级别是peotected,就会产生一个编译错误,因为这会使两个访问器的访问级别都不是属性。

3.自动实现的属性

3.构造函数

声明基本构造函数的语法就是声明一个与包含的类同名的方法,但该方法没有返回类型。

没有必要被类提供构造函数。一般情况下,如果没有提供任何构造函数,编译器会在后台创建一个默认的构造函数。这是一个非常基本的构造函数,它只能把所有的成员字段初始化为标准的默认值(例如,引用类型为空引用,数值数据类型化为0,bool为false)。这通常就足够了,否则就需要编写自己的构造函数。

构造函数的重载遵循与其它方法相同的规则。换言之,可以为构造函数提供任意多的重载,只要它们的签名有明显的区别即可。

但如果提供了带参数的构造函数,编译器就不会自动提供默认的构造函数。只有在没有定义任何构造函数时,编译器才会自动提供默认的构造函数。只有在没有定义任何构造函数时,编译器才会自动提供默认的构造函数。

注意,把构造函数定义为private或protected,这样不相关的类就不能访问它们。

注意:如果将构造函数定义为private的,这就是该类不能使用new运算符在外部代码中实例化(但可以在该类中编写一个公有的静态属性或方法,以实例化该类)。这在下面的两种情况下是有用的:

     *类仅用作某些静态成员或属性的容器,因此永远不会实例化它。

     *希望类仅通过调用某些静态成员函数来实例化(这就是所谓对象实例化的类工厂方法)

1.静态构造函数

  C#的一个新特性是也可以给类编写无参数的静态构造函数。这种构造函数只执行一次,而前面的够造函数时实例构造函数,只要是常见类的对象,就会执行它。

  编写静态构造函数的一个原因是,类有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性。

  .NET运行库没有确保什么时候执行静态构造函数,所以不应把要求在某个特定时刻(例如,加载程序集时)执行的代码放在静态构造函数中。也不能预计不同类的静态构造函数按照什么顺序执行。但是,可以确保静态构造函数之多运行一次,即在代码引用类之前调用它。在C#中,通常在第一次调用类的任何成员之前执行静态构造函数。

  注意,静态构造函数没有访问修饰符,其他C#代码从来不调用它,但在加载类时,总是由.NET运行库调用它,所以像public或private这样的访问修饰符就没有任何意义。处于同样的原因,静态构造函数不能带任何参数,一个类也只能有一个静态构造函数。很显然,静态构造函数只能访问类的静态成员,不能访问类的实例成员。

  无参数的实例构造函数与静态构造函数可以在同一个类中同时定义。尽管参数列表相同,但这并不矛盾,因为在加载类时执行静态构造函数,而在创建实例时执行实例构造函数,所以何时执行哪个构造函数并不会有冲突。

  如果多个类都有静态函数,先执行哪个静态构造函数就不确定。此时静态构造函数中的代码不应依赖于其他静态构造函数的执行情况。另一方面,如果任何静态字段有默认值,就在调用静态构造函数之前指定它们。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    public class MYClass
    {
        private int a = 0;
        public static int b = 0;

        public MYClass()
        {
            this.a = 100;
        }

        static MYClass()
        {
            b = 100;
            Console.WriteLine("b:" + b);
        }

        public void Print()
        {
            Console.WriteLine("a:" + this.a);
        }
            
    }

    class Program
    {
        static void Main(string[] args)
        {
            MYClass mc = new MYClass();
            mc.Print();
            Console.ReadKey();
        }
    }
}

 

2.从构造函数中调用其他构造函数

  有时,在一个类中有几个构造函数,以容纳某些可选参数,这些构造函数包含共有的代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    public class Car
    {
        private string m_carName = "";
        private int m_carAge = 0;
        public Car(string _carName)
        {
            m_carName = _carName;
            m_carAge = 0;
        }

        public Car(string _carName, int _carAge)
        {
            m_carName = _carName;
            m_carAge = _carAge;
        }

        public void Print()
        {
            Console.WriteLine("这辆车的名字是:" + m_carName + " 这辆车已经使用了:" + m_carAge + "");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Car car = new Car("大众甲壳虫");
            Car car2 = new Car("雪佛兰迈瑞宝", 5);
            car.Print();
            car2.Print();
            Console.ReadKey();
        }
    }
}

 

  这两个构造函数初始化了相同的字段,显然,最好把所有的代码放在一个地方。C#有一个特殊的语法,称为构造函数初始化器,可以实现此目的:

  这里,this 关键字仅调用参数最匹配的那个构造函数。注意,构造函数初始化器在构造函数的函数体之前执行。

  C#构造函数初始化器可以包含对同一个类的另外一个构造函数的调用(使用前面介绍的语法),也可以包含对直接基类的构造函数的调用(使用相同的语法,但应使用base关键字代替this)。初始化器中不能有多个调用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    public class Car
    {
        private string m_carName = "";
        private int m_carAge = 0;
        private string m_carHostName = "";

        public Car(string _carName, int _carAge, string _carHostName)
        {
            m_carName = _carName;
            m_carAge = _carAge;
            m_carHostName = _carHostName;
        }

        public Car(string _carName, int _carAge) : this(_carName, _carAge, "新车")
        {
        }

        

        public Car(string _carName) : this(_carName, 0, "新车")
        {
        }

        

        public void Print()
        {
            Console.WriteLine("这辆车的名字是:" + m_carName + " 这辆车已经使用了:" + m_carAge + "年 他的主人是:" + m_carHostName);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Car car = new Car("大众甲壳虫");
            Car car2 = new Car("雪佛兰迈瑞宝", 5);
            Car car3 = new Car("宝马X5", 1, "Dean");
            car.Print();
            car2.Print();
            car3.Print();
            Console.ReadKey();
        }
    }
}

 

3.只读字段

  常量的概念就是一个包含不能修改的值的变量,常量是C#与大多数编程语言共有的,但是,常量不必满足所有要求。有时可能需要一些变量,其值不应改变,但在运行之前其值是未知的。C#为这种情形提供了另一种类型的变量:只读字段。

  readonly关键字比const灵活得多,允许把一个字段设置为常量,但还需要执行一些计算,以确定它的初值。其规则是可以在构造函数中给只读字段赋值,但不能在其它地方赋值。只读字段还可以是一个实例字段,而不是静态字段,类的每个实例可以有不同的值,与const字段不同,如果把只读字段设置为静态,就必须显示声明它。

  如果有一个用于编辑文档的MDI程序,因为要注册,所以需要限制可以同时打开的文档数。现在假定要销售该软件的不同版本,而且顾客可以升级他们的版本,以便同时打开更多的文档。显然,不能在源代码中对最大文档数进行硬编程,而是需要一个字段表示这个最大文档数。这个字段必须是只读的------每次启动程序时,从注册表键或其它文件存储中读取。

  还要注意,在构造函数中不必给只读字段赋值。如果没有赋值,它的值就是其特定数据类型的默认值,或者在声明时给它初始化的值。这适用于只读的静态字段和实例字段。

 

 

 

 

 

 

 

 

 

 

posted @ 2016-11-08 17:30  Dean二十七  阅读(164)  评论(0编辑  收藏  举报