最近在学习C#高级一点语法,为了以后能更好的复习,所以写了这篇笔记。跳过前面的,直接从属性开始学习
1. 属性:
可以被看作是类的一个特殊成员,它结合了字段(Field)和方法(Method)的特点。属性允许开发者以类似字段的方式访问类的状态,同时又可以在访问过程中在Get和Set中执行额外的逻辑
简单理解
想象你有一个房间(类),这个房间里有一个温度调节器(字段)。但是,你并不希望房间外的其他人直接操作这个温度调节器(直接访问字段),因为这可能会导致温度设置得不合理或者损坏温度调节器。相反,你希望他们通过一个界面(属性)来间接地调节温度。
房间(类):
public class Room
{
private int temperatureSetting; // 字段温度调节器(字段):
// ... 其他成员 ...
}
在房间内部,有一个实际用来调节温度的装置,但我们不直接暴露它给外界。
温度界面(属性):
这就是属性发挥作用的地方。你提供了一个界面,让其他人可以通过这个界面来获取或设置温度。
获取温度(get访问器):
当有人想知道房间的温度时,他们会通过温度界面来查看,而不是直接看温度调节器。
public int Temperature
{
get
{
// 这里可以加入一些逻辑,比如返回温度前进行单位转换等
return temperatureSetting;
}
}
设置温度(set访问器):
当有人想要改变房间的温度时,他们也会通过温度界面来操作。在设置温度前,你可以加入一些验证逻辑,确保温度不会设置得太高或太低。
set
{
// 这里可以加入验证逻辑,比如检查温度是否在合理范围内
if (value >= 0 && value <= 100) // 假设温度范围是0到100度
{
temperatureSetting = value;
}
else
{
// 抛出异常或者给出错误提示
throw new ArgumentOutOfRangeException(nameof(value), "Temperature must be between 0 and 100 degrees.");
}
}
整合起来,你的房间类就像这样:
public class Room
{
private int temperatureSetting; // 温度调节器(字段)
public int Temperature // 温度界面(属性)
{
get
{
return temperatureSetting;
}
set
{
if (value >= 0 && value <= 100)
{
temperatureSetting = value;
}
else
{
throw new ArgumentOutOfRangeException(nameof(value), "Temperature must be between 0 and 100 degrees.");
}
}
}
// ... 其他成员 ...
}
通过这个例子,可以看到属性是如何封装了字段,并提供了额外的控制和逻辑。这使得代码更加安全、易于理解和维护。
属性的用途
封装:属性允许你隐藏类的内部实现细节,只暴露必要的接口给类的使用者。
验证:在设置属性值之前,你可以在set访问器中加入验证逻辑,确保传递的值符合预期。
延迟加载:属性可以用于实现延迟加载,即在真正需要属性值时才去计算或获取该值。
通知变化:属性可以在值改变时触发通知,这在实现模型-视图-控制器(MVC)模式时非常有用。
总的来说,属性是C#中一个非常灵活和强大的特性,它提供了一种安全、高效的方式来管理和操作类的状态。
2. 三元运算符
这个很好理解,C# 中的三元运算符(也称为条件运算符)是一个简洁的条件表达式,它根据一个条件的结果返回两个值中的一个。这个运算符的格式如下:
result = condition ? value_if_true : value_if_false;*
`condition:一个返回布尔值(true 或 false)的表达式。
value_if_true:如果 condition 为 true,则返回的值。
value_if_false:如果 condition 为 false,则返回的值。
示例
假设我们有一个整数 x,我们想要检查它是否大于 0,并基于这个条件返回不同的字符串:
int x = 5;
string result = x > 0 ? "x is positive" : "x is not positive";
Console.WriteLine(result); // 输出 "x is positive"
在上面的示例中,因为 x 的值是 5(大于 0),所以三元运算符返回 “x is positive”。
3.静态
静态的概念是区别于非静态的,使用 static 关键字来修饰字段、属性、方法、类等。
Why
在编程中引入静态(static)的概念主要是出于以下几个原因和好处:
1.资源共享:
静态成员属于类本身而不是类的实例,这意味着无论创建多少个类的实例,静态成员都只有一份内存空间。这种特性使得静态成员非常适合用于存储那些不需要与特定实例关联的数据,例如配置信息、全局状态、常量等。通过共享资源,可以减少内存消耗和提高性能。
2.访问便利性:
静态成员可以通过类名直接访问,而不需要创建类的实例。这使得在不需要对象上下文的情况下,可以直接调用静态方法和访问静态字段,提高了代码的简洁性和可读性。
3.逻辑封装:
静态类通常用于封装与类型本身相关的逻辑,而不是与特定实例相关的功能。通过将这些逻辑组织在静态类中,可以使代码结构更清晰,更易于理解和维护。静态方法尤其适合用于执行工具性、辅助性或计算性的任务,这些任务不依赖于对象的状态。
4.安全性:
通过将某些成员声明为静态,可以限制对它们的访问。静态构造函数只能访问静态成员,这有助于确保在对象创建之前正确初始化静态状态。此外,静态类不能被继承,这可以防止意外地修改静态类的行为。
5.性能优化:
由于静态成员属于类本身,因此它们的访问速度通常比非静态成员更快。这是因为静态成员的引用不需要通过对象实例的引用链来解析,从而减少了内存访问的开销。在需要频繁访问某些数据或执行某些操作时,使用静态成员可以提高性能。
6.设计灵活性:
静态成员提供了一种在类和对象之间灵活分配职责的方式。通过将某些职责分配给静态成员,可以减少对象的依赖性和耦合性,使代码更易于测试和扩展。此外,静态成员还可以用于实现单例模式、工厂模式等常见的设计模式,以满足特定的设计需求。
总之,静态概念的引入使得编程更加灵活、高效和易于管理。通过合理使用静态成员和静态类,可以优化代码结构、提高性能和安全性,并增强代码的可维护性和可扩展性。
静态字段(Static Fields):
静态字段是类的成员,它属于类本身而不是类的任何实例。静态字段在内存中只有一个副本,无论创建多少个类的对象,静态字段都只有一份内存空间。
csharp
public static int StaticField;
静态属性(Static Properties):
静态属性也是类的成员,它提供了对静态字段的访问方式,可以添加额外的逻辑。
public static int StaticProperty
{
get { return StaticField; }
set { StaticField = value; }
}
静态方法(Static Methods):
静态方法属于类本身,可以通过类名直接调用,而不需要创建类的实例。静态方法只能访问静态字段和其他静态成员。
public static void StaticMethod()
{
// ...
}
静态构造函数(Static Constructors):
静态构造函数在类型首次被使用前自动执行,用于初始化静态字段或执行其他只需执行一次的操作。静态构造函数不能有访问修饰符(如 public private 等),也不能有参数。
static MyClass()
{
// 初始化静态字段或执行其他操作
}
静态类(Static Classes):
静态类仅包含静态成员,并且不能被实例化。静态类主要用于组织逻辑上属于同一类的静态方法和属性。
public static class StaticClass
{
public static void SomeMethod()
{
// ...
}
}
需要注意的是,static 关键字不能用于修饰局部变量(即在方法或块内部定义的变量),因为这些变量只存在于它们被声明的上下文中,并且它们的生命周期与包含它们的方法或块的执行周期相同。static 关键字只能用于修饰类的成员(字段、属性、方法等)或类本身
4.方法重载
方法重载(Overloading)
定义:
在C#中,方法重载允许你在同一个类中定义多个同名但参数列表不同的方法。编译器会根据提供的参数类型和数量来确定应该调用哪个方法。
形象介绍:
想象一下你有一个计算器,上面有一个“+”按钮。但是,这个计算器不仅仅能进行两个整数的加法,还能进行两个浮点数的加法,甚至还能进行三个整数的加法。虽然都是“+”操作,但是它们处理的数据类型和数量是不同的。
优点:
1.代码可读性:使用相同的方法名可以使代码更加清晰和易于理解。
灵活性:通过重载,可以为同一个操作提供多种实现方式,以满足不同的需求。
2.扩展性**:当需要为某个方法添加新的功能时,可以通过重载来扩展它,而不需要修改原有的代码。
缺点:
1.可能增加复杂性:如果过度使用重载,可能会导致代码变得复杂和难以维护。
2.命名冲突:虽然重载方法的名字相同,但它们的参数列表是不同的。然而,在某些情况下,可能会因为参数类型的相似性而导致命名冲突或混淆。
代码示例:
using System;
class Calculator
{
// 整数加法
public int Add(int a, int b)
{
return a + b;
}
// 浮点数加法
public double Add(double a, double b)
{
return a + b;
}
// 三个整数的加法
public int Add(int a, int b, int c)
{
return a + b + c;
}
// 示例方法,展示如何调用重载的方法
public void ShowAddition()
{
int intResult = Add(1, 2); // 调用整数加法
double doubleResult = Add(1.5, 2.5); // 调用浮点数加法
int threeIntResult = Add(1, 2, 3); // 调用三个整数的加法
Console.WriteLine("整数加法结果: " + intResult);
Console.WriteLine("浮点数加法结果: " + doubleResult);
Console.WriteLine("三个整数加法结果: " + threeIntResult);
}
}
class Program
{
static void Main()
{
Calculator calc = new Calculator();
calc.ShowAddition(); // 调用示例方法,展示重载方法的调用
}
}
5.泛型
(1)介绍
泛型是C# 2.0及更高版本中引入的一个特性,允许你定义灵活、可重用且类型安全的代码。泛型的主要目的是在编译时引入类型参数,使得你可以编写与类型无关的代码,然后在运行时将类型参数替换为实际的类型。
可用在哪里:
集合类:如List、Dictionary<TKey, TValue>等。这些集合类能够存储任何类型的对象,只要这些对象在创建集合时指定了类型参数。
算法:你可以使用泛型来编写与类型无关的算法,如排序、搜索等。
自定义类:你可以在自己的类中使用泛型来提供类型安全的代码重用。
优点:
类型安全:由于泛型在编译时就知道类型参数,因此可以在编译时捕获类型不匹配的错误,而不是在运行时。
代码重用:泛型允许你编写一次代码,然后将其用于多种数据类型,从而减少了代码的重复。
性能:泛型通常比使用非泛型集合(如ArrayList)或进行装箱/拆箱操作(如将值类型存储在Object数组中)更高效。
更好的可读性:使用泛型可以使代码更易于理解,因为你可以明确看到集合或方法所操作的数据类型。
缺点:
复杂性:对于初学者来说,泛型可能有些难以理解,因为它们引入了额外的概念(如类型参数、类型约束等)。
与旧代码的集成:如果你的项目需要与不支持泛型的旧代码集成,那么可能需要额外的转换或包装代码。
形象理解:
想象你有一个工具箱,里面有各种工具(如螺丝刀、扳手等)。每个工具都适用于特定的任务,但如果你有一个“泛型”工具(如可调节的扳手),那么它就可以适应多种不同的螺栓和螺母。泛型在编程中的作用与此类似:它允许你编写能够处理多种数据类型的代码,而不仅仅是硬编码为处理特定类型的代码。这样,你就可以重用代码,提高性能,并减少错误。
!泛型约束
第一步,我要明白约束,是对谁约束,是对占位符T 的约束
第二步,可以对T怎么约束
***(1)基类约束:约束类型参数必须是某个特定类的派生类。
public class BaseClass { }
public class GenericClass<T> where T : BaseClass
{
// T 必须是 BaseClass 或其派生类
}
在这个例子中,泛型约束where T : BaseClass是对泛型类型参数T的约束,它要求T必须是BaseClass或其派生类。
(2)接口约束:约束类型参数必须实现某个接口。
public interface IInterface { }
public class GenericClass<T> where T : IInterface
{
// T 必须实现 IInterface 接口
}
在这个例子中,泛型约束where T : IInterface是对泛型类型参数T的约束,它要求T必须实现IInterface接口。
(3)构造函数约束:约束类型参数必须具有无参数的公共构造函数。
public class GenericClass<T> where T : new()
{
public T CreateInstance()
{
return new T(); // 这只有在 T 有一个无参数的公共构造函数时才有效
}
}
在这个例子中,泛型约束where T : new()是对泛型类型参数T的约束,它要求T必须具有无参数的公共构造函数。
(5)引用类型约束和值类型约束:分别约束类型参数必须是引用类型或值类型。
`public class GenericClass<T> where T : class
// T 必须是引用类型
{
// ...
}
public class GenericClass<T> where T : struct
// T 必须是值类型
{
// ...
}
这些约束分别是对泛型类型参数T的约束,确保T是引用类型或值类型。
通过这些约束,你可以编写更加健壮和类型安全的泛型代码,因为它们只允许满足特定条件的类型作为类型参数
6. 继承
在C#中,继承是一种面向对象编程的核心概念,它允许我们创建一个新的类(称为派生类或子类),该类继承自一个已存在的类(称为基类或父类)。通过继承,派生类可以获取基类的所有非私有成员(字段、属性、方法和事件),并且可以添加新的成员或覆盖已存在的成员。
下面是C#中继承的三个主要方面的概述:
继承的实现
在C#中,使用冒号(:)和基类名称来指定一个类继承自另一个类。例如:
public class Animal
{
public void Speak()
{
Console.WriteLine("The animal speaks.");
}
}
public class Dog : Animal // Dog类继承自Animal类
{
public void Bark()
{
Console.WriteLine("The dog barks.");
}
}
在这个例子中,Dog类继承了Animal类,因此Dog类可以访问Animal类的Speak方法。
访问修饰符和继承
基类的成员可以通过不同的访问修饰符(如public、protected、internal、protected internal、private)来控制其在派生类中的可见性。
public:成员在派生类中可见。
protected:成员在基类和派生类中可见,但在基类的实例外部不可见。
internal:成员在同一程序集中的任何类中都可见,但在其他程序集中不可见(与继承无关)。
protected internal:成员在同一程序集中的任何类中都可见,并且在派生类中可见,但在基类的实例外部和其他程序集中不可见。
private:成员在基类中可见,但在派生类中不可见。
方法重写(Overriding)
在派生类中,我们可以使用override关键字来重写基类中的虚方法(使用virtual关键字标记的方法)。当派生类对象调用该方法时,将执行派生类中的重写版本,而不是基类中的原始版本。
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("The animal speaks.");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("The dog barks.");
}
}
// 使用示例
Animal animal = new Dog();
animal.Speak(); // 输出 "The dog barks."
注意,只有标记为virtual、abstract或override的方法才能在派生类中被重写。如果基类中的方法没有使用这些修饰符,那么在派生类中尝试使用override关键字将会导致编译错误。
此外,C#还提供了new关键字来隐藏基类中的同名成员(而不是重写它们)。但是,这通常不是首选做法,因为它可能导致代码难以理解和维护。在大多数情况下,如果基类和派生类具有相同名称的成员,并且它们的意图不同,那么最好使用不同的名称来避免混淆。
! 注意虚方法和抽象方法区别
虚方法(virtual)和抽象方法(abstract)在C#中都是面向对象编程的重要概念,但它们之间有一些关键的区别。以下是这些区别:
实现方式:
虚方法(virtual)在基类中可以有具体的实现。子类可以选择重写(override)这个方法,也可以不重写,直接使用基类的实现。
抽象方法(abstract)在基类中没有任何实现。它只是一个方法的签名,没有方法体。子类必须提供这个方法的实现,否则子类也必须被声明为抽象的。
类定义:
含有虚方法的类可以是具体的类(即非抽象类),也可以是抽象类。
含有抽象方法的类必须被声明为抽象类。
调用方式:
虚方法可以直接通过基类对象进行调用(如果基类对象引用的是非重写该方法的子类实例,则调用的是基类中的实现;如果引用的是重写了该方法的子类实例,则调用的是子类中的实现),也可以通过子类对象进行调用(调用的是子类中的实现,如果子类没有重写该方法,则调用基类中的实现)。
抽象方法不能通过抽象类的实例直接调用,因为抽象类不能被实例化。它只能在非抽象子类中通过重写后被调用。
目的和用途:
虚方法通常用于实现某种行为的多态性,即不同的子类可以有不同的实现,但对外暴露的接口是一致的。
抽象方法主要用于定义接口,规定子类必须实现的方法。抽象方法通常用于定义一些通用的、但无法给出具体实现的行为。
示例:
虚方法示例:
public class Animal
{
public virtual void Speak()
{
Console.WriteLine("The animal speaks.");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("The dog barks.");
}
}
抽象方法示例:
public abstract class Shape
{
public abstract double CalculateArea();
// 抽象方法,没有实现
}
public class Rectangle : Shape
{
private double width, height;
public Rectangle(double w, double h)
{
width = w;
height = h;
}
public override double CalculateArea()
// 重写抽象方法,提供实现
{
return width * height;
}
}
总结来说,虚方法和抽象方法的主要区别在于它们的实现方式、类定义、调用方式以及目的和用途。
7.多态
多态是面向对象编程的三大特征之一,在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。可以把一组对象放到一个数组中,然后调用它们的方法,在这种场合下,多态性作用就体现出来了,这些对象不必是相同类型的对象。当然,如果它们都继承自某个类,你可以把这些派生类,都放到一个数组中。如果这些对象都有同名方法,就可以调用每个对象的同名方法。
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类重载基类中的虚函数型方法来实现。
在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作、如何实现由系统负责解释。
“多态性”一词最早用于生物学,指同一种族的生物体具有相同的特性。在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。
//以下是一个更具体的例子来解释多态:
public abstract class Animal
{
public abstract void MakeSound(); // 抽象方法,由子类实现
}
public class Dog : Animal
{
public override void MakeSound() // 重写父类的MakeSound方法
{
Console.WriteLine("The dog barks.");
}
}
public class Cat : Animal
{
public override void MakeSound() // 重写父类的MakeSound方法
{
Console.WriteLine("The cat meows.");
}
}
public class Zoo
{
public void AnimalSound(Animal animal) // 接受Animal类型的参数
{
animal.MakeSound();
// 调用MakeSound方法,运行时根据animal的
//实际类型确定执行哪个版本的MakeSound
}
}
class Program
{
static void Main(string[] args)
{
Zoo zoo = new Zoo();
Dog dog = new Dog();
Cat cat = new Cat();
zoo.AnimalSound(dog); // 输出:The dog barks.
zoo.AnimalSound(cat); // 输出:The cat meows.
}
}
在这个例子中,Zoo 类的 AnimalSound 方法接受一个 Animal 类型的参数。当我们传递一个 Dog 对象或 Cat 对象给这个方法时,多态允许我们调用各自子类实现的 MakeSound 方法,而不是父类 Animal 的(不存在的)MakeSound 方法。这就是多态性的体现:同样的代码(animal.MakeSound();)可以根据 animal 的实际类型(Dog 或 Cat)产生不同的行为。
8.成员隐藏
在C#中,成员隐藏(Member Hiding)或称为“新成员”(New Member)是一种特性,它允许你在派生类中定义一个与基类同名的成员,从而隐藏基类中的同名成员。但是,这种隐藏并不是真正的“覆盖”(Override),因为隐藏不会改变基类成员的行为,只是使得在派生类的作用域内,通过派生类实例访问该成员时,会访问到派生类定义的成员。
为了隐藏基类成员,你需要在派生类成员前使用new关键字进行显式标记。如果不使用new关键字,并且派生类中的成员与基类中的成员同名,编译器会发出警告,因为这种情况可能会导致混淆。
下面是一个简单的示例来说明成员隐藏:
class BaseClass
{
public void DoSomething()
{
Console.WriteLine("BaseClass.DoSomething() called.");
}
}
class DerivedClass : BaseClass
{
// 使用 new 关键字来隐藏基类中的 DoSomething 方法
new public void DoSomething()
{
Console.WriteLine("DerivedClass.DoSomething() called.");
}
}
class Program
{
static void Main()
{
DerivedClass derived = new DerivedClass();
// 调用派生类中的 DoSomething 方法
derived.DoSomething(); // 输出: DerivedClass.DoSomething() called.
// 如果要调用基类中被隐藏的 DoSomething 方法,需要通过强制类型转换或 base 关键字
((BaseClass)derived).DoSomething(); // 输出: BaseClass.DoSomething() called.
// 或者
// base.DoSomething(); // 但这需要在派生类的方法内部使用
}
}
在上面的示例中,DerivedClass 隐藏了 BaseClass 中的 DoSomething 方法。当你通过 DerivedClass 的实例调用 DoSomething 方法时,会调用 DerivedClass 中的方法。但是,如果你将 DerivedClass 的实例强制转换为 BaseClass 类型,或者在 DerivedClass 的方法内部使用 base 关键字,则可以访问基类中被隐藏的方法。
需要注意的是,成员隐藏和成员覆盖(Override)是不同的概念。成员覆盖通常用于实现多态性,其中派生类提供基类虚方法或抽象方法的具体实现。在成员覆盖中,派生类使用 override 关键字来指示它正在提供基类成员的替代实现。而成员隐藏则不会改变基类成员的行为,只是简单地隐藏了它。
9.覆盖
在C#中,覆盖(Override)是一个重要的面向对象编程概念,它允许派生类(或子类)提供一个与基类(或父类)中虚(virtual)成员或抽象(abstract)成员具有相同签名的新实现。通过覆盖,派生类可以扩展或修改基类的行为。
要覆盖一个基类成员,你需要满足以下条件:
基类成员必须是虚方法(virtual)、抽象方法(abstract)或接口中的方法。
派生类成员必须使用override关键字进行标记。
派生类成员的签名(包括方法名、参数列表和返回类型)必须与基类中被覆盖的成员完全相同。
下面是一个简单的示例,展示了如何在C#中使用覆盖:
csharp
class BaseClass
{
// 虚方法
public virtual void DoSomething()
{
Console.WriteLine("BaseClass.DoSomething() called.");
}
}
class DerivedClass : BaseClass
{
// 覆盖基类中的虚方法
public override void DoSomething()
{
base.DoSomething(); //如果同时也想使用父类中方法
Console.WriteLine("DerivedClass.DoSomething() called.");
}
}
class Program
{
static void Main()
{
BaseClass baseObj = new BaseClass();
DerivedClass derivedObj = new DerivedClass();
// 调用基类方法
baseObj.DoSomething(); // 输出: BaseClass.DoSomething() called.
// 调用派生类方法(虽然变量类型是基类,但引用的是派生类实例)
baseObj = derivedObj; // 这里的baseObj实际上引用的是DerivedClass对象
baseObj.DoSomething(); // 输出: DerivedClass.DoSomething() called.(多态)
// 直接调用派生类方法
derivedObj.DoSomething(); // 输出: DerivedClass.DoSomething() called.
}
}
在上面的示例中,BaseClass定义了一个虚方法DoSomething,而DerivedClass通过override关键字覆盖了该方法。当通过派生类实例调用DoSomething方法时,会调用派生类中的实现。此外,即使将派生类实例赋值给基类类型的变量(如baseObj = derivedObj;),由于多态性,调用DoSomething方法时仍然会执行派生类中的实现。
需要注意的是,如果尝试在没有使用override关键字的情况下在派生类中定义一个与基类方法同名的方法,编译器会发出警告(如果基类方法不是虚方法或抽象方法)或错误(如果基类方法是虚方法或抽象方法)。这是为了确保意图明确,并防止意外的行为。
10.面向对象三大特性简单理解
继承 (Inheritance)
形象理解:想象你有一个家族,你是这个家族中的一员。你继承了你的父母的一些特征,比如眼睛的颜色、头发的颜色等。同时,你也可能继承了你父母的某些技能或习惯,比如你父亲的烹饪技巧或你母亲的园艺技能。
编程解释:在面向对象编程中,一个类(子类)可以继承另一个类(父类)的属性和方法。子类会获得父类的所有公共和保护属性和方法,并且还可以添加或修改它们。
多态 (Polymorphism)
形象理解:在你的家族中,你和你的兄弟姐妹可能都继承了父母的某些技能,但每个人对这些技能的掌握程度和表现方式可能都不同。比如,你和你的哥哥都继承了父亲的烹饪技巧,但你可能更擅长做甜点,而你的哥哥更擅长做烤肉。
编程解释:多态是指在面向对象编程中,不同的对象对同一消息做出不同的响应。这通常通过方法的重写(override)和重载(overload)来实现。子类可以重写父类的方法,以提供自己的实现。在运行时,根据对象的实际类型来调用相应的方法,从而实现多态。
封装 (Encapsulation)
形象理解:你的家里有一个电视遥控器,你可以通过按遥控器上的按钮来控制电视。但你不需要知道遥控器内部是如何工作的,你只需要知道每个按钮的功能即可。这就是封装的一个例子,它把复杂的内部实现隐藏起来,只暴露出简单的接口供外部使用。
编程解释:在面向对象编程中,封装是把对象的属性和方法结合在一个单一的单元中,并通过一个访问接口来与外界交互。封装隐藏了对象的内部实现细节,只允许通过公共的接口来访问和操作对象。这样可以保护对象的数据不被外部随意修改,同时也可以简化对象的使用。
总结起来,继承、多态和封装是面向对象编程的三大特性,它们共同构成了面向对象编程的基础。通过这三个特性,我们可以创建出更加灵活、可维护和可扩展的代码。
11 .异常捕获
在C#中,异常处理是通过try-catch-finally语句块来实现的。这些语句块允许你执行可能引发异常的代码,并捕获这些异常以进行适当的处理。以下是异常处理的基本语法:
try
{
// 尝试执行可能会引发异常的代码
// 如果发生异常,控制流将跳转到相应的catch块
}
catch (SpecificExceptionType ex)
{
// 处理SpecificExceptionType类型的异常
// 你可以通过ex变量访问异常的详细信息
}
catch (AnotherExceptionType ex)
{
// 处理AnotherExceptionType类型的异常
// 你可以添加多个catch块来处理不同类型的异常
}
// ... 可以添加更多的catch块
finally
{
// 无论是否发生异常,finally块中的代码都会执行
// 通常用于清理资源,如关闭文件或数据库连接
}
在上面的代码中,try块包含可能会引发异常的代码。如果try块中的代码引发了一个异常,控制流将立即离开try块,并查找与异常类型匹配的catch块。一旦找到匹配的catch块,控制流将进入该块,并执行其中的代码来处理异常。
如果try块中的代码没有引发异常,或者异常没有被任何catch块捕获,那么控制流将跳过所有catch块(如果有的话),并直接进入finally块(如果有的话)。finally块是可选的,但无论是否发生异常,它都会执行。
注意:如果在catch块中再次引发异常,并且没有外部的catch块来捕获它,那么程序将终止并显示未处理的异常信息。为了避免这种情况,你可以在catch块中使用throw;(不带参数)来重新引发异常,这样它就可以被外部的catch块捕获。
下面是一个简单的示例,演示了如何在C#中使用异常处理:
try
{
int result = int.Parse("这不是一个数字"); // 这将引发FormatException异常
Console.WriteLine(result);
}
catch (FormatException ex)
{
Console.WriteLine("发生了格式异常:" + ex.Message);
}
catch (Exception ex) // 捕获所有其他类型的异常
{
Console.WriteLine("发生了未知异常:" + ex.Message);
}
finally
{
Console.WriteLine("无论是否发生异常,这都会执行。");
}