在本文中,我们介绍了C#中的面向对象编程。
有三种广泛使用的编程范式:过程式编程、函数式编程和面向对象编程。C#支持面向过程和面向对象的编程。
面向对象定义
面向对象编程(OOP)是一种使用对象及其交互来设计应用程序和计算机程序的编程范例。
OOP中有一些基本的编程概念:
- 抽象
- 多态
- 封装
- 继承
抽象通过建模适合问题的类来简化复杂的现实。多态性是针对不同的数据输入以不同的方式使用运算符或函数的过程。封装向其他对象隐藏类的实现细节。继承是一种使用已经定义的类来形成新类的方法。
C#对象
对象是C#OOP程序的基本构建块。对象是数据和方法的组合。数据和方法称为对象的成员。在OOP程序中,我们创建对象。这些对象通过方法相互通信。每个对象都可以接收消息、发送消息和处理数据。
创建一个对象有两个步骤。首先,我们定义一个类。类是对象的模板。它是一个蓝图,描述了类的所有对象共享的状态和行为。一个类可以用来创建许多对象。在运行时从类创建的对象称为该特定类的实例。
var b = new Being(); Console.WriteLine(b); class Being {}
在我们的第一个示例中,我们创建了一个简单的对象。
class Being {}
这是一个简单的类定义。模板的主体是空的。它没有任何数据或方法。
var b = new Being();
我们创建了一个Being
类的新实例。为此,我们有new
关键字。b
变量是创建对象的句柄。
Console.WriteLine(b);
我们将对象打印到控制台以获得对象的一些基本描述。打印对象是什么意思?当我们打印一个对象时,我们实际上调用了ToString
方法。但是我们还没有定义任何方法。这是因为创建的每个对象都继承自基础object
。它具有一些在所有创建的对象之间共享的基本功能。其中之一是ToString
方法。
$ dotnet run Being
C#对象属性
对象属性是捆绑在类实例中的数据。对象属性称为实例变量或成员字段。实例变量是在类中定义的变量,类中的每个对象都有一个单独的副本。
var p1 = new Person(); p1.name = "Jane"; var p2 = new Person(); p2.name = "Beky"; Console.WriteLine(p1.name); Console.WriteLine(p2.name); class Person { public string name; }
在上面的C#代码中,我们有一个带有一个成员字段的Person
类。
class Person { public string name; }
我们声明一个名称成员字段。public
关键字指定成员字段可以在类块之外访问。
var p1 = new Person(); p1.name = "Jane";
我们创建了一个Person
类的实例,并将名称变量设置为“Jane”。我们使用点运算符来访问对象的属性。
var p2 = new Person(); p2.name = "Beky";
我们创建了Person
类的另一个实例。这里我们将变量设置为“Beky”。
Console.WriteLine(p1.name); Console.WriteLine(p2.name);
我们将变量的内容打印到控制台。
$ dotnet run Jane Beky
Person
类的每个实例都有一个单独的namemember字段副本。
C#方法
方法是在类体内定义的函数。它们用于对我们的对象的属性执行操作。方法为我们的程序带来模块化。
方法在OOP范例的封装概念中是必不可少的。例如,我们的AccessDatabase
类中可能有一个Connect
方法。我们不需要知道Connect
方法究竟是如何连接到数据库的。我们只需要知道它是用来连接数据库的。这对于划分编程中的职责至关重要,尤其是在大型应用程序中。
对象集合状态和行为,方法代表对象的行为部分。
var c = new Circle(); c.SetRadius(5); Console.WriteLine(c.Area()); class Circle { private int radius; public void SetRadius(int radius) { this.radius = radius; } public double Area() { return this.radius * this.radius * Math.PI; } }
在代码示例中,我们有一个Circle类。我们定义了两种方法。
private int radius;
我们有一个成员字段。它是圆的半径。private
关键字是访问说明符。它表明该变量仅限于外部世界。如果我们想从外部修改这个变量,我们必须使用公开可用的SetRadius
方法。这样我们可以保护我们的数据。
public void SetRadius(int radius) { this.radius = radius; }
这是SetRadius
方法。this
变量是一个特殊变量,我们用它来访问方法中的成员字段。this.radius
是一个实例变量,而radius是一个局部变量,只在SetRadius
方法内部有效。
var c = new Circle(); c.SetRadius(5);
我们创建了一个Circle
类的实例,并通过调用圆对象上的SetRadius
方法来设置它的半径。我们使用点运算符来调用该方法。
public double Area() { return this.radius * this.radius * Math.PI; }
Area
方法返回圆的面积。Math.PI
是一个内置常量。
$ dotnet run 78.5398163397448
C#构造函数
构造函数是一种特殊的方法。创建对象时会自动调用它。构造函数不返回值。构造函数的目的是初始化一个对象的状态。构造函数与类同名。构造函数是方法,因此它们也可以被重载。
构造函数不能被继承。它们按照继承的顺序被调用。如果我们不为类编写任何构造函数,C#提供了一个隐式的默认构造函数。如果我们提供任何类型的构造函数,则不提供默认值。
new Being(); new Being("Tom"); class Being { public Being() { Console.WriteLine("Being is created"); } public Being(string being) { Console.WriteLine($"Being {being} is created"); } }
我们有一个Being
类。这个类有两个构造函数。第一个不带参数;第二个接受一个参数。
public Being(string being) { Console.WriteLine($"Being {being} is created"); }
这个构造函数接受一个字符串参数。
new Being();
Being
类的一个实例被创建。这次在创建对象时调用不带参数的构造函数。
$ dotnet run Being is created Being Tom is created
在下一个例子中,我们初始化类的数据成员。初始化变量是构造函数的典型工作。
var name = "Lenka"; var born = new DateTime(1990, 3, 5); var friend = new MyFriend(name, born); friend.Info(); class MyFriend { private DateTime born; private string name; public MyFriend(string name, DateTime born) { this.name = name; this.born = born; } public void Info() { Console.WriteLine("{0} was born on {1}", this.name, this.born.ToShortDateString()); } }
我们有一个包含数据成员和方法的MyFriend
类。
private DateTime born; private string name;
我们在类定义中有两个私有变量。
public MyFriend(string name, DateTime born) { this.name = name; this.born = born; }
在构造函数中,我们初始化两个数据成员。this
变量是用于引用对象变量的处理程序。
var friend = new MyFriend(name, born); friend.Info();
我们创建一个带有两个参数的MyFriend
对象。然后我们调用对象的Info
方法。
$ dotnet run Lenka was born on 3/5/1990
C#构造函数链
构造函数链接是类从构造函数调用另一个构造函数的能力。要从同一个类调用另一个构造函数,我们使用this
关键字。
new Circle(5); new Circle(); class Circle { public Circle(int radius) { Console.WriteLine($"Circle, r={radius} is created"); } public Circle() : this(1) { } }
我们有一个Circle
类。该类有两个构造函数。一种接受一个参数,一种不接受任何参数。
public Circle(int radius) { Console.WriteLine("Circle, r={0} is created", radius); }
此构造函数采用一个参数——radius
。
public Circle() : this(1) { }
这是没有参数的构造函数。它只是调用另一个构造函数并为其指定默认半径1。
$ dotnet run Circle, r=5 is created Circle, r=1 is created
C#ToString方法
每个对象都有一个ToString
方法。它返回一个对象的人类可读表示。默认实现返回Object
类型的完全限定名称。请注意,当我们使用对象作为参数调用Console.WriteLine
方法时,将调用ToString
。
var b = new Being(); var o = new Object(); Console.WriteLine(o.ToString()); Console.WriteLine(b.ToString()); Console.WriteLine(b); class Being { public override string ToString() { return "This is Being class"; } }
我们有一个Being
类,我们在其中覆盖了ToString
方法的默认实现。
public override string ToString() { return "This is Being class"; }
创建的每个类都继承自基对象
。ToString
方法属于这个对象类。我们使用override
关键字来通知我们正在覆盖一个方法。
var b = new Being(); var o = new Object();
我们创建了一个自定义对象和一个内置对象。
Console.WriteLine(o.ToString()); Console.WriteLine(b.ToString());
我们对这两个对象调用ToString
方法。
Console.WriteLine(b);
正如我们之前指定的,将对象作为参数传递给Console.WriteLine
将调用其ToString
方法。这次,我们隐式调用了该方法。
$ dotnet run System.Object This is Being class This is Being class
C#对象初始化器
对象初始值设定项让我们可以在创建时为对象的任何可访问字段或属性赋值,而无需调用构造函数。属性或字段在{}
括号。此外,我们可以为构造函数指定参数或省略参数。
var u = new User { Name = "John Doe", Occupation = "gardener" }; Console.WriteLine(u); class User { public User() {} public string Name { set; get; } public string Occupation { set; get; } public override string ToString() { return $"{Name} is a {Occupation}"; } }
在示例中,我们使用对象初始化器语法创建一个新用户。
public User() {}
我们定义了一个空的构造函数。
public string Name { set; get; } public string Occupation { set; get; }
我们有两个属性:Name
和Occupation
。
var u = new User { Name = "John Doe", Occupation = "gardener" };
我们将值分配给{}
括号中的属性。
$ dotnet run John Doe is a gardener
C#expression-bodiedconstructor
可以创建表达式主体的构造函数;它们提供了更简洁、更美观的语法。
var u1 = new User("John Doe", "gardener"); var u2 = new User("Roger Roe", "driver"); Console.WriteLine(u1); Console.WriteLine(u2); class User { private string Name; private string Occupation; public User(string Name, string Occupation) => (this.Name, this.Occupation) = (Name, Occupation); public override string ToString() => $"User {{ {this.Name} {this.Occupation} }}"; }
我们有一个带有两个参数的构造函数;它们被设置在表达式主体中。
public User(string Name, string Occupation) => (this.Name, this.Occupation) = (Name, Occupation);
在这种情况下,this
关键字是必需的。
C#目标类型的新表达式
当类型已知时,目标类型的new表达式不需要为构造函数指定类型。此功能是在C#9.0中引入的。
var u1 = new User("Roger", "Roe", "driver"); Console.WriteLine(u1); User u2 = new("John", "Doe", "gardener"); Console.WriteLine(u2); var users = new List<User> { new("Thomas", "Roove", "programmer"), new("Lucia", "Smith", "hair dresser"), new("Peter", "Holcomb", "painter"), new("Orlando", "Black", "actor"), new("Patrick", "Allen", "police officer") }; foreach (var user in users) { Console.WriteLine(user); } class User { public string FirstName { get; set; } public string LastName { get; set; } public string Occupation { get; set; } public User(string FirstName, string LastName, string Occupation) => (this.FirstName, this.LastName, this.Occupation) = (FirstName, LastName, Occupation); public override string ToString() => $"User {{ {this.FirstName} {this.LastName} {this.Occupation} }}"; }
我们演示了用户类型的目标类型的新表达式。
var u1 = new User("Roger", "Roe", "driver");
var
关键字可用于省略赋值左侧的类型声明,因为编译器可以从右侧推断类型。
User u2 = new("John", "Doe", "gardener");
目标类型的new表达式允许我们省略赋值右侧的类型声明。
var users = new List<User> { new("Thomas", "Roove", "programmer"), new("Lucia", "Smith", "hair dresser"), new("Peter", "Holcomb", "painter"), new("Orlando", "Black", "actor"), new("Patrick", "Allen", "police officer") };
在列表初始化器中,我们通过省略每个用户的类型来节省一些击键。
C#类常量
C#允许创建类常量。这些常量不属于具体对象。他们属于阶级。按照惯例,常量用大写字母书写。
Console.WriteLine(Math.PI); class Math { public const double PI = 3.14159265359; }
我们有一个带有PI
常量的Math
类。
public const double PI = 3.14159265359;
const
关键字用于定义常量。public
关键字使其可以在类的主体之外访问。
$ dotnet run 3.14159265359
C#继承
继承是一种使用已经定义的类来形成新类的方法。新形成的类称为派生类,我们从中派生的类称为基类。继承的重要好处是代码重用和降低程序的复杂性。派生类(后代)覆盖或扩展基类(祖先)的功能。
new Human(); class Being { public Being() { Console.WriteLine("Being is created"); } } class Human : Being { public Human() { Console.WriteLine("Human is created"); } }
在这个程序中,我们有两个类。基Being
类和派生的Human
类。派生类继承自基类。
new Human();
我们实例化派生的Human
类。
class Human : Being
在C#中,我们使用冒号(:)运算符来创建继承关系。
$ dotnet run Being is created Human is created
我们可以看到两个构造函数都被调用了。首先调用基类的构造函数,然后调用派生类的构造函数。
下面是一个更复杂的例子。
new Human(); var dog = new Dog(); dog.GetCount(); class Being { static int count = 0; public Being() { count++; Console.WriteLine("Being is created"); } public void GetCount() { Console.WriteLine("There are {0} Beings", count); } } class Human : Being { public Human() { Console.WriteLine("Human is created"); } } class Animal : Being { public Animal() { Console.WriteLine("Animal is created"); } } class Dog : Animal { public Dog() { Console.WriteLine("Dog is created"); } }
我们有四个班级。继承层次比较复杂。Human
和Animal
类继承自Being
类。Dog类直接继承自Animal
类,间接继承自Being
类。我们还引入了static
变量的概念。
new Human(); var dog = new Dog(); dog.GetCount();
我们从Human
和Dog
类创建实例。我们调用Dog对象的GetCount
方法。
static int count = 0;
我们定义了一个static
变量。静态成员是由类的所有实例共享的成员。
Being() { count++; Console.WriteLine("Being is created"); }
每次实例化Being
类时,我们都会将计数变量加一。通过这种方式,我们可以跟踪创建的实例数。
class Animal : Being ... class Dog : Animal ...
Animal
继承自Being
,Dog
继承自Animal
。Dog
也间接继承自Being
。
$ dotnet run Being is created Human is created Being is created Animal is created Dog is created There are 2 Beings
Human
调用了两个构造函数。Dog
调用三个构造函数。实例化了两个存在。
我们使用base
关键字来显式调用父级的构造函数。
var c = new Circle(2, 5, 6); Console.WriteLine(c); class Shape { protected int x; protected int y; public Shape() { Console.WriteLine("Shape is created"); } public Shape(int x, int y) { this.x = x; this.y = y; } } class Circle : Shape { private int r; public Circle(int r, int x, int y) : base(x, y) { this.r = r; } public override string ToString() { return String.Format("Circle, r:{0}, x:{1}, y:{2}", r, x, y); } }
我们有两个类:Shape
类和Circle
类。Shape
类是几何形状的基类。我们可以将常见形状的一些共性放入此类,例如x
和y
坐标.
public Shape() { Console.WriteLine("Shape is created"); } public Shape(int x, int y) { this.x = x; this.y = y; }
Shape
类有两个构造函数。第一个是默认构造函数。第二个有两个参数:x、y坐标。
public Circle(int r, int x, int y) : base(x, y) { this.r = r; }
这是Circle
类的构造函数。此构造函数启动r
成员并调用父级的第二个构造函数,并将x
、y
坐标传递给该构造函数。如果我们没有使用base
关键字显式调用构造函数,则会调用Shape
类的默认构造函数。
$ dotnet run Circle, r:2, x:5, y:6
C#多态
多态性是对不同的数据输入以不同的方式使用运算符或函数的过程。实际上,多态意味着如果B类继承自A类,则它不必继承A类的所有内容;它可以做一些A类做的不同的事情。
一般来说,多态性是以不同形式出现的能力。从技术上讲,它是为派生类重新定义方法的能力。多态性涉及将特定实现应用于接口或更通用的基类。
多态性是为派生类重新定义方法的能力。
namespace Polymorphism; abstract class Shape { protected int x; protected int y; public abstract int Area(); } class Rectangle : Shape { public Rectangle(int x, int y) { this.x = x; this.y = y; } public override int Area() { return this.x * this.y; } } class Square : Shape { public Square(int x) { this.x = x; } public override int Area() { return this.x * this.x; } } class Program { static void Main(string[] args) { Shape[] shapes = { new Square(5), new Rectangle(9, 4), new Square(12) }; foreach (Shape shape in shapes) { Console.WriteLine(shape.Area()); } } }
在上面的程序中,我们有一个抽象的Shape
类。这个类变形为两个后代类:Rectangle
和Square
。两者都提供了它们自己的Area
方法实现。多态性为OOP系统带来了灵活性和可扩展性。
public override int Area() { return this.x * this.y; } ... public override int Area() { return this.x * this.x; }
Rectangle
和Square
类有它们自己的Area
方法实现。
Shape[] shapes = { new Square(5), new Rectangle(9, 4), new Square(12) };
我们创建一个包含三个形状的数组。
foreach (Shape shape in shapes) { Console.WriteLine(shape.Area()); }
我们遍历每个形状并在其上调用Area
方法。编译器为每个形状调用正确的方法。这就是多态的本质。
C#部分类
使用partial
关键字,可以将一个类的定义拆分成同一个命名空间内的几个部分。该类也可以定义在多个文件中。
部分类在处理非常大的代码库时使用,它可以被拆分成更小的单元。部分类也与自动代码生成器一起使用。
namespace PartialClass; partial class Worker { public string DoWork() { return "Doing work"; } } partial class Worker { public string DoPause() { return "Pausing"; } } class Program { static void Main(string[] args) { var worker = new Worker(); Console.WriteLine(worker.DoWork()); Console.WriteLine(worker.DoWork()); Console.WriteLine(worker.DoPause()); } }
在示例中,我们将Worker
类定义为两部分。编译器将这些部分连接在一起形成最终类。
$ dotnet run Doing work Doing work Pausing
在本文中,我们介绍了C#中的OOP。
列出所有C#教程。