当前位置: 首页 > news >正文

学习c#的第二十一天

目录

C# 泛型(Generic)

泛型类型参数

类型参数的约束

约束多个参数

未绑定的类型参数

类型参数作为约束

notnull 约束

class 约束

default 约束

非托管约束

委托约束

枚举约束

类型参数实现声明的接口

泛型类

泛型方法

泛型和数组

泛型

数组

泛型和数组的结合运用

泛型委托

委托

泛型委托

C++ 模板和 C# 泛型之间的区别


C# 泛型(Generic)

泛型类型参数

在泛型类型或方法定义中,类型参数充当了一个占位符,用于在创建泛型类型的实例时由客户端指定特定的类型。 泛型类(例如泛型介绍中列出的 GenericList<T>)本身并不是一个具体的类型,而更像是类型的模板或蓝图。要使用 GenericList<T>,客户端代码必须在尖括号内指定类型参数,以声明并实例化特定的构造类型。这个类型参数可以是编译器能够识别的任何类型。通过这种方式,可以创建任意数量的泛型类型实例,每个实例都使用不同的类型参数。

举个例子,假设有一个泛型类 GenericList<T>,客户端可以这样使用它来创建不同类型的实例:

GenericList<int> intList = new GenericList<int>();
GenericList<string> stringList = new GenericList<string>();
GenericList<Customer> customerList = new GenericList<Customer>();

