C# 是面向对象语言,所有面向对象语言都有三个特征
封装
封装指的是把类内部的数据隐藏起来,不让对象实例直接对其操作。C# 中提供了属性机制来对类内部的状态进行操作。此外,封装可以通过 public、private、protected 和 internal 等关键字来体现
代码语言:javascript复制public class Person
{
private int _age;
public int age
{
get
{
return _age;
}
set
{
if(value > 99 && value < 0)
throw new Exception("年龄不得大于99且小于0");
_age = value;
}
}
}
使用了封装技术后,外部数据只能对属性进行操作。如果把不符合逻辑的值赋给属性,我们可以在运行时抛出异常。客户端可以通过异常捕获来进行错误处理
继承
在 C# 中,一个类可以继承另一个已有的类(密封类除外),被继承的类成为基类(父类),继承的类称为派生类(子类),子类将获得基类除构造函数和析构函数以外的所有成员
C# 中的继承
C# 与 C 不同,C# 仅支持派生于一个基类,而 C 则支持多重继承。但 C# 可以继承多个接口
代码语言:javascript复制public class Animal
{
private int _age;
public int age
{
get { return _age; }
set { _age = value; }
}
}
public class Horse : Animal
{
}
public class Sheep : Animal
{
}
这里虽然各个子类并没有定义 age 属性,但是由于它们继承各自基类 Animal,基类中又定义了 age 属性,所以子类也继承了父类的 age。这样避免了在子类中重复定义 age
密封类
密封类不可以被另外一个类继承,如果强行在代码中继承一个密封类,编译会报错
子类的初始化执行顺序
使用了继承之后,当我们初始化一个子类,除了会调用子类的构造函数外,同时也会调用基类的构造函数。子类的初始化顺序如下:
- 初始化类的实例字段
- 调用基类的构造函数,如果没有指明基类,则调用
System.Object
的构造函数 - 调用子类的构造函数
namespace ConsoleApp1
{
class Program
{
public class Model : Root
{
public Model()
{
Console.WriteLine("我是Model");
}
}
public class Root
{
public string name { get; set; }
public Root()
{
Console.WriteLine("我是Root");
}
}
static void Main(string[] args)
{
var instance = new Model();
Console.WriteLine(instance.name);
Console.ReadKey();
}
}
}
多态
由于可以继承基类的所有成员,子类就有了相同的行为,但是有时候子类的某些行为需要相互区别,子类需要覆盖父类中的方法来实现子类特有的行为。这样的技术在面向对象的编程中就是多态。多态即相同类型的对象调用相同方法却表现出不同行为的现象
代码语言:javascript复制class Program
{
public class Animal
{
public virtual void Voice()
{
Console.WriteLine("动物发出叫声");
}
}
public class Cat : Animal
{
public override void Voice()
{
Console.WriteLine("喵~");
}
}
public class Sheep : Animal
{
public override void Voice()
{
Console.WriteLine("咩~");
}
}
static void Main(string[] args)
{
var cat = new Cat();
cat.Voice();
var sheep = new Sheep();
sheep.Voice();
Console.ReadKey();
}
}
上面代码通过使用virtual
关键字,把需要在子类中表现为不同行为的方法定义为虚方法,然后在子类中使用override
关键字对基类方法进行重写。这样,每个基类在调用相同方法时将表现出不同的行为,这段代码正是 C# 中多态的实现
如果子类还行继续访问基类定义的方法,则可以使用base
关键字完成调用
抽象类
上面的代码存在一个问题:我们可以通过new
操作符创建 Animal 基类的实例,可 Animal 基类的作用是为所有子类提供公共成员,它是一个抽象的概念,在实际的系统中我们希望能避免创建该类的实例,怎么办?
public abstract class Animal
{
}
你会发现,如果你想实例化一个 Animal 会报错
阻止派生类重写虚成员
我们知道,用sealed
关键字可以防止一个类被其他类继承。同样,也可以使用sealed
关键字来阻止派生类重写虚成员
public class Horse : Animal
{
public sealed override void Voice()
{
base.Voice();
Console.WriteLine("嘶嘶");
}
}
public class Test : Horse
{
// 编译时错误
public override void Voice()
{
}
}
使用新成员隐藏基类成员
如果想在派生类中定义与基类成员同名的成员,则可以使用new
关键字来把基类成员隐藏起来,如果不使用new
关键字,编译器会产生警告信息
class Program
{
public class Animal
{
public Animal()
{
Voice();
}
public void Voice()
{
Console.WriteLine("动物发出叫声");
}
}
public class Cat : Animal
{
public Cat()
{
Voice();
}
public new void Voice()
{
Console.WriteLine("喵~");
}
}
static void Main(string[] args)
{
var cat = new Cat();
Console.ReadKey();
}
}
这里注意一下override
和new
的区别哦
class Program
{
public class Animal
{
public Animal()
{
Voice();
}
public virtual void Voice()
{
Console.WriteLine("动物发出叫声");
}
}
public class Cat : Animal
{
public Cat()
{
Voice();
}
public override void Voice()
{
Console.WriteLine("喵~");
}
}
static void Main(string[] args)
{
var cat = new Cat();
Console.ReadKey();
}
}
使用new
的打印结果是:
动物发出叫声
喵~
使用override
的打印结果是:
喵~
喵~
所有类的父类:System.Object
在 C# 中,所有类都派生自System.Object
类。如果定义的类没有指定任何基类,编译器就会自动把Object
类当作它的基类。和其他类一样,System.Object
类也定义了一组共有的成员,定义如下:
namespace System
{
//
// 摘要:
// Supports all classes in the .NET class hierarchy and provides low-level services
// to derived classes. This is the ultimate base class of all .NET classes; it is
// the root of the type hierarchy.
public class Object
{
//
// 摘要:
// Initializes a new instance of the System.Object class.
public Object();
//
// 摘要:
// Allows an object to try to free resources and perform other cleanup operations
// before it is reclaimed by garbage collection.
~Object();
//
// 摘要:
// Determines whether the specified object instances are considered equal.
//
// 参数:
// objA:
// The first object to compare.
//
// objB:
// The second object to compare.
//
// 返回结果:
// true if the objects are considered equal; otherwise, false. If both objA and
// objB are null, the method returns true.
public static bool Equals(Object? objA, Object? objB);
//
// 摘要:
// Determines whether the specified System.Object instances are the same instance.
//
// 参数:
// objA:
// The first object to compare.
//
// objB:
// The second object to compare.
//
// 返回结果:
// true if objA is the same instance as objB or if both are null; otherwise, false.
public static bool ReferenceEquals(Object? objA, Object? objB);
//
// 摘要:
// Determines whether the specified object is equal to the current object.
//
// 参数:
// obj:
// The object to compare with the current object.
//
// 返回结果:
// true if the specified object is equal to the current object; otherwise, false.
public virtual bool Equals(Object? obj);
//
// 摘要:
// Serves as the default hash function.
//
// 返回结果:
// A hash code for the current object.
public virtual int GetHashCode();
//
// 摘要:
// Gets the System.Type of the current instance.
//
// 返回结果:
// The exact runtime type of the current instance.
public Type GetType();
//
// 摘要:
// Returns a string that represents the current object.
//
// 返回结果:
// A string that represents the current object.
public virtual string? ToString();
//
// 摘要:
// Creates a shallow copy of the current System.Object.
//
// 返回结果:
// A shallow copy of the current System.Object.
protected Object MemberwiseClone();
}
}
归纳总结
我们学习了面向对象的三个特性——封装、继承和多态。也了解了所有类的父类System.Object
面向对象的内容是后期学习设计模式和企业系统开发的基础,当然,我们也应该去了解一下面向过程,了解两者的区别