一、虚方法(Virtual Methods)

(一)定义与基本概念

  1. 定义:在基类中使用 virtual 关键字声明的方法。虚方法提供了一种多态的基础,允许子类根据自身需求对该方法进行重写(Override),从而实现不同的行为。
  2. 语法格式
public class BaseClass
{
    public virtual void VirtualMethod()
    {
        // 基类方法的实现
        Console.WriteLine("这是基类的虚方法。");
    }
}

public class DerivedClass : BaseClass
{
    public override void VirtualMethod()
    {
        // 子类重写后的方法实现
        Console.WriteLine("这是子类重写的虚方法。");
    }
}

(二)重点

  1. 多态性实现:虚方法是实现运行时多态性的关键机制之一。通过将对象声明为基类类型,但实际指向子类实例,在调用虚方法时,会根据对象的实际类型(即运行时类型)来决定执行哪个类中的方法版本。例如:
BaseClass baseObj1 = new BaseClass();
BaseClass baseObj2 = new DerivedClass();

baseObj1.VirtualMethod(); // 输出:这是基类的虚方法。
baseObj2.VirtualMethod(); // 输出:这是子类重写的虚方法。
  1. 方法重写规则
    • 子类必须使用 override 关键字来重写基类的虚方法,且方法的签名(方法名、参数列表和返回类型)必须与基类中的虚方法完全一致。
    • 重写的方法不能比基类中的虚方法更具限制性(例如,基类中虚方法为 public,子类重写时不能改为 private)。

(三)难点

  1. 理解运行时绑定:对于初学者来说,理解为什么在编译时对象声明为基类类型,但在运行时却能调用子类重写的方法,这涉及到运行时绑定的概念。这是因为在运行时,CLR(公共语言运行时)会根据对象的实际类型来查找并调用合适的方法版本,而不是根据对象声明的类型。
  2. 虚方法链调用:在某些复杂场景下,可能需要在子类重写的方法中调用基类的虚方法版本,这就涉及到使用 base 关键字。例如:
public class DerivedClass : BaseClass
{
    public override void VirtualMethod()
    {
        // 调用基类的虚方法
        base.VirtualMethod(); 
        // 子类自己的额外逻辑
        Console.WriteLine("子类重写方法中的额外操作。"); 
    }
}

(四)与其他概念的对比

  1. 与抽象方法对比
    • 抽象方法:必须在抽象类中声明,且只有声明没有实现(即没有方法体),必须由子类重写。例如:
public abstract class AbstractBaseClass
{
    public abstract void AbstractMethod();
}

public class ConcreteClass : AbstractBaseClass
{
    public override void AbstractMethod()
    {
        Console.WriteLine("子类实现抽象方法。");
    }
}
  • 区别
    • 虚方法有默认实现,子类可选择重写;抽象方法无实现,子类必须重写。
    • 包含抽象方法的类必须声明为抽象类,而包含虚方法的类不一定是抽象类。
  1. 与密封方法对比
    • 密封方法:使用 sealed 关键字修饰,用于防止子类进一步重写该方法。例如:
public class BaseClass
{
    public virtual void VirtualMethod()
    {
        Console.WriteLine("基类虚方法。");
    }
}

public class DerivedClass : BaseClass
{
    public sealed override void VirtualMethod()
    {
        Console.WriteLine("子类重写并密封的虚方法。");
    }
}

public class FurtherDerivedClass : DerivedClass
{
    // 下面这行代码会报错,因为VirtualMethod已被密封
    // public override void VirtualMethod() 
    // { 
    //     Console.WriteLine("尝试进一步重写,但不允许。"); 
    // } 
}
  • 区别:虚方法允许子类重写以实现多态,而密封方法在子类重写后阻止后续子类再重写,用于防止意外的方法重写改变原有逻辑。

二、索引器(Indexers)

(一)定义与基本概念

  1. 定义:索引器允许类或结构的实例像数组一样通过索引进行访问。它是一种特殊的属性,通过 this 关键字定义,使对象可以使用类似数组的语法来获取或设置值。
  2. 语法格式
public class MyList
{
    private int[] items = new int[10];

    public int this[int index]
    {
        get
        {
            if (index >= 0 && index < items.Length)
            {
                return items[index];
            }
            throw new IndexOutOfRangeException();
        }
        set
        {
            if (index >= 0 && index < items.Length)
            {
                items[index] = value;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
    }
}

在上述代码中,MyList 类定义了一个索引器 this[int index],允许通过索引来访问和修改 items 数组中的元素。

(二)重点

  1. 提供便捷访问方式:索引器提供了一种直观且方便的方式来访问对象内部的数据集合,使得代码更易读和编写。例如:
MyList list = new MyList();
list[0] = 10;
int value = list[0];
Console.WriteLine(value); // 输出:10
  1. 索引器重载:与方法重载类似,一个类可以定义多个索引器,只要它们的参数列表不同。例如,可以定义一个接受字符串参数的索引器来根据名称查找元素:
public class MyDictionary
{
    private Dictionary<string, int> data = new Dictionary<string, int>();

    public int this[string key]
    {
        get
        {
            if (data.ContainsKey(key))
            {
                return data[key];
            }
            throw new KeyNotFoundException();
        }
        set
        {
            if (data.ContainsKey(key))
            {
                data[key] = value;
            }
            else
            {
                data.Add(key, value);
            }
        }
    }
}

(三)难点

  1. 索引器的签名与实现逻辑:确定合适的索引器参数类型和实现正确的访问逻辑是关键。例如,在实现基于自定义类型的索引器时,需要考虑如何根据索引参数准确地定位和操作内部数据结构。同时,要处理好边界条件,如索引越界等情况,以确保程序的健壮性。
  2. 与属性和数组的区别
    • 与属性对比:虽然索引器和属性都使用 getset 访问器,但属性通常用于访问单个数据成员,而索引器用于访问对象内部的一组数据,通过索引来区分不同元素。
    • 与数组对比:索引器提供了对对象内部数据集合的类似数组的访问方式,但它更灵活,可以自定义索引的类型(不限于整数),并且可以在访问器中实现复杂的逻辑,而数组的索引只能是整数,且访问逻辑相对简单直接。

(四)与其他概念的对比

  1. 与属性对比
    • 属性:用于封装类的字段,提供对单个数据成员的访问控制。例如:
public class Person
{
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}
  • 区别
    • 属性通常操作单个数据值,而索引器用于操作一组数据值,通过索引区分不同元素。
    • 属性的访问通过属性名,索引器通过索引值(可以是各种类型)。
  1. 与数组对比
    • 数组:是一种固定类型的有序数据集合,其索引只能是整数类型。例如:
int[] numbers = new int[5];
numbers[0] = 1;
  • 区别
    • 索引器的索引类型可以自定义,不局限于整数,而数组索引只能是整数。
    • 索引器可以在访问器中实现复杂的逻辑,如数据验证、日志记录等,数组访问相对简单直接。
    • 数组是一种数据结构,而索引器是类或结构中的一种特殊成员。

更多推荐