在这个例子中,我们使用了三种不同的类型参数(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 不支持的任何方法,则将对类型参数应用约束。 例如,基类约束告诉编译器,仅此类型的对象或派生自此类型的对象可用作类型参数。 编译器有了此保证后,就能够允许在泛型类中调用该类型的方法。 以下代码示例演示可通过应用基类约束添加到(泛型介绍中的)GenericList<T> 类的功能。

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 GenericList<T> 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 IEnumerator<T> 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 EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{// ...
}

在应用 where T : class 约束时,请避免对类型参数使用 == 和 != 运算符,因为这些运算符仅测试引用标识而不测试值相等性。 即使在用作参数的类型中重载这些运算符也会发生此行为。 下面的代码说明了这一点;即使 String 类重载 == 运算符,输出也为 false。

public static void OpEqualsTest<T>(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();OpEqualsTest<string>(s1, s2);
}

编译器只知道 T 在编译时是引用类型,并且必须使用对所有引用类型都有效的默认运算符。 如果必须测试值相等性,建议同时应用 where T : IEquatable<T> 或 where T : IComparable<T> 约束,并在用于构造泛型类的任何类中实现该接口。

约束多个参数

可以对多个参数应用多个约束,对一个参数应用多个约束,如下例所示:

class Base { }
class Test<T, U>where U : structwhere T : Base, new()
{ }

未绑定的类型参数

没有约束的类型参数(如公共类 SampleClass<T>{} 中的 T)称为未绑定的类型参数。 未绑定的类型参数具有以下规则:

  • 不能使用 != 和 == 运算符,因为无法保证具体的类型参数能支持这些运算符。
  • 可以在它们与 System.Object 之间来回转换,或将它们显式转换为任何接口类型。
  • 可以将它们与 null 进行比较。 将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较将始终返回 false。

类型参数作为约束

在具有自己类型参数的成员函数必须将该参数约束为包含类型的类型参数时,将泛型类型参数用作约束非常有用,如下例所示:

public class List<T>
{public void Add<U>(List<U> items) where U : T {/*...*/}
}

在上述示例中,T 在 Add 方法的上下文中是一个类型约束,而在 List 类的上下文中是一个未绑定的类型参数。

类型参数还可在泛型类定义中用作约束。 必须在尖括号中声明该类型参数以及任何其他类型参数:

//Type parameter V is used as a type constraint.
public class SampleClass<T, 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是 Nullable<T>的实例,则 T? 与 T 相同。 换句话说,它不会成为 T??。

由于现在可在没有 class 或 struct 约束的情况下使用 T?,因此在重写或显式接口实现中可能会出现歧义。 在这两种情况下,重写不包含约束,但从基类继承。 当基类不应用 class 或 struct 约束时,派生类需要通过某种方式在不使用任一种约束的情况下指定应用于基方法的重写。 此时派生方法将应用 default 约束。 default 约束不阐明 class 和 struct 约束。

非托管约束

可使用 unmanaged 约束来指定类型参数必须是不可为 null 的非托管类型。通过 unmanaged 约束,用户能编写可重用例程,从而使用可作为内存块操作的类型,如以下示例所示:

unsafe public static byte[] ToByteArray<T>(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? TypeSafeCombine<TDelegate>(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!();Func<bool> 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 Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{var result = new Dictionary<int, 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 = EnumNamedValues<Rainbow>();foreach (var pair in map)Console.WriteLine($"{pair.Key}:\t{pair.Value}");

类型参数实现声明的接口

某些场景要求为类型参数提供的参数实现该接口。 例如:

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{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 IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{public abstract static IAdditionSubtraction<T> operator +(IAdditionSubtraction<T> left,IAdditionSubtraction<T> right);public abstract static IAdditionSubtraction<T> operator -(IAdditionSubtraction<T> left,IAdditionSubtraction<T> right);
}

上述语法要求实现者对这些方法使用显式接口实现。 提供额外的约束使接口能够根据类型参数来定义运算符。 实现接口的类型可以隐式实现接口方法。

泛型类

泛型类是一种具有泛型类型参数的类,它可以在定义时不指定具体的数据类型,而在实例化时再指定具体的数据类型。使用泛型类可以编写出更加通用和灵活的代码,以适应各种不同类型的数据。

1、泛型类的定义:泛型类的定义与普通类类似,只是在类名后面使用尖括号加上类型参数,例如:public class MyGenericClass<T> { /*...*/ }。

2、类型参数 T:类型参数 T 是一个占位符,它代表着实际的数据类型,在实例化时将会被替换为具体的类型。可以有多个类型参数,用逗号分隔。

3、使用类型参数:在泛型类的定义中,可以在类的字段、属性、方法等地方使用类型参数 T,从而创建与特定类型无关的通用代码。要将何种约束(如有)应用到类型参数(请参阅类型参数的约束)。

4、实例化泛型类:在实例化泛型类时,需要为类型参数 T 指定具体的数据类型,例如:MyGenericClass<int> myObj = new MyGenericClass<int>();,这样就创建了一个具体类型为 int 的泛型类实例。

5、泛型类的优势:

  • 提高代码的复用性:通过泛型类,可以编写出可以适用于多种数据类型的通用代码,提高代码的复用性。
  • 增强类型安全性:泛型类可以在编译时捕获一些类型不匹配或错误使用,增强代码的类型安全性。
  • 提高性能:泛型类可以避免装箱和拆箱操作,提高程序的性能。

6、标准.NET泛型类:在.NET框架中,有许多标准的泛型类,如 List<T>、Dictionary<TKey, TValue> 等,它们提供了对泛型编程的丰富支持。 有关使用这些类的详细信息,请参阅 .NET 中的泛型集合。

7、实现一个泛型接口还是多个泛型接口:例如,如果要设计用于在基于泛型的集合中创建项的类,则可能必须实现一个接口,例如 IComparable<T>,其中 T 为类的类型。

总之,泛型类是一种非常有用的工具,可以帮助我们编写出更加通用、灵活和健壮的代码,提高代码的复用性和可维护性。

以下是一个代码示例:

using System;
using System.Collections;
using System.Collections.Generic;// 创建泛型队列类并实现泛型接口 IEnumerable<T>
public class Queue<T> : IEnumerable<T>
{private List<T> items; // 使用 List<T> 存储队列中的元素// 构造函数,初始化队列public Queue(){items = new List<T>();}// 将元素添加到队列末尾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;}// 实现 IEnumerable<T> 接口的 GetEnumerator 方法public IEnumerator<T> GetEnumerator(){foreach (T item in items){yield return item;}}// 实现 IEnumerable 接口的 GetEnumerator 方法IEnumerator IEnumerable.GetEnumerator(){return GetEnumerator();}
}class Program
{static void Main(){Queue<int> intQueue = new Queue<int>(); // 创建存储整数的队列intQueue.Enqueue(10); // 添加元素intQueue.Enqueue(20);// 使用 foreach 循环对队列中的元素进行迭代输出foreach (int item in intQueue){Console.WriteLine(item);}}
}

在这个示例中,我们创建了一个名为 Queue 的泛型类,实现了泛型接口 IEnumerable<T>。在 Queue 类中,我们使用了 List<T> 来存储队列中的元素,并提供了 Enqueue 和 Dequeue 方法来向队列中添加和移除元素。通过实现 IEnumerable<T> 接口,我们使得 Queue 类可以被用于 foreach 循环,从而对队列中的元素进行迭代输出。

在 Main 方法中,我们创建了一个存储整数的队列 intQueue,并向其中添加了两个整数。随后,我们使用 foreach 循环对队列中的元素进行迭代输出。

通过这个示例,我们展示了如何创建一个泛型类,并实现一个泛型接口以提供迭代功能。这种方式可以使得我们的泛型类更加灵活和通用。

泛型方法

泛型方法是一种在方法中使用泛型类型参数的技术,它允许我们编写能够处理多种类型数据的方法,而不需要为每种类型都编写单独的方法。在C#中,我们可以使用泛型方法来实现这一点。

以下是泛型方法的特点和用法:

  1. 灵活性: 泛型方法可以处理各种类型的数据,例如整数、浮点数、字符串等,而不需要针对每种类型编写单独的方法。
  2. 代码重用: 泛型方法提高了代码的重用性,因为一个泛型方法可以适用于多种数据类型,避免了重复编写类似的方法。
  3. 类型安全: 使用泛型方法可以在编译时进行类型检查,确保方法在处理数据时符合类型约束。

下面是一个简单的示例,演示了如何创建和使用泛型方法:

using System;public class Program
{// 定义一个泛型方法 Swap,用于交换两个变量的值public static void Swap<T>(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 来交换整数变量的值Swap<int>(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 来交换字符串变量的值Swap<string>(ref str1, ref str2);Console.WriteLine($"After swap: str1 = {str1}, str2 = {str2}");}
}

在上面的示例中,我们定义了一个名为 Swap 的泛型方法。该方法使用了一个泛型类型参数 T,该参数可以代表任意类型。在方法体内部,我们可以像操作普通变量一样操作类型为 T 的变量。通过在方法名称后面加上尖括号和类型参数,我们可以告诉编译器这是一个泛型方法,并且在调用该方法时需要指定具体的类型。

在 Main 方法中,我们展示了如何使用泛型方法 Swap 来交换整数和字符串变量的值。在调用泛型方法时,我们需要在方法名后面的尖括号中指定具体的类型,以告诉编译器我们要使用该方法处理哪种类型的数据。

通过泛型方法,我们可以编写更加通用和灵活的代码,而无需针对不同类型重复编写多个相似的方法。这提高了代码的重用性和可维护性。

泛型和数组

泛型和数组是C#中两个非常重要的概念,它们可以结合在一起提供更强大和灵活的编程功能。

泛型

泛型是C#中的一种编程机制,它允许我们编写能够处理各种类型数据的代码,而不需要针对每种类型都编写单独的代码。通过泛型,我们可以实现代码的重用和类型安全。

泛型的特点:

灵活性: 泛型允许我们编写能够处理各种类型数据的代码,例如集合类、方法等。
类型安全: 使用泛型可以在编译时进行类型检查,确保代码在处理数据时符合类型约束。
代码重用: 泛型提高了代码的重用性,因为一个泛型类或方法可以适用于多种数据类型,避免了重复编写类似的代码。

数组

数组是一种存储相同类型元素的连续内存空间的数据结构,它是C#中最基本的数据结构之一。通过数组,我们可以方便地存储和访问多个相同类型的元素。

数组的特点:

连续存储: 数组中的元素在内存中是连续存储的,这使得访问数组中的元素非常高效。
固定长度: 数组一旦创建后,其长度通常是固定的,不能动态改变。
下标访问: 我们可以使用下标来访问数组中的元素,下标从0开始计数。

泛型和数组的结合运用

在C#中,我们可以使用泛型来创建数组,从而实现存储不同类型数据的灵活性。例如,我们可以使用泛型类 List<T> 来代替传统的数组,它可以存储任意类型的元素,并且提供了丰富的操作方法。

以下是一个简单的示例,演示了如何使用泛型类 List<T> 来存储不同类型的元素:

using System;
using System.Collections.Generic;public class Program
{public static void Main(){// 创建一个存储整数的 ListList<int> intList = new List<int>();intList.Add(10);intList.Add(20);// 创建一个存储字符串的 ListList<string> stringList = new List<string>();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);}}
}

在上面的示例中,我们使用泛型类 List<T> 分别创建了存储整数和字符串的列表,并且成功存储和遍历了不同类型的元素。这展示了泛型和数组(通过泛型类 List<T>)结合在一起的灵活性和强大功能。

泛型委托

泛型委托是C#中的一种高级特性,它结合了泛型和委托的功能,使得我们可以定义能够处理不同类型参数的委托类型。泛型委托为我们提供了更灵活、通用的委托类型,可以在编写泛型方法或类时发挥重要作用。

委托

首先,让我们先来了解一下委托的概念。委托是一种类型安全的函数指针,它允许我们将方法作为参数传递、存储方法的引用,并且可以实现回调等功能。在C#中,委托使用 delegate 关键字进行定义。

泛型委托

泛型委托是指具有泛型参数的委托类型。通过使用泛型委托,我们可以定义一个委托类型,该委托可以接受不同类型的参数,并返回不同类型的结果。这使得我们可以编写通用的委托类型,而不需要为每种参数类型都定义一个单独的委托类型。

以下是一个简单的示例,演示了如何定义和使用泛型委托:

using System;// 定义一个泛型委托类型
public delegate T Calculator<T>(T x, T y);public class Program
{// 泛型方法,接受泛型委托作为参数public static void PerformCalculation<T>(T a, T b, Calculator<T> 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);}
}

在上面的示例中,我们首先定义了一个泛型委托类型 Calculator<T>,它接受两个类型为 T 的参数,并返回类型为 T 的结果。然后,在 PerformCalculation 方法中,我们接受了一个泛型委托作为参数,并在方法内部使用该委托进行计算。在 Main 方法中,我们展示了如何使用泛型委托进行整数加法计算和字符串连接操作。

通过泛型委托,我们可以编写更加通用和灵活的代码,能够处理不同类型参数的计算或处理逻辑。这提高了代码的重用性和可扩展性,并使得我们的代码更具有通用性。

C++ 模板和 C# 泛型之间的区别

C# 泛型和 C++ 模板均是支持参数化类型的语言功能。 但是,两者之间存在很多不同。 在语法层次,C# 泛型是参数化类型的一个更简单的方法,而不具有 C++ 模板的复杂性。 此外,C# 不试图提供 C++ 模板所具有的所有功能。 在实现层次,主要区别在于 C# 泛型类型的替换在运行时执行,从而为实例化对象保留了泛型类型信息。有关详细信息,请参阅运行时中的泛型。

以下是 C# 泛型和 C++ 模板之间的主要差异:

  • C# 泛型的灵活性与 C++ 模板不同。 例如,虽然可以调用 C# 泛型类中的用户定义的运算符,但是无法调用算术运算符。
  • C# 不允许使用非类型模板参数,如 template C<int i> {}。
  • C# 不支持显式定制化;即特定类型模板的自定义实现。
  • C# 不支持部分定制化:部分类型参数的自定义实现。
  • C# 不允许将类型参数用作泛型类型的基类。
  • C# 不允许类型参数具有默认类型。
  • 在 C# 中,泛型类型参数本身不能是泛型,但是构造类型可以用作泛型。 C++ 允许使用模板参数。
  • C++ 允许在模板中使用可能并非对所有类型参数有效的代码,随后针对用作类型参数的特定类型检查此代码。 C# 要求类中编写的代码可处理满足约束的任何类型。 例如,在 C++ 中可以编写一个函数,此函数对类型参数的对象使用算术运算符 + 和 -,在实例化具有不支持这些运算符的类型的模板时,此函数将产生错误。 C# 不允许此操作;唯一允许的语言构造是可以从约束中推断出来的构造。

相关文章:

学习c#的第二十一天

目录 C# 泛型&#xff08;Generic&#xff09; 泛型类型参数 类型参数的约束 约束多个参数 未绑定的类型参数 类型参数作为约束 notnull 约束 class 约束 default 约束 非托管约束 委托约束 枚举约束 类型参数实现声明的接口 泛型类 泛型方法 泛型和数组 泛型…...

Michael Jordan最新报告:去中心化机器学习中的契约、不确定性和激励

‍ ‍导读 11月3日&#xff0c;智源研究院学术顾问委员会委员、机器学习泰斗Michael Jordan在以“新一代人工智能前沿”为主题的2023北京论坛 新工科专题论坛上&#xff0c;发表了题为Contracts, Uncertainty, and Incentives in Decentralized Machine Learning&#xff08;去…...

3ds Max渲染用专业显卡还是游戏显卡?

使用3dsmax建模时&#xff0c;会面临诸多选择&#xff0c;除了用vr还是cr的决策&#xff0c;硬件选择上也存在着疑问&#xff0c;比如用专业显卡还是消费级游戏显卡&#xff1f;一般来说&#xff0c;除非是特别专业的大型项目和软件&#xff0c;且预算在5位数以上&#xff0c;常…...

airlearning-ue4安装的踩坑记录

最近要安装airlearning-ue4&#xff0c;用于实现无人机仿真环境&#xff0c;该项目地址为&#xff1a;GitHub - harvard-edge/airlearning-ue4: Environment Generator for Air Learning Project. This version is build on top of UE4 game engine 由于这个项目已经完成好几年…...

uniapp优化h5项目-摇树优化,gzip压缩和删除console.log

1.摇树优化 勾选摇树优化,打包删除死代码 2.gzip压缩和删除console.log 安装插件webpack和compression-webpack-plugin webpack插件 npm install webpack4.46.0 --save-devcompression-webpack-plugin插件 npm install compression-webpack-plugin6.1.1 --save-devconst Com…...

Pycharm之配置python虚拟环境

最近给身边的人写了脚本&#xff0c;在自己电脑可以正常运行。分享给我身边的人&#xff0c;却运行不起来&#xff0c;然后把报错的截图给我看了&#xff0c;所以难道不会利用pycharm搭建虚拟的环境&#xff1f;记录一下配置的过程。 第一步&#xff1a;右键要打开的python的代…...

如何使用MybatisPlus进行数据分页显示

如何使用MybatisPlus进行数据的分页呢&#xff1f; 使用Mybatis Plus提供的分页插件来简化开发&#xff0c;在MybatisPlusInterceptor的拦截器中添加自动分页的PaginationInnerInterceptor拦截器&#xff0c;当前配置需要交给spring的bean管理&#xff0c;类上添加注解Configu…...

代码随想录 Day49 单调栈01 LeetCode LeetCodeT739每日温度 T496 下一个最大元素I

前言 折磨的死去活来的动态规划终于结束啦,今天秋秋给大家带来两题非常经典的单调栈问题,可能你不清楚单调栈是什么,可以用来解决什么问题,今天我们就来一步一步的逐渐了解单调栈,到能够灵活使用单调栈.注意以下讲解中&#xff0c;顺序的描述为 从栈头到栈底的顺序 什么时候用单…...

高可用--限流熔断降级

熔断 熔断是应对微服务雪崩效应的一种链路保护机制。 场景 服务端出现问题 服务指标&#xff1a;响应时间、错误率、连续错误数等&#xff0c;超过阈值出发熔断。硬件指标&#xff1a;CPU、网络IO、内存 目的 服务端恢复需要时间、服务端需要休息避免全调用链路崩溃&…...

win10电脑无法联网,设置IPv4,点击属性无法打开,闪退

win10设置IPv4&#xff0c;点击属性无法打开&#xff0c;闪退 问题:win10设置IPv4&#xff0c;点击属性无法打开&#xff0c;闪退 问题:win10设置IPv4&#xff0c;点击属性无法打开&#xff0c;闪退 第1步&#xff1a;用管理员打开cmd命令窗口&#xff0c;然后输入下面的命令&…...

【数据结构】邻接表与邻接矩阵的转换

一.基本思想 1.邻接矩阵转换为邻接表&#xff1a; 先设置一个空的邻接表&#xff0c;然后查找邻接矩阵的值不为零元素&#xff0c;找到后在邻接表的单链表对应位置加入表边节点。 2.邻接表转换为邻接矩阵&#xff1a; 在邻接表上顺序取出每个表边结点&#xff0c;将邻接矩阵…...

VR智慧景区:VR赋能文旅产业,激活消费潜能

随着国家数字化战略的不断深入实施&#xff0c;文旅产业数字化转型的步伐也在逐渐加快&#xff0c;以VR技术赋能文旅产业&#xff0c;让文旅景区线上线下双渠道融合&#xff0c;进一步呈现文化底蕴、激活消费潜能。 VR智慧景区以沉浸式、互动式、科技感的方式&#xff0c;将景区…...

Spring Boot EasyPOI 使用指定模板导出Excel

相信大家都遇到过&#xff0c;用户提出要把界面上的数据导成一个Excel&#xff0c;还得是用户指定的Excel格式&#xff0c;用原生的POI&#xff0c;需要自己去实现&#xff0c;相信是比较麻烦的&#xff0c;所以我们可以使用开源的EasyPOI. 先上个图&#xff0c;看看是不是大家…...

postgresql:记录表膨胀引起的io问题的处理

文章目录 1. io异常2.查看profile报告2.1 生成事发时间段的pgprofile2.2 查看报告 3.检查table是否膨胀4.执行vacuum full5.总结 1. io异常 iostat -x 1 20 Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq…...

Windows下安装RabbitMQ

1.安装Erlang 因为RabbitMQ是用Erlang语言编写的&#xff0c;所以在安装RabbitMQ之前需要先安装Erlang。 如果还未安装Erlang&#xff0c;官方下载安装包&#xff0c;点击Download Windows installer下载Erlang Downloads - Erlang/OTP 下载Erlang/OTP后&#xff0c;双击otp的…...

广州华锐互动VRAR:利用VR开展刑事案件公安取证培训,沉浸式体验提升实战能力

随着科技的飞速发展&#xff0c;虚拟现实(VR)技术为我们的生活和工作带来了前所未有的便利。近年来&#xff0c;VR技术在刑事案件公安取证培训中的应用逐渐显现出其独特优势。通过模拟真实的犯罪现场&#xff0c;VR技术为学员提供了沉浸式的体验&#xff0c;使他们在安全的环境…...

消息消费过程

前言 本文介绍下Kafka消费过程, 内容涉及消费与消费组, 主题与分区, 位移提交&#xff0c;分区再平衡和消费者拦截器等内容。 消费者与消费组 Kafka将消费者组织为消费组, 消息只会被投递给消费组中的1个消费者。因此, 从不同消费组中的消费者来看, Kafka是多播(Pub/Sub)模式…...

使用Lychee搭建个人图片存储系统并进行远程访问设置实现公网访问本地私人图床

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…...

12-2- DCGAN -简单网络-卷积网络

功能 随机噪声→生成器→MINIST图像。 训练方法 0 损失函数:gan的优化目标是一个对抗损失,是二分类问题,用BCELoss 1 判别器的训练,首先固定生成器参数不变,其次判别器应当将真实图像判别为1,生成图像判别为0 loss=loss(real_out, 1)+loss(fake_out, 0) 2 生成器的…...

Redis持久化策略之RDB与AOF

文章目录 1.RDB1)基本介绍2)自动触发3)手动触发4)RDB文件5)优点缺点 2.AOF1)基本介绍2)使用方式3)工作流程4)重写机制5)AOF文件6)优点缺点 3.RDB AOF 我们都知道&#xff0c;redis 是一个基于内存的数据库。基于内存的好处是访问速度快&#xff0c;缺点是“不持久”——当数据…...

