三门网站制作,destoon做众筹网站,企查查企业信息查询手机版,如何在godaddy空间做手机网站目录
C# 泛型#xff08;Generic#xff09;
泛型类型参数
类型参数的约束
约束多个参数
未绑定的类型参数
类型参数作为约束
notnull 约束
class 约束
default 约束
非托管约束
委托约束
枚举约束
类型参数实现声明的接口
泛型类
泛型方法
泛型和数组
泛型…目录
C# 泛型Generic
泛型类型参数
类型参数的约束
约束多个参数
未绑定的类型参数
类型参数作为约束
notnull 约束
class 约束
default 约束
非托管约束
委托约束
枚举约束
类型参数实现声明的接口
泛型类
泛型方法
泛型和数组
泛型
数组
泛型和数组的结合运用
泛型委托
委托
泛型委托
C 模板和 C# 泛型之间的区别 C# 泛型Generic
泛型类型参数
在泛型类型或方法定义中类型参数充当了一个占位符用于在创建泛型类型的实例时由客户端指定特定的类型。 泛型类例如泛型介绍中列出的 GenericListT本身并不是一个具体的类型而更像是类型的模板或蓝图。要使用 GenericListT客户端代码必须在尖括号内指定类型参数以声明并实例化特定的构造类型。这个类型参数可以是编译器能够识别的任何类型。通过这种方式可以创建任意数量的泛型类型实例每个实例都使用不同的类型参数。
举个例子假设有一个泛型类 GenericListT客户端可以这样使用它来创建不同类型的实例
GenericListint intList new GenericListint();
GenericListstring stringList new GenericListstring();
GenericListCustomer customerList new GenericListCustomer();在这个例子中我们使用了三种不同的类型参数int、string 和 Customer分别实例化了三个不同类型的 GenericList。每个实例在运行时会被视为独立的类型它们拥有各自特定的类型参数并且可以确保类型安全性和有效性。
类型参数的约束
约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下类型参数可以是任何类型。 编译器只能假定 System.Object 的成员它是任何 .NET 类型的最终基类。 如果客户端代码使用不满足约束的类型编译器将发出错误。 通过使用 where 上下文关键字指定约束。 下表列出了各种类型的约束
约束描述where T : struct类型参数必须是不可为 null 的值类型。 有关可为 null 的值类型的信息请参阅可为 null 的值类型。 由于所有值类型都具有可访问的无参数构造函数因此 struct 约束表示 new() 约束并且不能与 new() 约束结合使用。 struct 约束也不能与 unmanaged 约束结合使用。where T : class类型参数必须是引用类型。 此约束还应用于任何类、接口、委托或数组类型。 在可为 null 的上下文中T 必须是不可为 null 的引用类型。where T : class?类型参数必须是可为 null 或不可为 null 的引用类型。 此约束还应用于任何类、接口、委托或数组类型。where T : notnull类型参数必须是不可为 null 的类型。 参数可以是不可为 null 的引用类型也可以是不可为 null 的值类型。where T : default重写方法或提供显式接口实现时如果需要指定不受约束的类型参数此约束可解决歧义。 default 约束表示基方法但不包含 class 或 struct 约束。 有关详细信息请参阅default约束规范建议。where T : unmanaged类型参数必须是不可为 null 的非托管类型。 unmanaged 约束表示 struct 约束且不能与 struct 约束或 new() 约束结合使用。where T : new()类型参数必须具有公共无参数构造函数。 与其他约束一起使用时new() 约束必须最后指定。 new() 约束不能与 struct 和 unmanaged 约束结合使用。where T :基类名类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中T 必须是从指定基类派生的不可为 null 的引用类型。where T :基类名?类型参数必须是指定的基类或派生自指定的基类。 在可为 null 的上下文中T 可以是从指定基类派生的可为 null 或不可为 null 的类型。where T :接口名称类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在的可为 null 的上下文中T 必须是实现指定接口的不可为 null 的类型。where T :接口名称?类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。 在可为 null 的上下文中T 可以是可为 null 的引用类型、不可为 null 的引用类型或值类型。 T 不能是可为 null 的值类型。where T : U为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。 在可为 null 的上下文中如果 U 是不可为 null 的引用类型T 必须是不可为 null 的引用类型。 如果 U 是可为 null 的引用类型则 T 可以是可为 null 的引用类型也可以是不可为 null 的引用类型。
使用约束的原因 约束指定类型参数的功能和预期。 声明这些约束意味着你可以使用约束类型的操作和方法调用。 如果泛型类或方法对泛型成员使用除简单赋值之外的任何操作或调用 System.Object 不支持的任何方法则将对类型参数应用约束。 例如基类约束告诉编译器仅此类型的对象或派生自此类型的对象可用作类型参数。 编译器有了此保证后就能够允许在泛型类中调用该类型的方法。 以下代码示例演示可通过应用基类约束添加到泛型介绍中的GenericListT 类的功能。
public class Employee
{public Employee(string name, int id) (Name, ID) (name, id);public string Name { get; set; }public int ID { get; set; }
}public class GenericListT where T : Employee
{private class Node{public Node(T t) (Next, Data) (null, t);public Node? Next { get; set; }public T Data { get; set; }}private Node? head;public void AddHead(T t){Node n new Node(t) { Next head };head n;}public IEnumeratorT GetEnumerator(){Node? current head;while (current ! null){yield return current.Data;current current.Next;}}public T? FindFirstOccurrence(string s){Node? current head;T? t null;while (current ! null){//The constraint enables access to the Name property.if (current.Data.Name s){t current.Data;break;}else{current current.Next;}}return t;}
}
约束使泛型类能够使用 Employee.Name 属性。 约束指定类型 T 的所有项都保证是 Employee 对象或从 Employee 继承的对象。
可以对同一类型参数应用多个约束并且约束自身可以是泛型类型如下所示
class EmployeeListT where T : Employee, IEmployee, System.IComparableT, new()
{// ...
}
在应用 where T : class 约束时请避免对类型参数使用 和 ! 运算符因为这些运算符仅测试引用标识而不测试值相等性。 即使在用作参数的类型中重载这些运算符也会发生此行为。 下面的代码说明了这一点即使 String 类重载 运算符输出也为 false。
public static void OpEqualsTestT(T s, T t) where T : class
{System.Console.WriteLine(s t);
}private static void TestStringEquality()
{string s1 target;System.Text.StringBuilder sb new System.Text.StringBuilder(target);string s2 sb.ToString();OpEqualsTeststring(s1, s2);
}
编译器只知道 T 在编译时是引用类型并且必须使用对所有引用类型都有效的默认运算符。 如果必须测试值相等性建议同时应用 where T : IEquatableT 或 where T : IComparableT 约束并在用于构造泛型类的任何类中实现该接口。
约束多个参数
可以对多个参数应用多个约束对一个参数应用多个约束如下例所示
class Base { }
class TestT, Uwhere U : structwhere T : Base, new()
{ }
未绑定的类型参数
没有约束的类型参数如公共类 SampleClassT{} 中的 T称为未绑定的类型参数。 未绑定的类型参数具有以下规则
不能使用 ! 和 运算符因为无法保证具体的类型参数能支持这些运算符。可以在它们与 System.Object 之间来回转换或将它们显式转换为任何接口类型。可以将它们与 null 进行比较。 将未绑定的参数与 null 进行比较时如果类型参数为值类型则该比较将始终返回 false。
类型参数作为约束
在具有自己类型参数的成员函数必须将该参数约束为包含类型的类型参数时将泛型类型参数用作约束非常有用如下例所示
public class ListT
{public void AddU(ListU items) where U : T {/*...*/}
}
在上述示例中T 在 Add 方法的上下文中是一个类型约束而在 List 类的上下文中是一个未绑定的类型参数。
类型参数还可在泛型类定义中用作约束。 必须在尖括号中声明该类型参数以及任何其他类型参数
//Type parameter V is used as a type constraint.
public class SampleClassT, U, V where T : V { }
类型参数作为泛型类的约束的作用非常有限因为编译器除了假设类型参数派生自 System.Object 以外不会做其他任何假设。 如果要在两个类型参数之间强制继承关系可以将类型参数用作泛型类的约束。
notnull 约束
可以使用 notnull 约束指定类型参数必须是不可为 null 的值类型或不可为 null 的引用类型。 与大多数其他约束不同如果类型参数违反 notnull 约束编译器会生成警告而不是错误。
notnull 约束仅在可为 null 上下文中使用时才有效。 如果在过时的可为 null 上下文中添加 notnull 约束编译器不会针对违反约束的情况生成任何警告或错误。
class 约束
可为 null 的上下文中的 class 约束指定类型参数必须是不可为 null 的引用类型。 在可为 null 上下文中当类型参数是可为 null 的引用类型时编译器会生成警告。
default 约束
添加可为空引用类型会使泛型类型或方法中的 T? 使用复杂化。 T? 可以与 struct 或 class 约束一起使用但必须存在其中一项。 使用 class 约束时T? 引用了 T 的可为空引用类型。 从 C# 9 开始可在这两个约束均未应用时使用 T?。 在这种情况下对于值类型和引用类型T? 解读为 T?。 但是如果 T是 NullableT的实例则 T? 与 T 相同。 换句话说它不会成为 T??。
由于现在可在没有 class 或 struct 约束的情况下使用 T?因此在重写或显式接口实现中可能会出现歧义。 在这两种情况下重写不包含约束但从基类继承。 当基类不应用 class 或 struct 约束时派生类需要通过某种方式在不使用任一种约束的情况下指定应用于基方法的重写。 此时派生方法将应用 default 约束。 default 约束不阐明 class 和 struct 约束。
非托管约束
可使用 unmanaged 约束来指定类型参数必须是不可为 null 的非托管类型。通过 unmanaged 约束用户能编写可重用例程从而使用可作为内存块操作的类型如以下示例所示
unsafe public static byte[] ToByteArrayT(this T argument) where T : unmanaged
{var size sizeof(T);var result new Byte[size];Byte* p (byte*)argument;for (var i 0; i size; i)result[i] *p;return result;
}
以上方法必须在 unsafe 上下文中编译因为它并不是在已知的内置类型上使用 sizeof 运算符。 如果没有 unmanaged 约束则 sizeof 运算符不可用。
unmanaged 约束表示 struct 约束且不能与其结合使用。 因为 struct 约束表示 new() 约束且 unmanaged 约束也不能与 new() 约束结合使用。
委托约束
可以使用 System.Delegate 或 System.MulticastDelegate 作为基类约束。 CLR 始终允许此约束但 C# 语言不允许。 使用 System.Delegate 约束用户能够以类型安全的方式编写使用委托的代码。 以下代码定义了合并两个同类型委托的扩展方法
public static TDelegate? TypeSafeCombineTDelegate(this TDelegate source, TDelegate target)where TDelegate : System.Delegate Delegate.Combine(source, target) as TDelegate;
可使用上述方法来合并相同类型的委托
Action first () Console.WriteLine(this);
Action second () Console.WriteLine(that);var combined first.TypeSafeCombine(second);
combined!();Funcbool test () true;
// Combine signature ensures combined delegates must
// have the same type.
//var badCombined first.TypeSafeCombine(test);
如果取消评论最后一行它将不会编译。 first 和 test 均为委托类型但它们是不同的委托类型。
枚举约束
还可指定 System.Enum 类型作为基类约束。 CLR 始终允许此约束但 C# 语言不允许。使用 System.Enum 的泛型提供类型安全的编程缓存使用 System.Enum 中静态方法的结果。 以下示例查找枚举类型的所有有效的值然后生成将这些值映射到其字符串表示形式的字典。
public static Dictionaryint, string EnumNamedValuesT() where T : System.Enum
{var result new Dictionaryint, string();var values Enum.GetValues(typeof(T));foreach (int item in values)result.Add(item, Enum.GetName(typeof(T), item)!);return result;
}
Enum.GetValues 和 Enum.GetName 使用反射这会对性能产生影响。 可调用 EnumNamedValues 来生成可缓存和重用的集合而不是重复执行需要反射才能实施的调用。
如以下示例所示可使用它来创建枚举并生成其值和名称的字典
enum Rainbow
{Red,Orange,Yellow,Green,Blue,Indigo,Violet
}
var map EnumNamedValuesRainbow();foreach (var pair in map)Console.WriteLine(${pair.Key}:\t{pair.Value});
类型参数实现声明的接口
某些场景要求为类型参数提供的参数实现该接口。 例如
public interface IAdditionSubtractionT where T : IAdditionSubtractionT
{public abstract static T operator (T left, T right);public abstract static T operator -(T left, T right);
}
此模式使 C# 编译器能够确定重载运算符或任何 static virtual 或 static abstract 方法的包含类型。 它提供的语法使得可以在包含类型上定义加法和减法运算符。 如果没有此约束需要将参数和自变量声明为接口而不是类型参数
public interface IAdditionSubtractionT where T : IAdditionSubtractionT
{public abstract static IAdditionSubtractionT operator (IAdditionSubtractionT left,IAdditionSubtractionT right);public abstract static IAdditionSubtractionT operator -(IAdditionSubtractionT left,IAdditionSubtractionT right);
}
上述语法要求实现者对这些方法使用显式接口实现。 提供额外的约束使接口能够根据类型参数来定义运算符。 实现接口的类型可以隐式实现接口方法。
泛型类
泛型类是一种具有泛型类型参数的类它可以在定义时不指定具体的数据类型而在实例化时再指定具体的数据类型。使用泛型类可以编写出更加通用和灵活的代码以适应各种不同类型的数据。
1、泛型类的定义泛型类的定义与普通类类似只是在类名后面使用尖括号加上类型参数例如public class MyGenericClassT { /*...*/ }。
2、类型参数 T类型参数 T 是一个占位符它代表着实际的数据类型在实例化时将会被替换为具体的类型。可以有多个类型参数用逗号分隔。
3、使用类型参数在泛型类的定义中可以在类的字段、属性、方法等地方使用类型参数 T从而创建与特定类型无关的通用代码。要将何种约束如有应用到类型参数请参阅类型参数的约束。
4、实例化泛型类在实例化泛型类时需要为类型参数 T 指定具体的数据类型例如MyGenericClassint myObj new MyGenericClassint();这样就创建了一个具体类型为 int 的泛型类实例。
5、泛型类的优势
提高代码的复用性通过泛型类可以编写出可以适用于多种数据类型的通用代码提高代码的复用性。增强类型安全性泛型类可以在编译时捕获一些类型不匹配或错误使用增强代码的类型安全性。提高性能泛型类可以避免装箱和拆箱操作提高程序的性能。
6、标准.NET泛型类在.NET框架中有许多标准的泛型类如 ListT、DictionaryTKey, TValue 等它们提供了对泛型编程的丰富支持。 有关使用这些类的详细信息请参阅 .NET 中的泛型集合。
7、实现一个泛型接口还是多个泛型接口例如如果要设计用于在基于泛型的集合中创建项的类则可能必须实现一个接口例如 IComparableT其中 T 为类的类型。
总之泛型类是一种非常有用的工具可以帮助我们编写出更加通用、灵活和健壮的代码提高代码的复用性和可维护性。
以下是一个代码示例
using System;
using System.Collections;
using System.Collections.Generic;// 创建泛型队列类并实现泛型接口 IEnumerableT
public class QueueT : IEnumerableT
{private ListT items; // 使用 ListT 存储队列中的元素// 构造函数初始化队列public Queue(){items new ListT();}// 将元素添加到队列末尾public void Enqueue(T item){items.Add(item);}// 从队列头部移除并返回元素public T Dequeue(){if (items.Count 0){throw new InvalidOperationException(The queue is empty);}T item items[0];items.RemoveAt(0);return item;}// 实现 IEnumerableT 接口的 GetEnumerator 方法public IEnumeratorT GetEnumerator(){foreach (T item in items){yield return item;}}// 实现 IEnumerable 接口的 GetEnumerator 方法IEnumerator IEnumerable.GetEnumerator(){return GetEnumerator();}
}class Program
{static void Main(){Queueint intQueue new Queueint(); // 创建存储整数的队列intQueue.Enqueue(10); // 添加元素intQueue.Enqueue(20);// 使用 foreach 循环对队列中的元素进行迭代输出foreach (int item in intQueue){Console.WriteLine(item);}}
}在这个示例中我们创建了一个名为 Queue 的泛型类实现了泛型接口 IEnumerableT。在 Queue 类中我们使用了 ListT 来存储队列中的元素并提供了 Enqueue 和 Dequeue 方法来向队列中添加和移除元素。通过实现 IEnumerableT 接口我们使得 Queue 类可以被用于 foreach 循环从而对队列中的元素进行迭代输出。
在 Main 方法中我们创建了一个存储整数的队列 intQueue并向其中添加了两个整数。随后我们使用 foreach 循环对队列中的元素进行迭代输出。
通过这个示例我们展示了如何创建一个泛型类并实现一个泛型接口以提供迭代功能。这种方式可以使得我们的泛型类更加灵活和通用。
泛型方法
泛型方法是一种在方法中使用泛型类型参数的技术它允许我们编写能够处理多种类型数据的方法而不需要为每种类型都编写单独的方法。在C#中我们可以使用泛型方法来实现这一点。
以下是泛型方法的特点和用法
灵活性 泛型方法可以处理各种类型的数据例如整数、浮点数、字符串等而不需要针对每种类型编写单独的方法。代码重用 泛型方法提高了代码的重用性因为一个泛型方法可以适用于多种数据类型避免了重复编写类似的方法。类型安全 使用泛型方法可以在编译时进行类型检查确保方法在处理数据时符合类型约束。
下面是一个简单的示例演示了如何创建和使用泛型方法
using System;public class Program
{// 定义一个泛型方法 Swap用于交换两个变量的值public static void SwapT(ref T a, ref T b){T temp a;a b;b temp;}public static void Main(){int x 10, y 20;Console.WriteLine($Before swap: x {x}, y {y});// 调用泛型方法 Swap 来交换整数变量的值Swapint(ref x, ref y);Console.WriteLine($After swap: x {x}, y {y});string str1 Hello, str2 World;Console.WriteLine($Before swap: str1 {str1}, str2 {str2});// 调用泛型方法 Swap 来交换字符串变量的值Swapstring(ref str1, ref str2);Console.WriteLine($After swap: str1 {str1}, str2 {str2});}
}在上面的示例中我们定义了一个名为 Swap 的泛型方法。该方法使用了一个泛型类型参数 T该参数可以代表任意类型。在方法体内部我们可以像操作普通变量一样操作类型为 T 的变量。通过在方法名称后面加上尖括号和类型参数我们可以告诉编译器这是一个泛型方法并且在调用该方法时需要指定具体的类型。
在 Main 方法中我们展示了如何使用泛型方法 Swap 来交换整数和字符串变量的值。在调用泛型方法时我们需要在方法名后面的尖括号中指定具体的类型以告诉编译器我们要使用该方法处理哪种类型的数据。
通过泛型方法我们可以编写更加通用和灵活的代码而无需针对不同类型重复编写多个相似的方法。这提高了代码的重用性和可维护性。
泛型和数组
泛型和数组是C#中两个非常重要的概念它们可以结合在一起提供更强大和灵活的编程功能。
泛型
泛型是C#中的一种编程机制它允许我们编写能够处理各种类型数据的代码而不需要针对每种类型都编写单独的代码。通过泛型我们可以实现代码的重用和类型安全。
泛型的特点
灵活性 泛型允许我们编写能够处理各种类型数据的代码例如集合类、方法等。 类型安全 使用泛型可以在编译时进行类型检查确保代码在处理数据时符合类型约束。 代码重用 泛型提高了代码的重用性因为一个泛型类或方法可以适用于多种数据类型避免了重复编写类似的代码。
数组
数组是一种存储相同类型元素的连续内存空间的数据结构它是C#中最基本的数据结构之一。通过数组我们可以方便地存储和访问多个相同类型的元素。
数组的特点
连续存储 数组中的元素在内存中是连续存储的这使得访问数组中的元素非常高效。 固定长度 数组一旦创建后其长度通常是固定的不能动态改变。 下标访问 我们可以使用下标来访问数组中的元素下标从0开始计数。
泛型和数组的结合运用
在C#中我们可以使用泛型来创建数组从而实现存储不同类型数据的灵活性。例如我们可以使用泛型类 ListT 来代替传统的数组它可以存储任意类型的元素并且提供了丰富的操作方法。
以下是一个简单的示例演示了如何使用泛型类 ListT 来存储不同类型的元素
using System;
using System.Collections.Generic;public class Program
{public static void Main(){// 创建一个存储整数的 ListListint intList new Listint();intList.Add(10);intList.Add(20);// 创建一个存储字符串的 ListListstring stringList new Liststring();stringList.Add(Hello);stringList.Add(World);// 遍历并打印整数 List 中的元素Console.WriteLine(Integers:);foreach (int num in intList){Console.WriteLine(num);}// 遍历并打印字符串 List 中的元素Console.WriteLine(Strings:);foreach (string str in stringList){Console.WriteLine(str);}}
}在上面的示例中我们使用泛型类 ListT 分别创建了存储整数和字符串的列表并且成功存储和遍历了不同类型的元素。这展示了泛型和数组通过泛型类 ListT结合在一起的灵活性和强大功能。
泛型委托
泛型委托是C#中的一种高级特性它结合了泛型和委托的功能使得我们可以定义能够处理不同类型参数的委托类型。泛型委托为我们提供了更灵活、通用的委托类型可以在编写泛型方法或类时发挥重要作用。
委托
首先让我们先来了解一下委托的概念。委托是一种类型安全的函数指针它允许我们将方法作为参数传递、存储方法的引用并且可以实现回调等功能。在C#中委托使用 delegate 关键字进行定义。
泛型委托
泛型委托是指具有泛型参数的委托类型。通过使用泛型委托我们可以定义一个委托类型该委托可以接受不同类型的参数并返回不同类型的结果。这使得我们可以编写通用的委托类型而不需要为每种参数类型都定义一个单独的委托类型。
以下是一个简单的示例演示了如何定义和使用泛型委托
using System;// 定义一个泛型委托类型
public delegate T CalculatorT(T x, T y);public class Program
{// 泛型方法接受泛型委托作为参数public static void PerformCalculationT(T a, T b, CalculatorT calculator){T result calculator(a, b);Console.WriteLine($Result: {result});}public static void Main(){// 使用泛型委托进行整数加法计算PerformCalculation(10, 20, (x, y) x y);// 使用泛型委托进行字符串连接PerformCalculation(Hello, , World!, (x, y) x y);}
}在上面的示例中我们首先定义了一个泛型委托类型 CalculatorT它接受两个类型为 T 的参数并返回类型为 T 的结果。然后在 PerformCalculation 方法中我们接受了一个泛型委托作为参数并在方法内部使用该委托进行计算。在 Main 方法中我们展示了如何使用泛型委托进行整数加法计算和字符串连接操作。
通过泛型委托我们可以编写更加通用和灵活的代码能够处理不同类型参数的计算或处理逻辑。这提高了代码的重用性和可扩展性并使得我们的代码更具有通用性。
C 模板和 C# 泛型之间的区别
C# 泛型和 C 模板均是支持参数化类型的语言功能。 但是两者之间存在很多不同。 在语法层次C# 泛型是参数化类型的一个更简单的方法而不具有 C 模板的复杂性。 此外C# 不试图提供 C 模板所具有的所有功能。 在实现层次主要区别在于 C# 泛型类型的替换在运行时执行从而为实例化对象保留了泛型类型信息。有关详细信息请参阅运行时中的泛型。
以下是 C# 泛型和 C 模板之间的主要差异
C# 泛型的灵活性与 C 模板不同。 例如虽然可以调用 C# 泛型类中的用户定义的运算符但是无法调用算术运算符。C# 不允许使用非类型模板参数如 template Cint i {}。C# 不支持显式定制化即特定类型模板的自定义实现。C# 不支持部分定制化部分类型参数的自定义实现。C# 不允许将类型参数用作泛型类型的基类。C# 不允许类型参数具有默认类型。在 C# 中泛型类型参数本身不能是泛型但是构造类型可以用作泛型。 C 允许使用模板参数。C 允许在模板中使用可能并非对所有类型参数有效的代码随后针对用作类型参数的特定类型检查此代码。 C# 要求类中编写的代码可处理满足约束的任何类型。 例如在 C 中可以编写一个函数此函数对类型参数的对象使用算术运算符 和 -在实例化具有不支持这些运算符的类型的模板时此函数将产生错误。 C# 不允许此操作唯一允许的语言构造是可以从约束中推断出来的构造。