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

第18章 泛型 笔记

第18章 泛型 笔记

18.1 什么是泛型

泛型可以将重构代码并且额外添加一个抽象层,是专门为多段代码在不同的数据类型上执行相同指令而设计的。

18.2 C# 中的泛型

泛型可以让多个类型共享一组代码,允许声明类型参数化代码,用不同的类型进行实例化。

泛型不是类型,而是类型的模板。
在这里插入图片描述

C# 提供了以下 5 种泛型:

  1. 结构
  2. 接口
  3. 委托
  4. 方法

其中 1 ~ 4 是类型,5 是成员。

在这里插入图片描述

18.3 泛型类

泛型类不是实际的类,而是类的模板,必须先从它们构建实际的类,然后创建类的引用和实例。

1.在某些类型上使用一个占位符来声明一个类。

2.为占位符提供真实类型(构造类型)。

3.创建构造类型的实例。

18.3.1 声明泛型类

声明语法:

1.在类名之后放置一组尖括号。

2.在尖括号中用逗号分隔占位符字符串,用于表示需要提供的类型(类型参数)。

3.在泛型类声明的主体中使用类型参数来表示替代类型。

class SomeClass < T1, T2 >
{ // Normally, a type would be used in this position. public T1 SomeVar;public T2 OtherVar;
} 

18.3.2 创建构造类型

声明泛型类后,可以告诉编译器使用哪些真实类型来替代占位符,编译器将获取这些真实类型并创建构造类型(用来创建真实类对象的模板)。

SomeClass<short, int>

泛型类声明上的类型参数用作类型的占位符。

在创建构造类型时提供的真实类型是类型实参。

// 类型参数
class SomeClass<T1, T2>
{//...
}// 类型实参
SomeClass<short, int>

18.3.3 创建变量和实例

类对象的创建

MyNonGenClass myNGC = new MyNonGenClass ();
// Constructed class Constructed class
SomeClass<short, int> mySc1 = new SomeClass<short int>();
var mySc2 = new SomeClass<short, int>();

和非泛型类一样,引用和实例可以分开创建。

// 泛型类
class SomeClass<T1, T2>
{public T1 SpmeVar;public T2 OtherVar;
}// 分配类变量
SomeClass <short, int> myInst;
// 分配实例
myInst = new SomeClass<short, int> ();

18.3.4 使用泛型的示例

使用泛型来实现栈的示例

class MyStack<T>
{T[] StackArray;int StackPointer = 0;public void Push(T x){if ( !IsStackFull )StackArray[StackPointer++] = x;}public T Pop(){return ( !IsStackEmpty )? StackArray[--StackPointer]: StackArray[0];}const int MaxStack = 10;bool IsStackFull { get{ return StackPointer >= MaxStack; } }bool IsStackEmpty { get{ return StackPointer <= 0; } }public MyStack(){StackArray = new T[MaxStack];}public void Print(){for (int i = StackPointer-1; i >= 0 ; i--)Console.WriteLine($" Value: { StackArray[i] }");}} class Program
{static void Main( ){MyStack<int> StackInt = new MyStack<int>();MyStack<string> StackString = new MyStack<string>();StackInt.Push(3);StackInt.Push(5);StackInt.Push(7);StackInt.Push(9);StackInt.Print();StackString.Push("This is fun");StackString.Push("Hi there! ");StackString.Print();}
}// outputValue: 9Value: 7Value: 5Value: 3Value: Hi there!Value: This is fun

18.3.5 比较泛型和非泛型栈

非泛型栈和泛型栈之间的区别

非泛型泛型
源代码大小更大:需要为每一种类型编写一个新的实现更小:不管构造类型的数量有多少,只需要一个实现
可执行文件大小无论每一个版本的栈是否会被使用,都会在编译的版本中出现可执行文件中只会出现有构造类型的类型
写的难易度易于书写,因为它更具体比较难写,因为它更抽象
维护的难易度更容易出问题,因为所有修改需要应用到每一个可用的类型上易于维护,因为只需要修改一个地方

18.4 类型参数的约束

符合约束的类型参数叫做未绑定的类型参数

要让泛型更加有用,需要提供额外的信息让编译器直到参数可以接受哪些类型,这些额外的信息称为约束

18.4.1 Where 子句

每个有约束的类型参数都有自己的 where 子句。

如果形参有多个约束,则使用逗号分隔。

//    类型参数      约束列表
where TypeParam : constraint, constraint, ...

有关 where 子句的要点如下:

  1. 在类型参数列表的关闭尖括号后列出。
  2. 不使用分隔符。
  3. 可以随意次序列出。
  4. where 是上下文关键字,可以在其他上下文使用。
// T2,T3 有约束,并且没有分隔符
class MyClass < T1, T2, T3 >where T2: Customer // Constraint for T2where T3: IComparable // Constraint for T3
{ // ... 
}