Python学习笔记--初识 Python 正则表达式

初识 Python 正则表达式 正则表达式是一个特殊的字符序列,用于判断一个字符串是否与我们所设定的字符序列是否匹配,也就是说检查一个字符串是否与某种模式匹配。 Python 自 1.5 版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。re 模块使 Python 语言拥有全部的正…...

webAPP基础学习

###视觉基础 part-I ####1.面试中常见的像素问题 >什么是像素? *1.什么是px? px-虚拟像素,css像素的单位 px是一个相对单位,相对于设备像素而言 >相对性 a.相对于同一个设备,css像素的可变的 css像素物理像素>会受到缩放的影响 css像素缩放倍数*单个物理像…...

RIP路由信息协议

RIP路由信息协议(Routing Information Protocol) 最先得到广泛应用的协议&#xff0c;最大优点是简单要求网络中的每个路由器都要维护一张表&#xff0c;表中记录了从它自己到其他每一个目的网络的距离RIP是应用层协议&#xff0c;它在传输层使用UDP&#xff0c;RIP报文作为UD…...

kubernetes 高可用集群

目录 一、haproxy负载均衡 二、pacemaker高可用 三、部署control-plane 四、部署worker node 实验环境 主机名 IP 角色 docker 192.168.67.10 harbor k8s1 192.168.67.11 control-plane k8s2 192.168.67.12 control-plane k8s3 192.168.67.13 control-plane k8s…...

