C#学习笔记第二篇

面向对象编程

OOP三大特性:封装,继承,多态。封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或对象操作,对不可信的进行信息隐藏。在C#中,OOP技术的一个应用就是WPF桌面应用程序。

定义类

对象是OOP应用程序的组成部件,类是用于实例化对象的类型定义。对象可以包含数据,提供其他代码可以使用的操作。数据可以通过属性供外部代码使用,操作可以通过方法供外部代码使用。属性和方法都称为类的成员。属性可以进行读取访问,写入访问或读写访问。在.NET 中,所有的东西都是对象。

C#使用 class 关键字来定义类:

class MyClass : MyBase, MyInterface, MySecInterface  //用逗号将基类名和接口名分隔开,如果未指定基类,接口就跟在冒号的后面。
{
    //code
}

默认情况下,类声明为内部的,即只有当前项目中的代码才能访问它。可使用 internal 访问修饰符关键字来显式地指定这一点。另外,还可以使用 public 关键字指定类是公共的,abstract 关键字指定类是抽象的(可以是公共抽象类,也可以是内部抽象类),sealed 关键字指定类是密封的(可以是公共密封类,也可以是内部密封类)。还可以在类定义中指定继承,需要在类名的后面加一个冒号,其后是基类名。编译器不允许派生类的可访问性高于基类。如果没有使用基类,被定义的类就只继承于基类System.Object。类里面还可以嵌套类。

成员定义

类定义中的所有成员(字段,方法和属性)都有自己的访问级别:

  • public:成员可以由任何代码访问,.NET Framework中的公共字段以PascalCasing形式来命名
  • private:成员只能由类中的代码访问(默认使用这个关键字)
  • internal:成员只能由定义它的程序集(项目)内部的代码访问
  • protected:成员只能由类或派生类中的代码访问
  • static/const:表示类的静态成员,而不是对象实例的成员。

字段也可以使用关键字readonly,表示这个字段只能在执行构造函数的过程中赋值,或由初始化赋值语句赋值。定义方法时也可以使用下述关键字:virtual(方法可以重写),abstract(方法必须在非抽象的派生类中重写,只用于抽象类中),override(方法重写了一个基类方法),extern(方法定义放在其他地方)。属性也可以使用virtual, abstract, override关键字,但这些关键字不能用于字段。属性定义方式与字段类似,但包含的内容比较多。属性拥有两个类似于函数的块,一个块用于获取属性的值,另一个块用于设置属性的值。这两个块也称为访问器,分别用get和set关键字来定义,用于控制属性的访问级别。可以忽略其中的一个块来创建只读或只写属性。当然,这仅适用于外部代码,因为类中的其他代码可以访问这些代码块能访问的数据。还可以在访问器上包含可访问修饰符,例如使get块变成公共的,使set块变成受保护的。C#6引入了一个名为“基于表达式的属性”的功能。例如:

private int myDoubledInt = 5;
public int MyDoubledIntProp => (myDoubledInt * 2);
重构成员

在添加属性时有一项很方便的技术,可以从字段中生成属性:在代码视图中右击某个成员,选择“快速操作和重构”,这样就可以减少为该字段创建属性的时间。

成员隐藏与重写

当从基类继承一个(非抽象的)成员时,也就继承了其实现代码。如果继承的成员是虚拟的,就可以用 override 关键字重写这段实现代码。如果无论继承的成员是否为虚拟,我们想隐藏这些实现代码,则可以使用new关键字显式地表明意图,同时还可以通过基类访问它,但重写不能通过基类访问它。

调用重写或隐藏的基类方法

无论是重写成员还是隐藏成员,都可以在派生类的内部访问基类成员,可使用 base 关键字,它表示包含在派生类中的基类的实现代码。例如:

public class MyBaseClass
{
    public virtual void DoSomething()
    {
        //Base implementation
    }
}
public class MyDerivedClass : MyBaseClass
{
    public override void DoSomething()
    {
        base.DoSomething();
        //Other implementation
    }
}

除了 base 关键字,还可以使用 this 关键字,只是 this 引用的是当前的对象实例(即不能在静态成员中使用 this 关键字,因为静态成员不是对象实例的一部分)。this 的第一个常见用法是把当前对象实例的引用传递给一个方法,第二个常见用法是限定局部类型的成员。

类与结构

结构和类非常相似,但结构是值类型,类是引用类型。

接口

接口就是把公共实例(非静态)方法和属性组合起来,以封装特定功能的一个集合。一旦定义了接口,就可以在类中实现它。接口不能单独存在,不能像实例化一个类那样实例化接口,接口不能包含实现其成员的任何代码,只能定义成员本身。实现过程必须在实现接口的类中完成。接口的定义如下:

interface IMyInterface  //接口名一般以大写字母I开头
{
	//code
}

访问修饰符关键字有 internal 和 public 两个。与类继承不同的是,一个类可以实现多个基接口。

IDisposable接口

