C# 泛型

一、泛型概述

在 C# 中,泛型(Generics)是一种强大的编程特性,它允许您编写可以在不同数据类型上工作的通用代码,而无需为每种数据类型编写不同的代码。通过泛型,您可以编写更灵活可复用的代码,并提高代码的类型安全性性能

二、泛型特性

(一) 三大特性

1、类型安全性(Type Safety): 泛型提供了更强大的类型检查机制,使得编译器能够在编译时检查代码,防止不同数据类型之间的混淆。通过泛型,开发者可以在编译时发现并修复类型错误,而不是在运行时。

// 非泛型的 List,容易引起类型混淆
ArrayList nonGenericList = new ArrayList();
nonGenericList.Add("Hello");
int length = ((string)nonGenericList[0]).Length; // 运行时可能抛出异常

// 泛型的 List,类型安全
List<string> genericList = new List<string>();
genericList.Add("Hello");
int lengthSafe = genericList[0].Length; // 编译时就能发现错误

2、代码重用(Code Reusability): 泛型允许编写一次通用的代码,然后在不同的数据类型上重复使用,而无需为每种数据类型都编写特定的代码。这有助于减少代码的冗余,提高代码的可维护性和可读性。

// 非泛型的 Swap 方法,需要为每种类型写不同的实现
void SwapInt(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

void SwapString(ref string a, ref string b)
{
    string temp = a;
    a = b;
    b = temp;
}

// 泛型的 Swap 方法,通用于不同的数据类型
void Swap<T>(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}

3、性能优化(Performance Optimization): 泛型在一些情况下可以提高代码的性能。由于泛型是在编译时生成特定类型的代码,避免了运行时的装箱和拆箱操作。这有助于提高程序的执行效率。

// 非泛型的集合,需要装箱和拆箱操作
ArrayList nonGenericList = new ArrayList();
nonGenericList.Add(42);
int value = (int)nonGenericList[0]; // 运行时装箱和拆箱

// 泛型的集合,避免了装箱和拆箱
List<int> genericList = new List<int>();
genericList.Add(42);
int valueNoBoxing = genericList[0]; // 没有装箱和拆箱

这三大特性使得泛型成为现代编程语言中的一个重要工具,为开发者提供了更灵活、更安全、更高效的编程方式。

(二) 其他优点

  • 更好的抽象: 泛型允许你以一种更抽象的方式表示算法和数据结构,而不是专门针对特定类型。这使得代码更易理解、更容易维护,并且更容易适应变化。
  • 灵活性: 泛型提供了更大的灵活性,允许开发人员以一种通用的方式处理各种数据类型,而不必为每种类型编写特定的代码。
  • 可读性: 使用泛型可以使代码更加清晰和简洁,因为它减少了重复的代码块。这使得代码更易读懂,也更容易维护。
  • 容错性: 泛型能够提供更好的容错性,因为它在编译时就能够捕获到类型错误。这有助于避免在运行时发生的意外错误。

三、类型参数(Type Parameters)

在定义泛型类型或方法时,您可以声明一个或多个类型参数,它们在使用时将被实际类型替换。类型参数用尖括号(<>)括起来,并放置在类型或方法名称之后。

public class MyGenericClass<T>
{
    public T MyProperty { get; set; }

    public void MyMethod(T value)
    {
        // Do something with value
    }
}

四、泛型类(Generic Classes)

这些是具有一个或多个类型参数的类。您可以实例化泛型类,为每个类型参数提供实际类型。

MyGenericClass<int> intInstance = new MyGenericClass<int>();
MyGenericClass<string> stringInstance = new MyGenericClass<string>();

五、泛型方法(Generic Methods)

这些是具有类型参数的方法,可以在调用时为这些参数提供实际类型。泛型方法可以是类的成员方法,也可以是静态方法。

public T MyGenericMethod<T>(T value)
{
    return value;
}

六、泛型约束(Generic Constraints)

有时希望对泛型参数施加某些约束,以确保这些参数满足特定条件。例如,可以要求泛型类型实现特定接口或具有默认构造函数。

public class MyGenericClass<T> where T : IComparable
{
    // Class body
}

七、泛型接口(Generic Interfaces)

这些是具有一个或多个类型参数的接口。泛型接口可以被泛型类实现。

public interface IMyInterface<T>
{
    void MyMethod(T value);
}

八、泛型结构体

这些是具有一个或多个类型参数的结构体。可以实例化泛型结构体,为每个类型参数提供实际类型。

public struct MyGenericStruct<T>
{
    public T Value { get; }

    public MyGenericStruct(T value)
    {
        Value = value;
    }
}

九、List<T>

List<T> 是 C# 中的一个动态数组实现,它属于 System.Collections.Generic 命名空间,用于存储和操作元素的动态集合。以下是 List<T> 的简化版本源码,用于说明其基本结构和主要成员:

namespace System.Collections.Generic
{
    public class List<T> : IList<T>
    {
        private T[] items;  // 存储元素的数组
        private int count;   // 实际元素的数量

        public List()
        {
            items = new T[4];  // 默认初始容量为4
        }

        public int Count => count;

        public void Add(T item)
        {
            EnsureCapacity();  // 确保容量足够
            items[count++] = item;
        }

        public T this[int index]
        {
            get
            {
                if (index < 0 || index >= count)
                    throw new ArgumentOutOfRangeException(nameof(index));
                return items[index];
            }
            set
            {
                if (index < 0 || index >= count)
                    throw new ArgumentOutOfRangeException(nameof(index));
                items[index] = value;
            }
        }

        private void EnsureCapacity()
        {
            if (count == items.Length)
            {
                int newCapacity = items.Length * 2;  // 扩容为当前容量的两倍
                T[] newItems = new T[newCapacity];
                Array.Copy(items, newItems, count);
                items = newItems;
            }
        }

        // 其他 IList<T> 成员的实现...

        // 迭代器,用于遍历集合
        public IEnumerator<T> GetEnumerator()
        {
            for (int i = 0; i < count; i++)
            {
                yield return items[i];
            }
        }
    }
}

(一) List.Where

  1. 延迟执行Where() 方法采用延迟执行的机制,意味着它不会立即对列表进行遍历和筛选操作,而是在需要时才会执行实际的查询。
  2. 使用委托和Lambda表达式Where() 方法接受一个 Func<T, bool> 委托作为参数,该委托描述了筛选条件,通常使用 Lambda 表达式表示。
  3. 高效实现:对于内存中的 List<T>,LINQ 提供程序会尽可能地采用高效的实现方式来执行查询操作,这可能包括使用索引、缓存或其他数据结构,而不仅仅是简单地使用 foreach 循环逐一比较。
  4. 根据上下文和数据源类型选择执行方式:具体的执行方式取决于上下文和数据源的类型。对于内存中的列表,可能会采用不同于其他数据源(如数据库)的执行方式。

(二) List.Max

List.Max() 方法用于获取列表中的最大值。其对性能的影响取决于列表的大小和数据类型。

  1. 小型列表:对于包含少量元素的小型列表,List.Max() 方法的性能影响通常可以忽略不计。即使在较小的列表中,它也需要遍历列表一次以找到最大值,但由于列表规模较小,因此性能影响很小。
  2. 大型列表:对于大型列表,List.Max() 方法的性能影响可能会更加显著。在这种情况下,遍历整个列表以找到最大值可能会引起性能问题,特别是在高频率调用或在性能敏感的场景中。
  3. 数据类型List.Max() 方法对于基本数据类型(如整数、浮点数)的性能影响通常较小。但是,如果列表中的元素是自定义的复杂对象,那么计算最大值可能会涉及到比较复杂的逻辑,这可能会增加性能开销。
  4. 缓存:如果列表中的元素已经按照某种方式排序,或者最大值已经被计算并缓存,那么再次调用 List.Max() 方法时可能会更快,因为不需要重新遍历整个列表。

因此,虽然 List.Max() 方法本身在常规情况下的性能影响可能较小,但在处理大型列表或自定义数据类型时,需要谨慎考虑其使用频率以及可能的性能影响。在某些情况下,可能需要采取优化措施,例如使用更高效的数据结构或缓存结果,以降低性能开销。

posted @ 2024-03-07 14:42  咸鱼翻身?  阅读(7)  评论(0编辑  收藏  举报