java实现插入排序

图解 以下是Java实现插入排序的代码&#xff1a; public class InsertionSort {public static void main(String[] args) {int[] arr {5, 2, 4, 6, 1, 3};insertionSort(arr);System.out.println(Arrays.toString(arr)); // output: [1, 2, 3, 4, 5, 6]}public static void i…...

深度学习之基于YoloV5血红细胞检测识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习已经在许多领域中得到了广泛的应用&#xff0c;包括医疗健康领域。其中&#xff0c;YOLO&#xff08;You O…...

8、可视化高斯滤波器并完成高斯滤波

本节一起绘制一个可视化的高斯滤波器,同时对一个彩色图像增加高斯噪声,最后通过一个高斯滤波器对图像进行降噪处理。 就像上节说的那样,滤波不是学习重点,下面通过实操了解下原理即可。 可视化高斯滤波器 高斯滤波器符合高斯分布,并且是二维高斯分布,这一点在上一节高斯…...

Linux MMC子系统 - 5.eMMC 5.1工作模式-引导模式

By: Ailson Jack Date: 2023.11.19 个人博客&#xff1a;http://www.only2fire.com/ 本文在我博客的地址是&#xff1a;http://www.only2fire.com/archives/164.html&#xff0c;排版更好&#xff0c;便于学习&#xff0c;也可以去我博客逛逛&#xff0c;兴许有你想要的内容呢。…...

