C#泛型实战:从零构建类型安全的线性链表

沿途有光
沿途有光
发布时间:2026-10-14 | 2人浏览过
原创

在.NET开发中,早期使用System.Object作为所有集合元素的基类会导致严重的性能浪费(装箱/拆箱)和类型安全隐患。本文将通过动手编写一个线性链表,对比非泛型实现的缺陷与泛型实现的优势,帮助开发者理解C#泛型的实际价值。

为什么需要泛型:传统集合的痛点

C#是强类型语言,编译器旨在捕获潜在错误。但在.NET 1.1及早期版本中,内置集合仅支持存储 Object 类型。由于一切皆对象,这虽然实现了通用性,却丧失了类型检查能力,带来两大核心问题:

  1. 性能损耗:值类型(如 int)存入集合时需进行装箱(Boxing),取出时需进行拆箱(Unboxing)。这不仅增加CPU开销,还频繁分配堆内存导致GC压力。
  2. 类型不安全:取出数据时需显式强制转换,若类型不匹配将抛出 InvalidCastException 异常,且这些错误只能在运行时发现,而非编译期。

第一步:构建非泛型线性链表(Node结构)

为了直观感受上述问题,首先定义一个基础节点类。它存储任意对象数据,并通过递归方式引用下一个节点。

第1步:定义通用数据容器

入口:新建 C# 类文件 Node.cs

动作:创建一个以 object 为数据类型的节点类。为了实现链表的完整功能,我们需要实现添加节点的方法和打印方法。

复制代码代码如下:
public class Employee {
    private string name;
    public Employee(string name) {
        this.name = name;
    }
    public override string ToString() {
        return this.name;
    }
}

// 非泛型节点类
public class Node {
    object data;       // 存储数据的字段
    Node next;         // 指向下一个节点的引用

    public Node(object data) {
        this.data = data;
        this.next = null;
    }

    // 属性访问器
    public object Data {
        get { return this.data; }
        set { data = value; }
    }

    public Node Next {
        get { return this.next; }
        set { this.next = value; }
    }

    /// <summary>
    /// 将新节点追加到链表尾部
    /// </summary>
    public void Append(Node newNode) {
        if (this.next == null) {
            this.next = newNode;
        } else {
            // 递归调用直到找到末尾
            next.Append(newNode);
        }
    }

    /// <summary>
    /// 递归输出链表所有节点内容
    /// </summary>
    public override string ToString() {
        string output = data.ToString();
        if (next != null) {
            output += ", " + next.ToString();
        }
        return output;
    }
}

第2步:实现链表管理类

入口:新建 C# 类文件 LinkedList.cs

动作:创建管理链头的 LinkedList 类。核心逻辑包括判断是否为空头节点、使用 Add 方法插入数据,以及提供类似数组的索引访问能力。

复制代码代码如下:
public class LinkedList {
    Node headNode = null;

    // 添加数据到链表
    public void Add(object data) {
        if (headNode == null) {
            headNode = new Node(data);
        } else {
            headNode.Append(new Node(data));
        }
    }

    // 索引器:允许像数组一样访问元素
    public object this[int index] {
        get {
            int ctr = 0;
            Node node = headNode;
            while (node != null && ctr <= index) {
                if (ctr == index) {
                    return node.Data;
                } else {
                    node = node.Next;
                }
                ctr++;
            }
            return null;
        }
    }

    public override string ToString() {
        if (this.headNode != null) {
            return this.headNode.ToString();
        } else {
            return string.Empty;
        }
    }
}

第3步:测试非泛型链表的局限性

目的:验证混合类型存储带来的隐患。

操作:在同一实例中存入整型和员工对象。

