一、泛型概述
泛型的主要目的是提高代码的复用性和类型安全性,通过使用泛型,可以创建处理任何数据类型的类和方法,而无需为每种数据类型都编写一个单独的类或方法。同时,泛型还可以在编译时提供类型检查,从而避免在运行时出现类型错误。
二、泛型定义
在C#中,我们可以通过在类、接口、方法或委托的名称后面添加尖括号<>
来定义泛型。尖括号中包含的类型参数可以是任何有效的C#类型,包括类、结构、接口、委托等。
2.1 泛型方法的声明
//实现string 转换为其他类型
public static T TypeConvert<T>(string val)
{
//代码首先检查传入的字符串val是否为空、空白或为null。如果是,则直接返回该类型的默认值(例如,对于引用类型,返回null;对于值类型,返回其默认值,如0或false)。
if (String.IsNullOrWhiteSpace(val))
return default(T);
//如果T是一个枚举类型,则使用Enum.Parse方法将字符串val解析为该枚举类型的值。true参数表示不区分大小写。
if (typeof(T).IsEnum)
return (T)Enum.Parse(typeof(T), val, true);
//如果T不是枚举类型,则使用Convert.ChangeType方法将字符串val转换为T类型。如果转换失败,会抛出异常。
return (T)Convert.ChangeType(val, typeof(T));
}
2.1.1.用途
这个方法的主要用途是提供一个通用的字符串到类型的转换功能,适用于各种不同的类型,包括但不限于基本数据类型、枚举类型和自定义类型。它简化了类型转换的代码,使得代码更加简洁和易于维护。
2.2.2.注意事项
a.异常处理:如果Convert.ChangeType
方法无法将字符串转换为指定的类型,会抛出InvalidCastException
异常。在实际使用中,可能需要添加异常处理代码来捕获并处理这种异常;
b.枚举类型转换:Enum.Parse
方法在解析枚举值时,如果字符串val
不匹配任何枚举成员,会抛出ArgumentException
异常。因此,使用此方法时也需要考虑异常处理;
c.性能考虑:对于大量数据的转换,频繁调用此方法可能会影响性能。如果性能是一个关键考虑因素,可能需要寻找更高效的转换方法;
d.类型安全性:使用泛型方法时,编译器无法在编译时检查类型转换的正确性,因此需要在运行时处理可能的异常。
2.2 泛型方法的调用
int num = TypeConvert<int>("1");//将字符l类型的1转换成int 类型的数字1
三、泛型类和泛型接口
3.1 泛型类的声明
泛型类就是一个类可以实现多个类型的需求,常用的集合List<T> 就是一个泛型类,可以转到定义看到其声明 public class List<T> ,当然进行实例化的时候,必须要指定类型,List<string> names = new List<string>();
3.2 泛型接口
泛型接口,就是一个接口 满足多个多个类型的需求。
//声明泛型接口 public interface GenericInterface<T>
{
}
四、泛型委托
泛型委托与普通的委托相比,它可以适应更多的类型,一般提供了两种泛型委托,一个是Action,无返回值,另一个是Func,带返回值。通常可以自定义泛型委托,如下所示:
public delegate void GenericDelegate <T>(T para);
五、泛型约束
虽然泛型允许我们使用任何类型作为类型参数,但有时候可能需要对类型参数进行一些限制。这时,可以使用泛型约束来实现。泛型约束可以在类、接口或方法的定义中指定,以限制类型参数的类型范围。
C#提供了多种泛型约束,包括:
-
where T : struct
:约束类型参数必须是值类型。这确保了类型参数是如int
、double
、struct
等值类型,而不是引用类型。 -
where T : class
:约束类型参数必须是引用类型。这允许你指定类型参数必须是类、接口、委托或数组等引用类型。 -
where T : new()
:约束类型参数必须有一个无参数的构造函数。这确保了你可以创建该类型的新实例,这在某些需要实例化对象的泛型方法或类中特别有用。 -
where T : BaseClass
:约束类型参数必须是BaseClass
或其派生类。这允许你指定类型参数必须是某个特定类的子类或该类本身,从而确保类型参数具有某些特定的方法或属性。 -
where T : IInterface
:约束类型参数必须实现IInterface
接口。这确保了类型参数具有接口中定义的所有方法和属性,这对于确保类型参数符合某种约定或标准非常有用。
在使用泛型约束时,可以在一个泛型定义中指定多个约束。例如,可以同时要求一个类型参数是某个类的子类,并且实现某个接口。但是,这些约束之间必须是逻辑上兼容的,否则编译器会报错。下面是一个使用泛型约束的示例:
public class GenericList<T> where T : class, IComparable<T>
{
private List<T> items = new List<T>();//创建一个泛型列表来存储元素
public void Add(T item)//添加元素的方法
{
items.Add(item);
}
public T GetMax()//获取最大值的方法
{
if (items.Count == 0)
throw new InvalidOperationException("List is empty.");
T max = items[0];
foreach (T item in items)
{
if (item.CompareTo(max) > 0)
max = item;
}
return max;
}
}
在这个示例中,定义了一个泛型类GenericList<T>
,它接受一个类型参数T
。使用where
子句来约束T
,要求它必须是引用类型(class
),并且必须实现IComparable<T>
接口。这样,就可以在GetMax
方法中使用CompareTo
方法来比较列表中的元素,从而找出最大值。如果类型参数不满足这些约束,编译器将报错。
六、泛型的优点
使用泛型可以带来以下优点:
- 代码复用:通过定义泛型,我们可以编写一次代码,然后将其用于处理任何类型的数据,从而减少了重复代码的编写。
- 类型安全:泛型可以在编译时进行类型检查,从而避免了在运行时出现类型错误。
- 性能:由于泛型在编译时会将类型参数替换为具体的类型,因此可以避免在运行时进行类型检查和装箱/拆箱操作,从而提高了性能。
七、泛型的限制和注意事项
虽然泛型带来了许多优点,但在使用时也需要注意一些限制和注意事项。如,泛型不能用于静态字段或属性,因为静态成员是与类型本身关联的,而不是与类型参数关联的。此外,泛型也不能用于值类型的数组创建,因为值类型在编译时需要确定其大小。另外,虽然泛型可以提高类型安全性,但在某些情况下可能会增加代码的复杂性。因此,在使用泛型时需要根据具体的需求和场景进行权衡和选择。