2342. 数位和相等数对的最大和 --力扣 --JAVA

题目 给你一个下标从 0 开始的数组 nums &#xff0c;数组中的元素都是 正 整数。请你选出两个下标 i 和 j&#xff08;i ! j&#xff09;&#xff0c;且 nums[i] 的数位和 与 nums[j] 的数位和相等。 请你找出所有满足条件的下标 i 和 j &#xff0c;找出并返回 nums[i] num…...

linux如何重置root密码

目录 当我们想要重置root管理员密码时&#xff0c;我们可以有两种方法进行&#xff1a; 方法一、init方法 1、重启系统&#xff0c;在下图所示界面按e键 2、随后进入以下界面&#xff0c;、将ro修改为rw&#xff0c;在行末尾添加init/bin/sh。​编辑 3、随后按Ctrlx启动到s…...

Java 类之 java.util.Properties

Java 类之 java.util.Properties 文章目录 Java 类之 java.util.Properties一、简介二、主要功能1、存储键值对2、读取文件与属性代码示例运行结果截图 3、设置属性并保存文件代码示例结果截图 4、遍历属性代码示例运行结果 关联博客&#xff1a;《基于 Java 列举和说明常用的外…...

我遇到的bug(活动)

目录 方向一&#xff1a;身为程序员遇到过的奔溃瞬间 方向二&#xff1a;如何解决遇到的奔溃瞬间 方向三&#xff1a;在解决完后获得的收获和体会 方向一&#xff1a;身为程序员遇到过的奔溃瞬间 在一个项目中&#xff0c;我负责实现一个复杂的图像处理算法。经过几天的努力…...

