前言:类中的数据和函数称为类的成员。成员的可访问性可以是public protected internal protected、private或internal。

1.数据成员:

数据成员是包含类的数据(字段)、常量和事件的成员。数据成员可以是静态数据。类成员总是实例成员,除非用static进行显示的声明。字段是与类相关的变量。我们写代码先来表示一个类:

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

namespace _1.类的结构的区别
{
    public class PhoneCustomer2
    {
        public const string DayOfSendingBill = "Monday";        //发送账单的日期
        public int CustomerID;                                  //员工的ID号        
        public string FirstName;                                //
        public string LastName;                                 //
    }
}

一旦实例实例化了PhoneCustomer对象,就可以使用语法Object.FieldName来访问这些字段,如下实例所示:

       static void Main(string[] args)
        {
            //类的声明
            PhoneCustomer2 phoneCustomer2 = new PhoneCustomer2();
            //给字段进行赋值的操作
            phoneCustomer2.FirstName = "Simon";
        }

常量与类的关联方式与变量与类的关联方式相同。使用 const关键字来声明常量。如果把它声明为public,就可以在类的外部进行访问它。

   public class PhoneCustomer2
    {
        public const string DayOfSendingBill = "Monday";        //发送账单的日期
        public int CustomerID;                                  //员工的ID号        
        public string FirstName;                                //
        public string LastName;                                 //
    }

事件是类的成员,在发生某些行为(如改变类的字段或者属性,或者进行了某种形式的用户交互操作)时,它可以让对象通知调用方。客户包含所谓“事件处理程序”的代码来响应事件(事件在别的博客中进行详细的介绍)。 

2.函数成员:

函数成员提供了操作类中数据的某些功能,包括方法、属性、构造函数和终结器、运算符以及索引器。 

2.1:方法是某个类相关的函数,与数据成员一样,函数成员默认是实例成员。使用static修饰符可以把方法定义为静态方法。

2.2:属性是可以从客户端访问的函数组,其访问方式与访问类的公共字段类似。c#为读写类中的属性提供了专用的语法,所以不必使用那些名称中嵌有Get()或者Set()的方法。因为属性的这种方法不同于一般函数的语法,在客户端代码中,虚拟的对象被当作实际的东西。

2.3:构造函数是在实例化对象是自动调用的特殊的函数。他们必须与所属的类同名。且不能有返回类型、构造函数用于初始化字段的值。

2.4:终结器类似于构造函数,但是在CLR检测到不再需要某个对象时调用它。他们的名称与类相同。但是前面有一个“~”符号。不可能预测什么时候调用终结器(本章不做详细的解释)。

2.5:运算符执行的最简单的操作就是加法与减法。在两个整数相加时,严格的说,就是对整数使用“+”运算符。c#还允许指定把已有的运算符应用于自己的类(运算符的重载)。

2.6:索引器允许对象以数组或者集合的方式进行索引。

2.7:方法:

注意:正式的c#术语中区分函数与方法。“函数成员”不仅包含方法,也包含类或结构的一些非数据成员,如索引器、运算符、构造函数和析构函数,甚至还有属性。这些都不是数据成员。字段、常量、事件才是数据成员。

2.7.1:方法的声明:

在c#中,方法的定义包含任意方法的修饰符(如方法的可访问性)、返回值的类型,然后依次是方法名、输入参数的列表(用圆括号表示)和方法体(用花括号括起来)。实例如下:

[访问修饰符]返回类型 方法名([参数] )
{
  方法的主体
}

 每个参数都包括参数的类型名和在方法体中的引用的名称。但如果方法有返回值,return语句就必须与返回值一起使用。以指定出口点:

        public bool IsSquare(Rectangle rect)
        {
            return (rect.Height == rect.Width);
        }

如果没有返回值的话,就把返回类型指定为void,因为不能省略返回类型。如果方法不带参数,仍需要在方法名的后面包含一对空的圆括号(),此时return语句是可选的--当到达花括号时,方法会自动返回,注意方法可以包含任意多条return语句。

        /// <summary>
        /// 是否为正整数
        /// </summary>
        /// <param name="value">传入的值</param>
        /// <returns><返回true或者是false/returns>
        public bool IsPositive(int value)
        {
            if (value < 0)
            {
                return false;
            }
            return true;
        }

2.7.2:方法的调用:

在下面的例子中,MathTest说明了类的定义和实例化、方法的定义和调用的语法。除了包含Main()方法的类之外,它还定义了MathTest,该类包含了几个方法和一个字段:

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

namespace _1.类的结构的区别
{
    /// <summary>
    /// 数学类
    /// </summary>
    public class ClassMathTest
    {

        private int _value;                     //数值字段的定义

        /// <summary>
        /// 得到相乘的结果
        /// </summary>
        /// <returns>返回最后的结果</returns>
        public int GetSquare()
        {
            return _value * _value;
        }

        /// <summary>
        /// 得到结果
        /// </summary>
        /// <param name="x">外部传入的值</param>
        /// <returns>得到结果</returns>
        public static int IntGetSquareOf(int x)
        {
            return x * x;
        }