18.4.2 约束类型和次序

5种类型的约束,

约束类型描述
类名 class只有这个类型的类或从它派生的类才能用作类型实参
struct任何值类型都可以用作类型实参
接口名只有这个接口或实现这个接口的类型才能用作类型实参
new()任何带有无参公共构造函数的类型都可以用作类型实参。这叫作构造函数约束

where子句可以任意次序,子句中的约束必须具有特定的顺序。

  • 最多只能有一个主约束,必须放在第一位。
  • 可以有任意个接口名称约束。
  • 如果存在构造函数约束,必须放在最后。

在这里插入图片描述

如果类型参数有多个约束,则必须遵守的顺序

class SortedList<S>where S: IComparable<S> { ... }
class LinkedList<M,N>where M : IComparable<M>where N : ICloneable { ... }
class MyDictionary<KeyType, ValueType>where KeyType : IEnumerable,new() { ... }

18.5 泛型方法

泛型方法可以在泛型 / 非泛型类、结构和接口中声明。

在这里插入图片描述

18.5.1 声明泛型方法

泛型方法具有类型参数列表和可选的约束

  • 泛型方法有两个参数列表。
    • 方法参数列表(圆括号内)。
    • 类型参数列表(尖括号内)。
  • 方法参数列表后放置可选的约束子句。
//                类型参数列表  方法参数列表    约束子句
public void PrintData<S, T> ( S p, T t ) where S: Person
{// ...
}

18.5.2 调用泛型方法

MyMethod<short, int>();
MyMethod<int, long >();

编译器使用每个构造函数实例产生方法的不同版本。

编译器有时可以从方法参数推断类型参数。例如,对于如下的方法声明:

public void MyMethod <T> (T myVal) { ... }

编译器可以从 myInt 参数的类型推断出 T 为 int,因此可以省略尖括号。

int myInt = 5;
MyMethod <int> (myInt);

18.5.3 泛型方法的示例