复制代码代码如下:
public void Run() {
    LinkedList ll = new LinkedList();
    
    // 存入整型 -> 触发隐式装箱 (Boxing)
    for (int i = 0; i < 10; i++) {
        ll.Add(i); 
    }
    
    Console.WriteLine(ll);
    Console.WriteLine(" Done. Adding employees...");
    
    // 存入对象
    ll.Add(new Employee("John"));
    ll.Add(new Employee("Paul"));
    // ... 其他员工
    
    // 取回数据 -> 必须显式拆箱 (Unboxing) 和类型转换
    Console.WriteLine("The fourth integer is " + Convert.ToInt32(ll[3]));
    Employee d = (Employee)ll[11]; // 若此处取出的是int,程序将崩溃
    Console.WriteLine("The second Employee is " + d);
}

虽然代码运行看似正常,但生成的IL代码显示 box System.Int32,且在取值时必须进行脆弱的强制转换。

第二步:利用泛型重构链表(代码重用与类型安全)

针对每种数据类型编写专用链表类(如 EmployeeLinkedList)会导致大量重复代码。C#泛型通过在编译期建立类型契约,完美解决了此问题。

第4步:定义泛型节点

入口:修改 Node.cs

参数:引入 <T>,表示该节点可接受任意类型。

复制代码代码如下:
// T 代表具体的类型,如 int 或 Employee
public class Node<T> {
    T data;      // 现在直接保存 T 类型,无需装箱
    Node<T> next; // 下一个节点也是泛型化的

    public Node(T data) {
        this.data = data;
        this.next = null;
    }

    public T Data {
        get { return this.data; }
        set { data = value; }
    }

    public Node<T> Next {
        get { return this.next; }
        set { this.next = value; }
    }

    public void Append(Node<T> newNode) {
        if (this.next == null) {
            this.next = newNode;
        } else {
            next.Append(newNode);
        }
    }

    public override string ToString() {
        string output = data.ToString();
        if (next != null) {
            output += ", " + next.ToString();
        }
        return output;
    }
}

第5步:定义泛型链表

入口:修改 LinkedList.cs

动作:同样应用 <T> 标签,使得整个链表具备单一类型约束。

复制代码代码如下:
public class LinkedList<T> {
    Node<T> headNode = null;

    public void Add(T data) {
        if (headNode == null) {
            headNode = new Node<T>(data);
        } else {
            headNode.Append(new Node<T>(data));
        }
    }

    public T this[int index] {
        get {
            int ctr = 0;
            Node<T> node = headNode;
            while (node != null && ctr <= index) {
                if (ctr == index) {
                    return node.Data;
                } else {
                    node = node.Next;
                }
                ctr++;
            }
            return default(T); // 更安全地处理越界
        }
    }

    public override string ToString() {
        if (this.headNode != null) {
            return this.headNode.ToString();
        } else {
            return string.Empty;
        }
    }
}

第三步:泛型链表的实际效果与优势

在使用时,我们显式指定类型,即可获得零性能损耗的类型安全集合。

复制代码代码如下:
// 声明一个整数链表,自动绑定类型为 int
LinkedList<int> ll = new LinkedList<int>();
for (int i = 0; i < 10; i++) {
    ll.Add(i); // 无装箱行为,直接赋值
}
Console.WriteLine(ll);

// 声明一个员工链表,自动绑定类型为 Employee
LinkedList<Employee> employees = new LinkedList<Employee>();
employees.Add(new Employee("John"));
employees.Add(new Employee("Paul"));

Console.WriteLine(employees);

// 取值时无需转换,且无法添加错误类型
// 下面这行代码如果取消注释,将在编译阶段报错!
// employees.Add(5); 

Employee d = employees[1]; // 强类型返回,智能提示友好
Console.WriteLine("The second Employee is " + d);

总结

通过重写链表结构,可以看出泛型在C#中的核心价值:
1. **消除装箱拆箱**:对于值类型集合,性能提升显著。
2. **类型安全**:错误在编译期拦截,避免运行时的 InvalidCastException。
3. **代码复用**:只需编写一套 LinkedList<T> 即可服务于任何业务类型。

以上就是C#泛型学习详解及创建线性链表的详细内容,更多关于C#泛型原理与应用的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表