学习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# 不允许此操作;唯一允许的语言构造是可以从约束中推断出来的构造。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/147742.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android studio编译SDL so库

一、下载源码 SDL官网 二、解压&#xff0c;拷贝android项目&#xff0c;并重新命名 2.1、解压 2.2&#xff0c;重命名项目名称&#xff08;androidSDL&#xff09;AndroidSDL Github 三、导入头文件和源文件&#xff0c;修改android.mk文件 3.1、在jni目录下创建SDL2文件…

亚马逊云科技云存储服务指南

文章作者&#xff1a;Libai 高效的云存储服务对于现代软件开发中的数据管理至关重要。亚马逊云科技云存储服务提供了强大的工具&#xff0c;可以简化工作流程并增强数据管理能力。 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏…

在前端开发中,什么是CDN(Content Delivery Network)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

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

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

【RH850芯片】RH850U2A芯片平台Spinlock的底层实现

目录 前言 正文 1.RH850U2A上的原子操作 1.1 Link 1.2 Link generation 1.3 Success in storing 1.4 Failure in storing 1.5 Condition for successful storing 1.6 Loss of the link 1.7 示例代码 2.Spinlock代码分析 2.1 尝试获取Spinlock 2.2 释放Spinlock …

基于PyTorch搭建你的生成对抗性网络

前言 你听说过GANs吗&#xff1f;还是你才刚刚开始学&#xff1f;GANs是2014年由蒙特利尔大学的学生 Ian Goodfellow 博士首次提出的。GANs最常见的例子是生成图像。有一个网站包含了不存在的人的面孔&#xff0c;便是一个常见的GANs应用示例。也是我们将要在本文中进行分享的…

Spring Boot中使用MongoDB完成数据存储

我们在开发中用到的数据存储工具有许多种&#xff0c;我们常见的数据存储工具包括&#xff1a; 关系性数据库&#xff1a;使用表格来存储数据&#xff0c;支持事务和索引。&#xff08;如&#xff1a;MySQL&#xff0c;Oracle&#xff0c;SQL Server等&#xff09;。NoSQL数据…

Redis篇---第七篇

系列文章目录 文章目录 系列文章目录前言一、是否使用过 Redis Cluster 集群,集群的原理是什么?二、 Redis Cluster 集群方案什么情况下会导致整个集群不可用?三、Redis 集群架构模式有哪几种?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分…

OPPO Watch纯手机开启远程ADB调试

Wear OS手表中&#xff0c;我们可以直接在开发者设置中打开WiFi调试。但是这在OPPO等魔改Android系统中不再奏效。 需要什么&#xff1f;&#xff1f; 手表一台手机一个OTG转接头一个手表充电器一个 演示设备 手机&#xff1a; OPPO Find X手表&#xff1a; OPPO Watch 1代 …

Android——模块级build.gradle配置——applicationId和namespace

官方地址&#xff1a; 配置应用模块-applicationId和namespace了解 build.gradle 中的实用设置。https://developer.android.google.cn/studio/build/configure-app-module?hlzh-cn 产生那些异常场景&#xff1a; Android&#xff1a;Namespace not specified. Please spec…

3D重建相关

目录 <font colorblue>整个3D重建的过程是怎样的<font colorblue>体素、网格、点云之间的关系是什么<font colorblue>网格和体素化之间是什么关系<font colorblue>点云中的颜色怎么处理成最终3D模型上的颜色<font colorblue>点云还原的3D模型的颜…

Halcon (3):窗体常用语法使用

文章目录 文章专栏视频资源前言halcon图像使用加载图片示例绘制常用图像批量批注绘制 文章专栏 Halcon开发 视频资源 机器视觉之C#联合Halcon 前言 在使用halcon的算子之前&#xff0c;我们要先学会如何在图片上面进行标注。因为我们不仅要导出处理的结果&#xff0c;还要导出…

论文阅读:YOLOV: Making Still Image Object Detectors Great at Video Object Detection

发表时间&#xff1a;2023年3月5日 论文地址&#xff1a;https://arxiv.org/abs/2208.09686 项目地址&#xff1a;https://github.com/YuHengsss/YOLOV 视频物体检测&#xff08;VID&#xff09;具有挑战性&#xff0c;因为物体外观的高度变化以及一些帧的不同恶化。有利的信息…

Windows10电脑没有微软商店的解决方法

在Windows10电脑中用户可以打开微软商店&#xff0c;下载自己需要的应用程序。但是&#xff0c;有用户反映自己Windows10电脑上没有微软商店&#xff0c;但是不清楚具体的解决方法&#xff0c;接下来小编给大家详细介绍关于解决Windows10电脑内微软商店不见了的方法&#xff0c…

计算机的发展

硬件的发展 第一台电子数字计算机&#xff1a;ENIAC&#xff08;1946&#xff09;&#xff0c;作者&#xff1a;冯诺依曼&#xff0c;逻辑元件&#xff1a;电子管 bug&#xff1a;小虫子&#xff0c;会影响打点 Intel&#xff1a; 机器字长&#xff1a;计算机一次整数运算所能…

springcloudalibaba-3

一、Nacos Config入门 1. 搭建nacos环境【使用现有的nacos环境即可】 使用之前的即可 2. 在微服务中引入nacos的依赖 <!-- nacos配置依赖 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-…

深入Rust:探索所有权和借用机制

大家好&#xff01;我是lincyang。 今天&#xff0c;我们将一起深入探索Rust语言中的一个核心概念&#xff1a;所有权和借用机制。 这些特性是Rust区别于其他语言的重要特点&#xff0c;它们在内存管理和并发编程中扮演着关键角色。 一、Rust所有权机制 1. 什么是所有权&#x…

Qt HTTP 摘要认证(海康球机摄像机ISAPI开发)

接到一个需求是开发下海康的球机,控制云台,给到我的是一个开发手册,当然了是海康的私有协议 ISAPI开发手册https://download.csdn.net/download/qq_37059136/88547425关于开发这块读文档就可以理解了,海康使用的是摘要认证,当然了海康已经给出使用范例 通过libcurl就可以直接连…

Android VSYNC发展历程

0 前言 安卓直到android-4.1.1_r1才首次引入VSYNC实现&#xff0c;然后逐步演进到android-4.4才得以完善&#xff0c;并在android-11、12后继续大改。 1 尚未引入 android-4.0.4_r2.1之前尚未引入VSYNC[1]&#xff0c;SurfaceFlinger被实现为一个线程&#xff0c;通过睡眠来实…