MIB 6.1810实验Xv6 and Unix utilities(3)pingpong

Mit6.S081-实验1-Xv6 and Unix utilities-pingpong问题_Isana_Yashiro的博客-CSDN博客 Write a user-level program that uses xv6 system calls to ping-pong a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to…...

压力测试总共需要几个步骤?思路总结篇

在运维工作中&#xff0c;压力测试是一项很重要的工作。比如在一个网站上线之前&#xff0c;能承受多大访问量、在大访问量情况下性能怎样&#xff0c;这些数据指标好坏将会直接影响用户体验。今天我们就来深入了解下压力测试&#xff01; 1、首先&#xff0c;什么是压力测试&…...

03_面向对象高级_多态

多态 1. 什么是多态&#xff1f; “多态” 是在 “继承” 的基础上实现的一种现象&#xff0c;具体表现为&#xff1a;对象多态、行为多态。 public class HelloWorld {public static void main(String[] args) {// 1. 对象多态Human h1 new Student();Human h2 new Teach…...

【Kingbase FlySync】界面化管控平台:2.配置数据库同步之KES>KES

【Kingbase FlySync】界面化管控平台:3.配置数据库同步之KES->KES 部署KES数据库到KES数据库同步服务1.登录KFS管理平台2.开始配置数据节点信息(1)配置node1数据节点(2)配置node2数据节点 3.KFS拓扑图配置4.开始部署5.启动同步程序并查验是否运行正常 测试同步1.从node1数据…...