如果我们想不再需要某个对象时,就释放这个资源,则这个类必须要实现IDisposable接口中的 Dispose() 方法。C#允许使用一种可以优化使用这个方法的结构。using 关键字可以在代码块中初始化使用重要资源的对象,在这个代码块的末尾会自动调用 Dispose() 方法,用法如下:

  = new ();
using ()
{
	...
}
//或者把初始化对象作为 using 语句的一部分:
using (  = new ())
{
	...
}

继承

继承是一个类定义派生于另一个类定义的机制。在OOP中,被继承的类称为父类(也称为基类)。C#中的对象仅能直接派生于一个基类,当然基类也可以有自己的基类。基类还可以定义为抽象类,抽象类不能直接实例化。要使用抽象类,必须继承这个类,抽象类可以有抽象成员,这些成员在基类中没有实现代码,所以派生类必须实现它们。在继承一个基类时,成员的可访问性就成了一个重要问题。派生类可以访问基类的公共成员和受保护的成员(外部代码不能访问),但不能访问其私有成员。密封(seal)的类不能用作基类。在C#中,所有对象都有一个共同的基类 object (在 .NET Framework 中,它是 System.Object 类的别名)。

多态

继承的一个结果就是派生于基类的类在方法和属性上有一定的重叠,因此,可以使用相同的语法处理从同一个基类实例化的不同对象。多态性允许把某个派生类型的变量赋给基本类型的变量,之后就可以通过这个变量调用基类的方法(实际上是调用这个派生类中同名方法的实现代码)。还可以把基本类型的变量转换为派生类变量,调用派生类的方法。实现多态的方法有覆盖(运行期确定),重载(编译期确定)。

System.Object

在继承层次结构中,所有类的根都是 System.Object。System.Object 包含的方法在这里不再列出。

构造函数与析构函数

每个对象都有一个明确定义的生命周期,除了“正在使用”的正常状态外,还有两个重要的阶段:

  • 构造阶段:第一次实例化一个对象时,需要初始化该对象。这个初始化过程称为构造阶段,由构造函数完成。
  • 析构阶段:在删除一个对象时,常常需要执行一些清理工作,例如释放内存,这由析构函数完成。

在C#中定义类时,常常不需要定义相关的构造函数和析构函数,因为在编写代码时,如果没有提供它们,编译器会自动添加它们。但如有必要,可以提供自己的构造函数和析构函数,以便初始化对象和清理对象。

class MyClass
{
	public MyClass()
	{
		//Default constructor code
	}
	
	~MyClass()       //声明析构函数
	{
		//code
	}
}

所有的类定义至少包含一个构造函数,构造函数分为默认构造函数(没有参数)和非默认构造函数(带参数)。构造函数与字段,属性和方法一样,可以是公共或私有的,在类外部的代码不能使用私有构造函数实例化对象,而必须使用公共构造函数。一些类没有公共的构造函数,外部的代码就不可能实例化它们,这些类称为不可创建的类(静态类也是不可创建的类)。

构造函数的执行序列

在C#中,任何构造函数都可以配置为在执行自己的代码前调用其他构造函数。为了实例化派生的类,必须实例化它的基类,而要实例化这个基类,又必须实例化这个基类的基类,这样一直实例化到System.Object为止。结果是无论使用什么构造函数实例化一个类,总是首先调用System.Object.Object()。无论在派生类上使用什么构造函数(默认的或非默认的),除非明确指定,否则就使用基类的默认构造函数。只需使用构造函数初始化器,就可以指定.NET 实例化过程使用基类中具有指定参数的构造函数。也可以使用 base 这个关键字指定基类构造函数的字面值。除了 base 关键字外,还可将另一个关键字 this 用作构造函数初始化器。注意在定义构造函数时,不要创建无限循环。例如:

public class MyBaseClass
{
    public MyBaseClass() : this(5)
    {

    }
    public MyBaseClass(int i) : this()
    {

    }
}

此时,编译器会报错:构造函数“MyBaseClass.MyBaseClass(int)”无法通过另一构造函数调用自身。

接口和抽象类

相同点:都包含可以由派生类继承的成员;都不能直接实例化;派生类必须实现未实现的方法。
不同点:派生类只能直接继承自一个抽象类,类可以使用任意多个接口,但这并不会产生太大的区别;抽象类的成员可以是私有的,受保护的,内部的或受保护的内部成员,而接口成员必须是公共的;接口不能包含字段,构造函数,析构函数,静态成员或常量;抽象类可以拥有抽象成员(没有代码体,必须在派生类中实现,否则派生类本身必须也是抽象的)和非抽象成员(拥有代码体,也可以是虚拟的,这样就可以在派生类中重写),而接口成员必须都在使用接口的类上实现(它们没有代码体)。

什么场景使用什么技术?
抽象类主要用作对象系列的基类,这些对象共享某些主要特性,例如共同的目的和结构。接口则主要用于类,这些类存在根本性的区别,但仍可以完成某些相同的任务。

浅度和深度复制

你可能感兴趣的:(C#)