class Simple // Non-generic class
{static public void ReverseAndPrint<T>(T[] arr) // Generic method{Array.Reverse(arr);foreach (T item in arr) // Use type argument T.Console.Write( $"{ item.ToString() }, ");Console.WriteLine("");}
}class Program
{static void Main(){// Create arrays of various types.var intArray = new int[] { 3, 5, 7, 9, 11 };var stringArray = new string[] { "first", "second", "third" };var doubleArray = new double[] { 3.567, 7.891, 2.345 };Simple.ReverseAndPrint<int>(intArray); // Invoke method.Simple.ReverseAndPrint(intArray); // Infer type and invoke.Simple.ReverseAndPrint<string>(stringArray); // Invoke method.Simple.ReverseAndPrint(stringArray); // Infer type and invoke.Simple.ReverseAndPrint<double>(doubleArray); // Invoke method.Simple.ReverseAndPrint(doubleArray); // Infer type and invoke.}
}// output
11, 9, 7, 5, 3,
3, 5, 7, 9, 11,
third, second, first,
first, second, third,
2.345, 7.891, 3.567,
3.567, 7.891, 2.345,

18.6 扩展方法和泛型类

和非泛型类一样,泛型类的扩展方法必须满足如下条件:

  1. 声明为 static。
  2. 是静态类的成员。
  3. 第一个参数类型中必须有关键字 this,后面是扩展的泛型类的名字。

Print扩展了 Holder泛型类

static class ExtendHolder
{public static void Print<T>(this Holder<T> h){T[] vals = h.GetValues();Console.WriteLine($"{ vals[0] },\t{ vals[1] },\t{ vals[2] }");}
}
class Holder<T>
{T[] Vals = new T[3];public Holder(T v0, T v1, T v2){ Vals[0] = v0; Vals[1] = v1; Vals[2] = v2; }public T[] GetValues() { return Vals; } 
}
class Program
{static void Main(string[] args) {var intHolder = new Holder<int>(3, 5, 7);var stringHolder = new Holder<string>("a1", "b2", "c3");intHolder.Print();stringHolder.Print();}
}// output
3, 5, 7
a1, b2, c3

18.7 泛型结构

泛型结构的规则和条件与泛型类一致。

struct PieceOfData<T> // Generic struct
{public PieceOfData(T value) { _data = value; }private T _data;public T Data{get { return _data; }set { _data = value; }}
}
class Program
{static void Main() Constructed type{var intData = new PieceOfData<int>(10);var stringData = new PieceOfData<string>("Hi there.");Constructed typeConsole.WriteLine($"intData = { intData.Data }");Console.WriteLine($"stringData = { stringData.Data }");}
}// output
intData = 10
stringData = Hi there.

18.8 泛型委托

声明泛型委托

//   返回类型     类型参数     委托形参
delegate R MyDelegate<T, R>( T value );

有两个参数列表,委托形参列表和类型参数列表

类型参数的范围包括:返回类型、形参列表、约束子句

delegate void MyDelegate<T>(T value); // Generic delegate
class Simple
{static public void PrintString(string s) // Method matches delegate{Console.WriteLine( s );}static public void PrintUpperString(string s) // Method matches delegate{Console.WriteLine($"{ s.ToUpper() }");}
}class Program
{static void Main( ){var myDel = // Create inst of delegate.new MyDelegate<string>(Simple.PrintString);myDel += Simple.PrintUpperString; // Add a second method.myDel("Hi There."); // Call delegate.}
}// output
Hi There.
HI THERE.

C# LINQ 特性大量使用泛型委托。

public delegate TR Func<T1, T2, TR>(T1 p1, T2 p2); // Generic delegate
class Simple Delegate return type
{static public string PrintString(int p1, int p2) // Method matches delegate{int total = p1 + p2;return total.ToString();}
}
class Program
{static void Main(){var myDel = // Create inst of delegate.new Func<int, int, string>(Simple.PrintString);Console.WriteLine($"Total: { myDel(15, 13) }"); // Call delegate.}
}// output 
Total: 28

18.9 泛型接口

泛型接口的声明和非泛型接口的声明类似,但是要在接口名称后的尖括号中放置类型参数。

interface IMyIfc<T> // Generic interface
{T ReturnIt(T inValue);
}class Simple : IMyIfc<int>, IMyIfc<string> // Nongeneric class
{public int ReturnIt(int inValue) // Implement interface using int.{ return inValue; }public string ReturnIt(string inValue) // Implement interface using string.{ return inValue; }
}class Program
{static void Main(){Simple trivial = new Simple();Console.WriteLine($"{ trivial.ReturnIt(5) }");Console.WriteLine($"{ trivial.ReturnIt("Hi there.") }");}
}// output
5
Hi there.

18.9.1 使用泛型接口的示例

另外两项能力

用不同类型的参数实例化的泛型接口的实例是不同的接口

可以在非泛型类型中实现泛型接口

18.9.2 泛型接口的实现必须唯一

必须保证类型实参的组合 不会在类型中产生两个重复的接口。

例如,对于下面的泛型接口,会产生潜在的冲突:S 可能用作 int 类型,此时会有两个相同类型的接口,这将不被允许。

interface IMyIfc<T>
{T ReturnIt(T inValue);
}// Two interfaces
class Simple<S> : IMyIfc<int>, IMyIfc<S> // Error!
{public int ReturnIt(int inValue) // Implement first interface.{return inValue;}public S ReturnIt(S inValue) // Implement second interface,{ // but if it's int, it would bereturn inValue; // the same as the one above.}
}

泛型结构的名称不会和非泛型冲突。

18.10 协变和逆变

可变性分为三种:协变、逆变、不变

18.10.1 协变(out)

将派生类型的对象赋值给基类型的变量,叫做 赋值兼容性

给出如下例子:

class Animal
{ public int NumberOfLegs = 4; }
class Dog : Animal
{ }
class Program
{static void Main( ){Animal a1 = new Animal( );Animal a2 = new Dog( );Console.WriteLine($"Number of dog legs: { a2.NumberOfLegs }");}
}// output
Number of dog legs: 4

Dog 类型的变量可以作为 Animal 类型的引用,因为 DogAnimal 派生而来,发生了隐式类型转换。

进行扩展,添加 Factory 泛型委托、MakeDog 方法,并且 MakeDog 方法可以匹配 Factory 委托。

class Animal { public int Legs = 4; } // Base class
class Dog : Animal { } // Derived class
delegate T Factory<T>( ); // delegate Factory 
class Program
{static Dog MakeDog( ) // Method that matches delegate Factory{return new Dog( );}static void Main( ){Factory<Dog> dogMaker = MakeDog; // Create delegate object.Factory<Animal> animalMaker = dogMaker; // Attempt to assign delegate object.Console.WriteLine( animalMaker( ).Legs.ToString( ) );}
}

Main 函数的第二行尝试将 Factory<Dog> 类型赋给 Factory<Animal>类型,这将产生报错。

问题的原因在于,委托 Factory<Dog> 并没有从 Factory<Animal> 派生得到。

赋值兼容性不适用,因为两个委托没有继承关系

仅希望传递 DogFactory<Animal> 委托时,代码对 Dog 类型中的 Animal 部分进行操作,这并不会发生越界访问,是完全合理的。

为了完成我们的期望,可以通过添加 out 关键字改变委托声明。

delegate T Factory<out T>( );

协变关系允许程度更高的派生类型处于返回及输出位置

18.10.2 逆变

逆变:基类 → 派生类

与协变相反,如果类型参数只用于方法中的输入参数,那么可以传入更高程度的派生类引用,因为委托的方法中只对其基类部分进行操作。

class Animal { public int NumberOfLegs = 4; }
class Dog : Animal { }
class Program 
{// Keyword for contravariancedelegate void Action1<in T>( T a );static void ActOnAnimal( Animal a ) { Console.WriteLine( a.NumberOfLegs ); }static void Main( ){Action1<Animal> act1 = ActOnAnimal;Action1<Dog> dog1 = act1;dog1( new Dog() );}
}// output 
4

调用委托时,调用代码为方法 ActOnAnimal 传入的 Dog 类型的变量,而其期望的是 Animal 对象,因此可以进行操作。

逆变允许程度更高的派生类型作为输入参数

18.10.3 协变和逆变的不同

在这里插入图片描述

18.10.4 接口的协变和逆变

相同的原则也适用于接口。

class Animal { public string Name; }
class Dog: Animal{ };
// Keyword for covariance
interface IMyIfc<out T>
{T GetFirst();
}
class SimpleReturn<T>: IMyIfc<T>
{public T[] items = new T[2];public T GetFirst() { return items[0]; }
}
class Program
{static void DoSomething(IMyIfc<Animal> returner){Console.WriteLine( returner.GetFirst().Name );}static void Main( ){SimpleReturn<Dog> dogReturner = new SimpleReturn<Dog>();dogReturner.items[0] = new Dog() { Name = "Avonlea" };IMyIfc<Animal> animalReturner = dogReturner;DoSomething(dogReturner);}
}// output
Avonlea

18.10.5 关于可变性的更多内容

实际上,编译器可以自动识别某个已构建的委托是协变还是逆变,并且自动进行类型强制转换,但这通常发生在没有为对象的类型赋值的时候。

Main 第一行创建了 Factory<Animal> 类型的委托,并直接将方法 MakeDog 赋值给它。由于没有创建 Factory<Dog> 委托,因此编译器清楚这是协变关系,允许这种赋值,哪怕委托中没有 out 标识符。

到 Main 第三行时,由于第二行已经创建了 Factory<Dog> 委托,因此后面的协变关系赋值需要 out 标识符才能完成。

class Animal { public int Legs = 4; } // Base class
class Dog : Animal { } // Derived class
class Program
{delegate T Factory<out T>();static Dog MakeDog() { return new Dog(); }static void Main(){Factory<Animal> animalMaker1 = MakeDog; // Coerced implicitlyFactory<Dog> dogMaker = MakeDog;Factory<Animal> animalMaker2 = dogMaker; // Requires the out specifierFactory<Animal> animalMaker3= new Factory<Dog>(MakeDog); // Requires the out specifier}
}

重要事项

  • 可变性只适用于引用类型,不适用值类型。
  • in、out 关键字的显式变化只适用于委托和接口,不适用于类、结构和方法。
  • 不使用 int、out 关键字的委托和接口类型参数是不变的。
//                协变     逆变
delegate T Factory<out R, in S, T>( );
http://www.dtcms.com/a/303190.html

相关文章:

  • 第一第二章笔记整理
  • AutoGen - model_clients和model_context使用示例
  • Docker学习相关视频笔记(一)
  • 机器学习sklearn:决策树的参数、属性、接口
  • redis getshell得方式
  • Redis 部署模式详解
  • stm32开发 -- TFTLCD相关
  • Zabbix 6.0 监控AWS全栈实战|EC2至Lambda的无缝监控
  • 配置 MCP 让 cursor 结合 Figma 自动生成设计稿组件
  • Python defaultdict 的强大之处:告别繁琐的字典键检查: Effective Python 第17条
  • Python动态规划:从基础到高阶优化的全面指南
  • 网络与信息安全有哪些岗位:(3)安全运维工程师
  • 微算法科技(NASDAQ:MLGO)利用基于区块链的机器学习模型进行交易分类,实现交易数据的匿名化
  • Linux内核驱动开发核心问题全解
  • shell每日三题大神之路:第三天
  • Java 笔记 transient 用法
  • 四、计算机组成原理——第4章:指令系统
  • EAP(基于事件的异步编程模式)
  • 计算机网络编程-Socket通信以及实战
  • Ettus USRP X410/X440 运行 ADC 自校准
  • Yolo底层原理学习--(第二篇)
  • STM32-基本定时器
  • 【动态规划 | 路径问题】动态规划方法:解决路径问题的最佳策略
  • ESP32-S3学习笔记<8>:LEDC的应用
  • 【历史人物】【韩愈】简历与生平
  • Springboot 项目中使用 Filter 全指南
  • 基于Python的arXiv论文数据分析系统:从爬取到可视化的完整实践
  • flexbuild-imx91 imx93
  • Java-分布式锁
  • Lakehouse: Unifying DW Advanced Analytics in Open Platforms