企业计算机服务器中了mallox勒索病毒怎么解决,勒索病毒解密文件恢复

随着科技技术的不断发展&#xff0c;网络技术得到了快速提升&#xff0c;但网络安全威胁也不断增加&#xff0c;近期&#xff0c;云天数据恢复中心陆续接到很多企业的求助信息&#xff0c;企业的计算机服务器遭到了mallox勒索病毒攻击&#xff0c;导致企业的所有业务中断&#…...

Sonar生成PDF错误Can‘t get Compute Engine task status.Retry..... HTTP error: 401

报错及修改&#xff1a; 报错&#xff1a;INFO: Can’t get Compute Engine task status.Retry… org.sonarqube.ws.connectors.ConnectionException: HTTP error: 401, msg: , query: org.apache.commons.httpclient.methods.GetMethod7a021f49 ERROR: Problem generating PD…...

storage和正则表达式

一、Storage 1.认识Storage WebStorage主要提供了一种机制&#xff0c;可以让浏览器提供一种比cookie更直观的key、value存储方式&#xff1a; localStorage&#xff1a;本地存储&#xff0c;提供的是一种永久性的存储方法&#xff0c;在关闭掉网页重新打开时&#xff0c;存…...

【数据结构】图的广度优先遍历

一.广度优先遍历的基本思想 &#xff08;1&#xff09;访问顶点v&#xff1b; &#xff08;2&#xff09;依次访问v的各个未被访问的邻接点v1&#xff0c;v2&#xff0c;v3……&#xff0c;vk&#xff1b; &#xff08;3&#xff09;分别从v1&#xff0c;v2&#xff0c;v3……...