        /// <summary>
        /// 得到数值
        /// </summary>
        /// <returns>返回数值</returns>
        public static double GetPi()
        {
            return 3.14159;
        }

    }
}

客户端程序的调用:

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

namespace _1.类的结构的区别
{
    class Program
    {
        static void Main(string[] args)
        {

            //类的实例化
            ClassMathTest classMathTest = new ClassMathTest();

            Console.WriteLine("调用静态的方法得到的值:{0}", ClassMathTest.IntGetSquareOf(5));

            Console.WriteLine("调用静态的方法得到的值:{0}", ClassMathTest.GetPi());

            //实例方法的调用
            Console.WriteLine("调用实例方法得到的值:{0}", classMathTest.GetSquare());

            Console.ReadKey();

        }
    }
}

结果如下:

类

从代码中可以看出,MathTest类包含一个字段和一个方法,该字段包含一个数字,该方法计算该数字的平方。这个类还包含两个静态的方法。一个返回pi的值,另一个计算作为参数传入的数字的平方。

这个类的功能并不是设计c#程序好例子,例如,GetPi()通常作为const字段来执行,而好的设计应使用目前还没有介绍的概念。

2.7.3:给方法传递参数:

参数可以通过值传递和引用传递的方式传递给方法。在变量通过引用传递给方法时,被调用的方法得到的就是这个变量,更准确的说,是指向内存中变量的指针。所以在方法内部对变量进行的任何的改变在方法退出后仍然有效。而如果变量通过值传递的方式传送给方法,被调用的方法得到的是变量的一个相同的副本。也就是说,在方法退出后,对变量进行修改会丢失。对于复杂的数据类型,按引用传递的效率更高,因为在按值传递的时候,必须赋值大量的数据。

在c#中,除非特别指定,所有的引用类型都通过引用传递,所有的值类型都通过值传递。但是,在理解引用类型的含义时需要注意。因为引用类型的变量值包含对象的引用。作为参数传递的正是这个引用,而不是对象的本身。所以底层的对象的修改会保留下来。相反,值类型的变量包含的是实际的数据,所以传递给方法的是数据本身的副本。例如:int通过值传递给方法,对应方法对该int的值所做的任何的改变都没有改变原int对象的值。但是如果把数组或者其他引用类型(如类)传递给方法,对应的方法就会使用该引用改变这个数组的值,而新值会反射在原始数组对象上。我们下面例子说明参数的值类型的引用类型的区别:

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

namespace _2.给方法传递参数
{
    public class ParameterTest
    {
        /// <summary>
        /// 静态方法的调用
        /// </summary>
        /// <param name="ints">数组</param>
        /// <param name="i">数值</param>
        public static void SomeFunction(int[] ints, int i)
        {
            ints[0] = 100;
            i = 100;
        }
    }
}


客户端程序的调用:

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

namespace _2.给方法传递参数
{
    class Program
    {
        static void Main(string[] args)
        {

            int i = 0;
            int[] ints = { 0, 1, 2, 3, 4, 8 };
            Console.WriteLine("i={0}", i);
            Console.WriteLine("ints[0]={0}", ints[0]);

            //方法的调用
            ParameterTest.SomeFunction(ints, i);
            Console.WriteLine("i={0}", i);
            Console.WriteLine("ints[0]={0}", ints[0]);
            Console.ReadKey();

        }
    }
}

截图如下:

类

这时候我们发现i的值保持不变,而在ints中改变的值在原始的数组中也改变了。注意字符串是不可变的(如果改变字符串的值,就会创建一个全新的字符串),所以字符串无法采用一般引用类型的行为方式。在方法的调用的时候,对字符串所做的任何的修改都不会影响到原始的字符串(在别的博客中做详细的介绍)。

2.7.4:ref参数

如前所述,通过值传送变量是默认的。也可以迫使值参数通过引用传递给方法。为此,要使用ref关键字。如果把一个参数传递给方法,且这个方法的输入参数中带有ref关键字,则该方法对变量所做的任何的改变都会影响到原始对象的值。

这样我们对代码稍作修改(添加ref关键字):

        public static void SomeFunction(int[] ints, ref int i)
        {
            ints[0] = 100;
            i = 100;
        }

类

c#仍要求对传递给方法的参数进行初始化,理解这一点也非常重要。在传递方法之前,无论是按值传递,还是按引用传递,任何的变量都必须初始化。

2.7.5:out参数:

c#要求变量在被引用的前必须用一个初值进行初始化。尽管在吧输入变量传递给函数前,可以用没有意义的值初始化它们,因为函数将使用真实、有意义的值初始化他们,但是这样做是没有必要的。有时还会引起混乱。但是有一种方法能够简化c#编译器所坚持的输入参数的初始化。

我们可以使用out参数来初始化。在方法的时候输入参数前面加上out前缀的时候,传递给该方法的变量可以不进行初始化。该变量通过引用传递。所以在从被调用的方法中返回时,对应方法对该变量进行的任何的改变都会保留下来。在调用该方法的时候,还需要使用out关键字。与在定义该方法时一样:

        public static void SomeFunction(out int i)
        {
            //在其方法的内部必须为其赋值
            i = 100;
        }


