c#基础知识合集15 虚方法 索引器
·
一、虚方法(Virtual Methods)
(一)定义与基本概念
- 定义:在基类中使用
virtual关键字声明的方法。虚方法提供了一种多态的基础,允许子类根据自身需求对该方法进行重写(Override),从而实现不同的行为。 - 语法格式:
public class BaseClass
{
public virtual void VirtualMethod()
{
// 基类方法的实现
Console.WriteLine("这是基类的虚方法。");
}
}
public class DerivedClass : BaseClass
{
public override void VirtualMethod()
{
// 子类重写后的方法实现
Console.WriteLine("这是子类重写的虚方法。");
}
}
(二)重点
- 多态性实现:虚方法是实现运行时多态性的关键机制之一。通过将对象声明为基类类型,但实际指向子类实例,在调用虚方法时,会根据对象的实际类型(即运行时类型)来决定执行哪个类中的方法版本。例如:
BaseClass baseObj1 = new BaseClass();
BaseClass baseObj2 = new DerivedClass();
baseObj1.VirtualMethod(); // 输出:这是基类的虚方法。
baseObj2.VirtualMethod(); // 输出:这是子类重写的虚方法。
- 方法重写规则:
- 子类必须使用
override关键字来重写基类的虚方法,且方法的签名(方法名、参数列表和返回类型)必须与基类中的虚方法完全一致。 - 重写的方法不能比基类中的虚方法更具限制性(例如,基类中虚方法为
public,子类重写时不能改为private)。
- 子类必须使用
(三)难点
- 理解运行时绑定:对于初学者来说,理解为什么在编译时对象声明为基类类型,但在运行时却能调用子类重写的方法,这涉及到运行时绑定的概念。这是因为在运行时,CLR(公共语言运行时)会根据对象的实际类型来查找并调用合适的方法版本,而不是根据对象声明的类型。
- 虚方法链调用:在某些复杂场景下,可能需要在子类重写的方法中调用基类的虚方法版本,这就涉及到使用
base关键字。例如:
public class DerivedClass : BaseClass
{
public override void VirtualMethod()
{
// 调用基类的虚方法
base.VirtualMethod();
// 子类自己的额外逻辑
Console.WriteLine("子类重写方法中的额外操作。");
}
}
(四)与其他概念的对比
- 与抽象方法对比:
- 抽象方法:必须在抽象类中声明,且只有声明没有实现(即没有方法体),必须由子类重写。例如:
public abstract class AbstractBaseClass
{
public abstract void AbstractMethod();
}
public class ConcreteClass : AbstractBaseClass
{
public override void AbstractMethod()
{
Console.WriteLine("子类实现抽象方法。");
}
}
- 区别:
- 虚方法有默认实现,子类可选择重写;抽象方法无实现,子类必须重写。
- 包含抽象方法的类必须声明为抽象类,而包含虚方法的类不一定是抽象类。
- 与密封方法对比:
- 密封方法:使用
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)
(一)定义与基本概念
- 定义:索引器允许类或结构的实例像数组一样通过索引进行访问。它是一种特殊的属性,通过
this关键字定义,使对象可以使用类似数组的语法来获取或设置值。 - 语法格式:
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 数组中的元素。
(二)重点
- 提供便捷访问方式:索引器提供了一种直观且方便的方式来访问对象内部的数据集合,使得代码更易读和编写。例如:
MyList list = new MyList();
list[0] = 10;
int value = list[0];
Console.WriteLine(value); // 输出:10
- 索引器重载:与方法重载类似,一个类可以定义多个索引器,只要它们的参数列表不同。例如,可以定义一个接受字符串参数的索引器来根据名称查找元素:
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);
}
}
}
}
(三)难点
- 索引器的签名与实现逻辑:确定合适的索引器参数类型和实现正确的访问逻辑是关键。例如,在实现基于自定义类型的索引器时,需要考虑如何根据索引参数准确地定位和操作内部数据结构。同时,要处理好边界条件,如索引越界等情况,以确保程序的健壮性。
- 与属性和数组的区别:
- 与属性对比:虽然索引器和属性都使用
get和set访问器,但属性通常用于访问单个数据成员,而索引器用于访问对象内部的一组数据,通过索引来区分不同元素。 - 与数组对比:索引器提供了对对象内部数据集合的类似数组的访问方式,但它更灵活,可以自定义索引的类型(不限于整数),并且可以在访问器中实现复杂的逻辑,而数组的索引只能是整数,且访问逻辑相对简单直接。
- 与属性对比:虽然索引器和属性都使用
(四)与其他概念的对比
- 与属性对比:
- 属性:用于封装类的字段,提供对单个数据成员的访问控制。例如:
public class Person
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
- 区别:
- 属性通常操作单个数据值,而索引器用于操作一组数据值,通过索引区分不同元素。
- 属性的访问通过属性名,索引器通过索引值(可以是各种类型)。
- 与数组对比:
- 数组:是一种固定类型的有序数据集合,其索引只能是整数类型。例如:
int[] numbers = new int[5];
numbers[0] = 1;
- 区别:
- 索引器的索引类型可以自定义,不局限于整数,而数组索引只能是整数。
- 索引器可以在访问器中实现复杂的逻辑,如数据验证、日志记录等,数组访问相对简单直接。
- 数组是一种数据结构,而索引器是类或结构中的一种特殊成员。
更多推荐

所有评论(0)