c#编程
类与方法
在 C# 中,与其他面向对象编程语言类似,程序的功能通过一个或多个类实现。类的方法和属性包含定义类行为的代码。
C# 类支持信息隐藏,通过将功能封装在属性和方法中实现,同时支持多种多态形式,包括:
- 继承多态(子类型多态):通过继承实现。
- 泛型多态(参数化多态):通过泛型实现。
C# 中可以定义多种类,包括实例类(普通类,可以实例化)、静态类和结构体。
定义类
类使用 class
关键字定义,后跟类名(标识符)。类的实例通过 new
关键字创建。
下面的代码定义了一个名为 Employee
的类,包含属性 Name
和 Age
,以及空方法 GetPayCheck()
和 Work()
。同时还定义了一个 Sample
类,用来实例化并使用 Employee
类。
public class Employee
{
private int _Age;
private string _Name;
public int Age
{
get { return _Age; }
set { _Age = value; }
}
public string Name
{
get { return _Name; }
set { _Name = value; }
}
public void GetPayCheck()
{
}
public void Work()
{
}
}
public class Sample
{
public static void Main()
{
Employee marissa = new Employee();
marissa.Work();
marissa.GetPayCheck();
}
}
方法
C# 中的方法是类的成员,包含代码逻辑。方法可以有返回值、参数列表以及泛型声明。方法与字段类似,可以是静态的(通过类访问)或实例的(通过类的实例对象访问)。
从 C# 4.0 开始,方法可以包含默认值的可选参数。例如:
void Increment(ref int x, int dx = 1)
{
x += dx;
}
调用时可以省略第二个参数:
int number = 10;
Increment(ref number); // dx 默认为 1
构造函数
构造函数用于控制类的初始化。当程序请求类类型的新对象时,构造函数的代码会执行以初始化该实例。
特点:
- 构造函数通常用于设置类的属性,但不仅限于此。
- 构造函数可以有参数。
- 如果构造函数有参数,则在使用
new
创建对象时需要传递参数。
以下代码定义了 Employee
类的两个构造函数(一个无参数,一个有参数),并分别通过它们创建对象:
public class Employee
{
public Employee()
{
System.Console.WriteLine("Constructed without parameters");
}
public Employee(string strText)
{
System.Console.WriteLine(strText);
}
}
public class Sample
{
public static void Main()
{
System.Console.WriteLine("Start");
// 使用无参数构造函数
Employee Alfred = new Employee();
// 使用带参数的构造函数
Employee Billy = new Employee("Parameter for construction");
System.Console.WriteLine("End");
}
}
代码输出说明
执行上述代码的输出如下:
Start
Constructed without parameters
Parameter for construction
End
解释:
- 当创建
Alfred
时,调用无参数构造函数,输出"Constructed without parameters"
。 - 当创建
Billy
时,调用带参数的构造函数,输出"Parameter for construction"
。 - 主程序按顺序输出其他消息。
总结
- 类:通过
class
定义,可以包含属性、方法和构造函数。 - 方法:可以有返回值、参数、泛型和默认参数。
- 构造函数:用于初始化类的实例,可重载以提供不同的初始化方式。
- 实例化:通过
new
创建类的对象,可以选择使用合适的构造函数。
构造函数与析构函数
构造函数之间的调用
在 C# 中,构造函数可以通过 this
关键字相互调用,从而减少代码重复。以下是一个示例:
public class Employee
{
// 带两个参数的构造函数
public Employee(string strText, int iNumber)
{
// 构造逻辑
}
// 带一个参数的构造函数,调用带两个参数的构造函数
public Employee(string strText)
: this(strText, 1234) // 使用用户指定的文本和默认值 1234 调用上面的构造函数
{ }
// 无参数的构造函数,调用带一个参数的构造函数
public Employee()
: this("default text") // 使用默认文本调用上面的构造函数
{ }
}
解释:
Employee(string strText, int iNumber)
是主构造函数。Employee(string strText)
调用主构造函数,传递strText
和默认值1234
。- 无参数的
Employee()
调用带一个参数的构造函数,传递默认文本"default text"
。
这种方式可以避免代码重复,集中处理逻辑。
析构函数(终结器)
析构函数是构造函数的对立面,用于定义对象的最终行为,当对象不再使用时执行。
在 C++ 中,析构函数通常用于释放对象占用的资源,但在 C# 中,由于 .NET Framework 的垃圾回收器(Garbage Collector, GC),析构函数的使用较少。
特点:
- 析构函数无参数。
- 当对象不再被引用时,析构函数会在某个时刻被调用,但具体时间不确定。
- 垃圾回收的复杂性决定了析构函数的调用时机可能会延迟。
示例代码:
public class Employee
{
// 构造函数
public Employee(string strText)
{
System.Console.WriteLine(strText);
}
// 析构函数
~Employee()
{
System.Console.WriteLine("Finalized!");
}
public static void Main()
{
// 创建对象,调用构造函数
Employee marissa = new Employee("Constructed!");
// 将对象设置为 null,释放引用
marissa = null;
// 垃圾回收器可能会在此之后的某个时刻调用析构函数
}
}
输出:
Constructed!
Finalized!
注意:
"Constructed!"
是构造函数的输出,在创建对象时立即执行。"Finalized!"
是析构函数的输出,垃圾回收器在对象被标记为不可达后调用。
总结
-
构造函数调用:
- 使用
this
调用其他构造函数,减少代码重复。 - 可通过不同的构造函数提供多种初始化方式。
- 使用
-
析构函数:
- 用于对象销毁时执行清理操作,但在 C# 中较少使用。
- 垃圾回收器的存在使得析构函数的调用时间不确定。
在 C# 中,如果需要可靠地释放资源(如文件句柄或数据库连接),推荐使用 IDisposable
接口和 using
块来实现明确的资源管理,而不是依赖析构函数。
属性 (Properties)
简介
C# 的属性是类的成员,通过字段的语法暴露方法的功能,简化了传统 get
和 set
方法的调用(即访问器方法)。属性可以是静态的或实例的。
定义属性
属性的定义如下:
public class MyClass
{
private int m_iField = 3; // 默认值为 3
public int IntegerField
{
get
{
return m_iField; // 返回字段的值
}
set
{
m_iField = value; // 将值赋给字段
}
}
}
使用属性可以像使用变量一样,但实际上可以在 get
和 set
中添加逻辑。
简化的自动属性
C# 提供了一种更简洁的属性语法,称为自动属性:
class Culture
{
public int TalkedCountries { get; set; }
public string Language { get; set; }
}
使用示例:
Culture culture = new Culture();
culture.Language = "Italian"; // 自动调用 set 方法
string strThisLanguage = culture.Language; // 自动调用 get 方法
这种方式无需显式定义 get
和 set
方法,编译器会自动生成相关代码。
示例代码
public class MyProgram
{
public static void Main()
{
MyClass myClass = new MyClass();
Console.WriteLine(myClass.IntegerField); // 输出 3
myClass.IntegerField = 7; // 间接给字段赋值
Console.WriteLine(myClass.IntegerField); // 输出 7
}
}
通过这种方式使用属性提供了简洁、易用的数据保护机制。
索引器 (Indexers)
简介
索引器是定义数组访问操作的类成员,允许类像数组一样使用。例如,可以通过 list[0]
访问 list
的第一个元素,即使 list
不是数组。
定义索引器
使用 this
关键字定义索引器:
public string this[string strKey]
{
get { return coll[strKey]; }
set { coll[strKey] = value; }
}
如果这个索引器定义在 EmployeeCollection
类中,使用方式如下:
EmployeeCollection e = new EmployeeCollection();
string s = e["Jones"]; // 获取键为 "Jones" 的值
e["Smith"] = "xxx"; // 设置键为 "Smith" 的值为 "xxx"
事件 (Events)
简介
C# 的事件是类成员,用于向类的客户端提供通知。事件只能触发(fire),而不能直接赋值。
定义事件
以下是一个完整的事件示例,其中包含委托、事件和方法:
using System;
public delegate void ContractHandler(Employee sender);
public class Employee
{
private bool bIsEngaged = false;
public event ContractHandler Engaged;
public Employee()
{
// 为事件注册默认方法
this.Engaged += new ContractHandler(this.OnEngaged);
}
private void OnEngaged(Employee sender)
{
Console.WriteLine("OnEngaged was called! This employee is now engaged.");
}
public string Name { get; set; }
public int Age { get; set; }
public bool IsEngaged
{
get { return bIsEngaged; }
set
{
if (!bIsEngaged && value)
{
Engaged?.Invoke(this); // 触发事件
}
bIsEngaged = value;
}
}
}
public class EntryPointClass
{
static void Main(string[] args)
{
Employee simpleEmployee = new Employee();
simpleEmployee.Age = 18;
simpleEmployee.Name = "Samanta Rock";
simpleEmployee.Engaged += new ContractHandler(SimpleEmployee_Engaged);
simpleEmployee.IsEngaged = true; // 设置为 true 时触发事件
Console.ReadLine();
}
static void SimpleEmployee_Engaged(Employee sender)
{
Console.WriteLine("The employee {0} is happy!", sender.Name);
}
}
代码解析
-
事件声明:
public event ContractHandler Engaged;
ContractHandler
是事件的委托类型,表示事件的签名。 -
触发事件:
Engaged?.Invoke(this);
使用
?.
确保事件不为空,然后调用注册的方法。 -
注册事件:
simpleEmployee.Engaged += new ContractHandler(SimpleEmployee_Engaged);
使用
+=
将方法注册到事件中。 -
触发时执行方法: 当
IsEngaged
属性被设置为true
时,事件被触发,调用所有已注册的方法。
总结
- 属性:简化数据访问,可以通过逻辑控制字段的读取和写入。
- 索引器:允许类像数组一样使用,简化了数据的索引访问。
- 事件:提供通知机制,通过委托触发,允许客户端响应状态变化。
这些特性使得 C# 在数据封装、访问和事件驱动开发中更加高效和灵活。
运算符重载 (Operator Overloading)
简介
C# 支持运算符重载,允许为类或结构定义或重新定义基本运算符的行为。这些运算符可以通过隐式或显式调用。
以下示例展示了复数类 (Complex
) 的运算符重载,包括二元运算符、单元运算符以及类型转换运算符:
public class Complex
{
private double m_dReal, m_dImaginary;
public double Real
{
get { return m_dReal; }
set { m_dReal = value; }
}
public double Imaginary
{
get { return m_dImaginary; }
set { m_dImaginary = value; }
}
// 二元运算符重载
public static Complex operator +(Complex c1, Complex c2)
{
return new Complex() { Real = c1.Real + c2.Real, Imaginary = c1.Imaginary + c2.Imaginary };
}
// 单元运算符重载
public static Complex operator -(Complex c)
{
return new Complex() { Real = -c.Real, Imaginary = -c.Imaginary };
}
// 类型转换运算符重载
public static implicit operator double(Complex c)
{
// 返回复数的模
return Math.Sqrt(Math.Pow(c.Real, 2) + Math.Pow(c.Imaginary, 2));
}
public static explicit operator string(Complex c)
{
// 类型转换为字符串
return c.Real.ToString() + " + " + c.Imaginary.ToString() + "i";
}
}
使用示例:
public class StaticDemo
{
public static void Main()
{
Complex number1 = new Complex() { Real = 1, Imaginary = 2 };
Complex number2 = new Complex() { Real = 4, Imaginary = 10 };
Complex number3 = number1 + number2; // 复数相加
number3 = -number3; // 复数取负号
double modulus = number3; // 隐式转换为 double(复数的模)
Console.WriteLine((string)number3); // 显式转换为字符串,输出 "-5 + -12i"
}
}
结构 (Structures)
简介
结构是用 struct
关键字定义的轻量级数据类型,类似于类,但有一些重要区别:
- 结构是值类型,而类是引用类型。
- 将结构传递给函数时,传递的是副本,函数中对副本的修改不会影响原结构。
- 结构无法为字段提供默认值,必须显式初始化。
定义示例:
struct Employee
{
public int m_iAge;
private string m_strName;
public string Name
{
get { return m_strName; }
set { m_strName = value; }
}
}
结构构造函数
结构需要构造函数(更准确地说是初始化器)来初始化所有字段。无参数构造函数在结构中是不被允许的。
struct Timestamp
{
private ushort m_usYear;
private ushort m_usMonth;
private ushort m_usDayOfMonth;
public Timestamp(ushort usYear, ushort usMonth, ushort usDay)
{
m_usYear = usYear;
m_usMonth = usMonth;
m_usDayOfMonth = usDay;
}
}
静态类 (Static Classes)
简介
静态类用于实现单例模式或提供全局方法和属性。静态类的所有成员都是静态的,不能实例化。
定义示例:
public static class Writer
{
public static void Write()
{
System.Console.WriteLine("Text");
}
}
使用示例:
public class Sample
{
public static void Main()
{
Writer.Write(); // 调用静态方法
}
}
总结
-
运算符重载:
- 可以为类或结构定义自定义的运算符行为。
- 支持二元、单元运算符以及类型转换运算符。
-
结构:
- 值类型,适用于小型数据结构。
- 需要显式初始化字段。
-
静态类:
- 提供全局方法和属性。
- 无法实例化,所有成员都是静态的。