AM@函数展开成幂级数@间接法@常用麦克劳林幂级数展开公式

文章目录 间接法推导幂级数展开常用麦克劳林幂级数展开公式应用例例例 间接法推导幂级数展开 已知函数的幂级数展开公式间接推导其他函数幂级数 使用原始的推导公式推导函数的幂级数展开是繁琐不便的,需要分别计算各项系数 a n f ( n ) ( 0 ) n ! a_{n}\frac{f^{(n)}(0)}{n!}…...

LeetCode994.腐烂的橘子

看完题我觉得这不是和上一道岛屿的题一样简单嘛&#xff0c;然后写了将近2个小时才写出来&#xff0c;我的思路就是&#xff0c;用check()先对grid检查一下&#xff0c;是否有以下情况&#xff1a; &#xff08;如果有1的周围都是空&#xff0c;则这个位置用不腐烂&#xff0c;…...

【开源】基于Vue和SpringBoot的康复中心管理系统

项目编号&#xff1a; S 056 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S056&#xff0c;文末获取源码。} 项目编号&#xff1a;S056&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员…...

【音视频基础】AVI文件格式

AVI文件采用的是RIFF文件结构方式。波形音频wave&#xff0c;MIDI和数字视频AVI都采用这种格式存储。 AVI文件的整体结构如下图所示 构造RIFF文件的基本单元叫做数据块&#xff08;Chunk&#xff09;&#xff0c;每个数据块包含3个部分 4字节的数据块标记&#xff08;或者叫…...

图书馆整理I(从尾到头打印列表),剑指offer,力扣

目录 题目地址&#xff1a; 我们直接看题解吧&#xff1a; 解题方法&#xff1a; 难度分析&#xff1a; 审题目事例提示&#xff1a; 解题思路(辅助栈)&#xff1a; 代码&#xff08;递归&#xff09;&#xff1a; 代码&#xff08;列表插入&#xff09;&#xff1a; 相似题目对…...

C++编写的多线程自动爬虫程序

目录 引言 一、程序的设计 二、程序的实现 三、程序的测试 四、优化与改进 五、代码示例 总结 引言 随着互联网的快速发展&#xff0c;网络爬虫程序已经成为数据采集、信息处理的重要工具。C作为一种高效的编程语言&#xff0c;具有高效的并发处理能力和丰富的网络编程…...

SMB信息泄露的利用

一、背景 今天分享SMB信息泄露&#xff0c;SMB&#xff08;Server Message Block&#xff09;网络通信协议&#xff0c;早些时候被用于Web链接和客户端与服务器之间的信息通信&#xff0c;现在大部分Web页面使用HTTP协议&#xff0c;在web领域应用较少。另一方面SMB协议还是被…...

QT自定义信号,信号emit,信号参数注册

qt如何自定义信号 使用signals声明返回值是void在需要发送信号的地方使用 emit 信号名字(参数)进行发送 在需要链接的地方使用connect进行链接 ct进行链接...

06.webpack性能优化--构建速度

优化babel-loaderhappyPackIgnorePluginparalleUglifyPluginnoParse自动刷新 1 happypack多进程打包 js单线程&#xff0c;开启多进程打包提高构建速度&#xff08;特别是多核CPU&#xff09; const HappyPack require(happypack)module.exports smart(webpackCommonConf,…...

11-15 周三 softmax 回归学习

11-15 周三 softmax 回归学习 时间版本修改人描述2023年11月15日11:17:27V0.1宋全恒新建文档 简介 softmax分享可以参考什么是softmax 回归估计一个连续值&#xff0c;分类预测一个离散类别。 恶意软件的判断 回归和分类 分类可以认为从回归的单输出变成多输出 B站学习 softm…...