类与方法

在 C# 中,与其他面向对象编程语言类似,程序的功能通过一个或多个类实现。类的方法和属性包含定义类行为的代码。

C# 类支持信息隐藏,通过将功能封装在属性和方法中实现,同时支持多种多态形式,包括:

  1. 继承多态(子类型多态):通过继承实现。
  2. 泛型多态(参数化多态):通过泛型实现。

C# 中可以定义多种类,包括实例类(普通类,可以实例化)、静态类和结构体。


定义类

类使用 class 关键字定义,后跟类名(标识符)。类的实例通过 new 关键字创建。

下面的代码定义了一个名为 Employee 的类,包含属性 NameAge,以及空方法 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

构造函数

构造函数用于控制类的初始化。当程序请求类类型的新对象时,构造函数的代码会执行以初始化该实例。

特点:

  1. 构造函数通常用于设置类的属性,但不仅限于此。
  2. 构造函数可以有参数。
  3. 如果构造函数有参数,则在使用 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

解释:

  1. 当创建 Alfred 时,调用无参数构造函数,输出 "Constructed without parameters"
  2. 当创建 Billy 时,调用带参数的构造函数,输出 "Parameter for construction"
  3. 主程序按顺序输出其他消息。

总结

  • :通过 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") // 使用默认文本调用上面的构造函数
    { }
}

解释:

  1. Employee(string strText, int iNumber) 是主构造函数。
  2. Employee(string strText) 调用主构造函数,传递 strText 和默认值 1234
  3. 无参数的 Employee() 调用带一个参数的构造函数,传递默认文本 "default text"

这种方式可以避免代码重复,集中处理逻辑。


析构函数(终结器)

析构函数是构造函数的对立面,用于定义对象的最终行为,当对象不再使用时执行。

在 C++ 中,析构函数通常用于释放对象占用的资源,但在 C# 中,由于 .NET Framework 的垃圾回收器(Garbage Collector, GC),析构函数的使用较少。

特点:

  1. 析构函数无参数。
  2. 当对象不再被引用时,析构函数会在某个时刻被调用,但具体时间不确定。
  3. 垃圾回收的复杂性决定了析构函数的调用时机可能会延迟。

示例代码:

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!" 是析构函数的输出,垃圾回收器在对象被标记为不可达后调用。

总结

  1. 构造函数调用

    • 使用 this 调用其他构造函数,减少代码重复。
    • 可通过不同的构造函数提供多种初始化方式。
  2. 析构函数

    • 用于对象销毁时执行清理操作,但在 C# 中较少使用。
    • 垃圾回收器的存在使得析构函数的调用时间不确定。

在 C# 中,如果需要可靠地释放资源(如文件句柄或数据库连接),推荐使用 IDisposable 接口和 using 块来实现明确的资源管理,而不是依赖析构函数。

属性 (Properties)

简介

C# 的属性是类的成员,通过字段的语法暴露方法的功能,简化了传统 getset 方法的调用(即访问器方法)。属性可以是静态的或实例的。

定义属性

属性的定义如下:

public class MyClass
{
    private int m_iField = 3; // 默认值为 3

    public int IntegerField
    {
        get
        {
            return m_iField;  // 返回字段的值
        }
        set
        {
            m_iField = value; // 将值赋给字段
        }
    }
}

使用属性可以像使用变量一样,但实际上可以在 getset 中添加逻辑。


简化的自动属性

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 方法

这种方式无需显式定义 getset 方法,编译器会自动生成相关代码。


示例代码

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);
    }
}

代码解析

  1. 事件声明

    public event ContractHandler Engaged;
    

    ContractHandler 是事件的委托类型,表示事件的签名。

  2. 触发事件

    Engaged?.Invoke(this);
    

    使用 ?. 确保事件不为空,然后调用注册的方法。

  3. 注册事件

    simpleEmployee.Engaged += new ContractHandler(SimpleEmployee_Engaged);
    

    使用 += 将方法注册到事件中。

  4. 触发时执行方法: 当 IsEngaged 属性被设置为 true 时,事件被触发,调用所有已注册的方法。


总结

  1. 属性:简化数据访问,可以通过逻辑控制字段的读取和写入。
  2. 索引器:允许类像数组一样使用,简化了数据的索引访问。
  3. 事件:提供通知机制,通过委托触发,允许客户端响应状态变化。

这些特性使得 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 关键字定义的轻量级数据类型,类似于类,但有一些重要区别:

  1. 结构是值类型,而类是引用类型。
  2. 将结构传递给函数时,传递的是副本,函数中对副本的修改不会影响原结构。
  3. 结构无法为字段提供默认值,必须显式初始化。

定义示例:

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(); // 调用静态方法
    }
}

总结

  1. 运算符重载

    • 可以为类或结构定义自定义的运算符行为。
    • 支持二元、单元运算符以及类型转换运算符。
  2. 结构

    • 值类型,适用于小型数据结构。
    • 需要显式初始化字段。
  3. 静态类

    • 提供全局方法和属性。
    • 无法实例化,所有成员都是静态的。
最后修改: 2025年01月11日 星期六 22:33