方法的调用:

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

namespace _2.给方法传递参数
{
    class Program
    {
        static void Main(string[] args)
        {
            int j;
            ParameterTest.SomeFunction(out j);
            Console.WriteLine(j);
            Console.ReadKey();
        }
    }
}

类

3:属性:

属性的概念:它是一个方法或者一对方法。在c#中我们可以这样定义属性:

        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

Get访问器不带任何的参数,且必须返回属性声明的类型。也不应为Set访问器指定任何显示参数,但是编译器假定它带有一个参数,其类型也和属性相同。并表示为Value。例如上面的代码中包含一个属性Age,它设置了一个字段age,在这个例子中,age表示属性Age的后备变量。

在这里注意的是:我们采用c#的区分大小写模式,使用相同的名称,但共有属性采用Pascal大小写形式命名。如果存在一个等价的私有的字段,则它采用camel大小写形式命名。我们喜欢使用把下划线作为前缀的字段名,如 _age 这会为识别字段提供极大的便利。

3.1:自动属性的实现:

如果属性的set和get访问器中没有任何的逻辑。就可以使用自动实现的属性。这种属性会自动的实现后备成员变量。前面的Age实例的代码如下:

        public string Name { get; set; }

不需要声明private int _age;,编译器会自动的创建它。

使用了自动实现的属性,就不能再属性设置中验证实行的有效性。所以在上面的例子中,不能检验是否设置了无效的年龄。但是必须有两个访问器。尝试把该属性设置为只读属性,就会报错。但是,每个访问器的访问的级别可以不同。因此,下面的代码是合法的:

        public string Name { get; private set; }

 4:构造函数:

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

    public class Person
    {
        /// <summary>
        /// 构造函数的定义
        /// </summary>
        public Person()
        {

        }
    }

一般的情况下,如果我们没有提供任何的构造函数的话,编译器会在后台创建一个默认的构造函数。这是一个非常基本的构造函数,它只能把所有的成员字段初始化为标准的默认值(例如,引用类型为空引用,数值数据类型为0,bool为false),这通常就足够了,否则就需要编写自己的构造函数了。构造函数的重载遵循与其他方法相同的规则。换言之,可以为构造函数提供任意多的重载,只是他们的签名有明显的区别:

        /// <summary>
        /// 构造函数的定义
        /// </summary>
        public Person()
        {

        }

        public Person(string name)
        {

        }

但是如果提供了带参数的构造函数,编译器就不会自动提供了默认的构造函数。只是在没有定义任何构造函数的时候,编译器才会自动提供默认的构造函数。在下面的例子中,因为定义了一个带单个单数的构造函数,编译器会假定这是可用的唯一构造函数,所以他不会隐式的提供其他的构造函数:

    public class Person
    {

        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public Person(string name)
        {
            this.Name = name;
        }

    }


上面的代码还说明,一般的使用this关键字区分成员字段和同名的参数。如果试图使用无参数的构造函数实例化Person,就会得到一个编译错误。

我们还可以构造函数定义为private和protected,这样不相关的子类就不能访问它了。

5:静态构造函数:

当然我们也可以给类编写无参数的静态的构造函数。这种构造函数只执行一次。而上面的构造函数是实例构造函数,只要创建类的对象,就会执行它。编写静态的构造函数的一个原因是:类有一些静态字段或者属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性。

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

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

下面用一个例子来说明静态构造函数的用法,为了简单起见,假定只有一个用户首选项——BackColor,它表示要早应用程序中使用的背景色。假定该首选项在工作日中的背景色是红色。在周末的时候是绿色。代码如下:

    public class UsePreferences
    {

        //字段为只读类型,只能在构造函数中设置
        public static readonly ConsoleColor BackColor;

        /// <summary>
        /// 静态的构造函数
        /// </summary>
        static UsePreferences()
        {
            //表示时间上的一刻
            DateTime now = DateTime.Now;
            //如果是星期六或者是星期日
            if (now.DayOfWeek == DayOfWeek.Saturday || now.DayOfWeek == DayOfWeek.Sunday)
            {
                //则背景色为绿色
                BackColor = ConsoleColor.Green;
            }
            else
            {
                //否则为红色
                BackColor = ConsoleColor.Red;
            }
        }

        /// <summary>
        /// 私有的构造函数
        /// </summary>
        private UsePreferences() { }

    }

客户端代码的调用如下:

            Console.WriteLine("背景的颜色是:{0}", UsePreferences.BackColor.ToString());

            Console.ReadKey();

类

6.从构造函数中调用构造函数(使用this关键字):

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

        private string _description;
        private uint _nWheels;

        public Car(string description, uint nWheels)
        {
            this._description = description;
            this._nWheels = nWheels;
        }

        public Car(string description)
        {
            this._description = description;
            this._nWheels = 4;
        }


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

        private string _description;
        private uint _nWheels;

        public Car(string description, uint nWheels)
        {
            this._description = description;
            this._nWheels = nWheels;
        }

        public Car(string description) : this(description, 0)
        {

        }

客户端代码的调用:

        static void Main(string[] args)
        {
            Car aoDi = new Car("奥迪");
        }