做思维导图的资源网站,淘宝网站做推广收费吗,设计感网站有哪些方面,重庆待遇好的互联网公司C#提供了泛型的完整支持#xff0c;不仅在编译时#xff0c;运行时仍然保留泛型的类型信息#xff0c;同时提供了更加丰富的泛型约束和更加全面的协变逆变支持。TS的泛型#xff0c;在语法表现形式上#xff0c;和C#差不多#xff0c;但本质上两者是不一样的。TS的泛型不仅在编译时运行时仍然保留泛型的类型信息同时提供了更加丰富的泛型约束和更加全面的协变逆变支持。TS的泛型在语法表现形式上和C#差不多但本质上两者是不一样的。TS的泛型和Java一样使用类型擦除机制泛型只存在于编译时在运行时泛型的类型信息会被移除。 一、泛型究竟有啥意义
1.1 泛型应用的经典案例
正常情况下我们还没真正懂泛型的时候已经大量使用到注意是使用不是定义比如List、Func、RepositoryMyClass,long等等。下面是一个经典例子大家参考一下我觉得要到自己去封装一个泛型仓储时才会真正领会到泛型的强大这部分内容要到AspNetCore一章。下例中假设我们需要创建一个数据存储类它可以存储和检索任何类型的数据我们使用泛型来实现这个需求。
//1、C#实现
//定义类时使用泛型类型的占位符
public class StorageT
{//存储数据的字段_dataprivate T _data;//保存数据的方法public void Store(T data){_data data;Console.WriteLine(Data stored: data);}//获取数据的方法public T Retrieve(){Console.WriteLine(Data retrieved: _data);return _data;}
}
//实例化类时确定泛型的具体类型
public class Program
{public static void Main(){//保存和读取整数类型数据Storageint intStorage new Storageint();intStorage.Store(42);int retrievedInt intStorage.Retrieve();//保存和读取字符串类型数据Storagestring stringStorage new Storagestring();stringStorage.Store(Hello, World!);string retrievedString stringStorage.Retrieve();}
}//2、TS实现
class StorageT {//存储数据的字段_dataprivate data: T;//保存数据的方法store(data: T): void {this.data data;console.log(Data stored: data);}//读取数据的方法retrieve(): T {console.log(Data retrieved: this.data);return this.data;}
}
//保存和读取整数类型数据
const intStorage new Storagenumber();
intStorage.store(42);
const retrievedInt intStorage.retrieve();
//保存和读取字符串类型数据
const stringStorage new Storagestring();
stringStorage.store(Hello, World!);
const retrievedString stringStorage.retrieve();1.2 泛型的作用总结
上例中我们进行了整数和字符串的保存和读取实际上可以进行任意类型数据的保存和读取。如果没有泛型我们需要定义StorageInt、StorageString等等超多个class如果方法逻辑变了还得一个个修改。减少代码重复、增强类型安全这是泛型的最大意义即使像TS、Java这样的类型擦除机制的泛型都能实现。实际开发中除了大量使用到框架为我们定义好的泛型类型比如语言框架提供的List、Array、Action、Func等也有系统框架为提供的比如ABP的Repository、Vue的defineProps()等等。我们自己也时常会定义自己的泛型特别是在有数据传输场景由于无法提前获知数据的具体类型通常需要封装一个通用方法比如使用TS开发前端应用时一般需要使用泛型二次封装一下axios。有人说泛型是类型的参数非常对但不好理解。我觉得说泛型是类型的占位符会更易理解。比如上例中的_data字段我们定义时无法确定具体类型那就先用一个符号代替用的时候再确定具体类型。这个占位符可以是本例的T也可以是V也可以是TValue它只是一个占位的符号。补充说一下类型擦除Java和TS的泛型都使用了类型擦除机制泛型只存在于编译时运行时泛型会被移除。这会造成什么影响呢举个例子由于泛型类型被移除List和List在运行时通过反射获取类型时结果是一样的无法区分这会在一定程度上限制反射的能力同时也增加了一些不可预测的运行时类型错误的风险。总之C#的泛型肯定是可以将Java按在地上摩擦的除此之后还有许多语言特性一样有此能力比如反射、LINQ、属性、事件async/await…
二、泛型类、接口和方法
2.1 泛型类的定义和使用
2.1.1 C#中定义和使用泛型类
//定义泛型类
public class GenericClassT
{private T _value; //字段类型public GenericClass(T value) //构造方法的参数{_value value;}public T GetValue() //方法返回值{return _value;}public void SetValue(T value) //方法参数{_value value;}
}
//使用泛型类
GenericClassint intInstance new GenericClassint(10);
int value intInstance.GetValue(); // 10
2.1.1 TS中定义和使用泛型类
//定义泛型类
class GenericClassT {private value: T;constructor(value: T) {this.value value;}getValue(): T {return this.value;}setValue(value: T): void {this.value value;}
}
//使用泛型类
let intInstance new GenericClassnumber(10);
let value intInstance.getValue(); // 10
2.2 泛型方法的定义和使用
2.2.1 C#中定义和使用泛型方法
//定义泛型方法
public class GenericMethods
{public T GenericMethodT(T parameter){return parameter;}
}
//使用泛型方法
GenericMethods gm new GenericMethods();
int intValue gm.GenericMethodint(5); // 5
string stringValue gm.GenericMethodstring(hello); // hello
2.2.2 TS中定义和使用泛型方法
//定义泛型方法
class GenericMethods {genericMethodT(parameter: T): T {return parameter;}
}
//使用泛型方法
let gm new GenericMethods();
let intValue gm.genericMethodnumber(5); // 5
let stringValue gm.genericMethodstring(hello); // hello
2.3 泛型接口的定义和使用
2.3.1 C#中定义和使用泛型接口
//定义泛型接口
public interface IGenericInterfaceT
{T GetValue();void SetValue(T value);
}//实现泛型接口时类依然是泛型类接口的类型依然没有确定
//类的泛型T和接口泛型T是对应着的
public class GenericClassT : IGenericInterfaceT
{private T _value;public GenericClass(T value){_value value;}public T GetValue(){return _value;}public void SetValue(T value){_value value;}
}//实现泛型接口时并确定接口具体的类型
public class SpecificClass : IGenericInterfacestring
{private string _value;public GenericClass(string value){_value value;}public string GetValue(){return _value;}public void SetValue(string value){_value value;}
}//实现多个泛型接口
public class MultipleRepositoryT : IRepositoryT, IAnotherRepositoryT{...}
2.3.2 TS中定义和使用泛型接口
//定义泛型接口
interface IGenericInterfaceT {getValue(): T;setValue(value: T): void;
}//实现泛型接口时类依然是泛型类接口的类型依然没有确定
//类的泛型T和接口泛型T是对应着的
class GenericClassT implements IGenericInterfaceT {private value: T;constructor(value: T) {this.value value;}getValue(): T {return this.value;}setValue(value: T): void {this.value value;}
}//和C#一样实现泛型接口时也可以确定接口的类型
public class SpecificClass implements IGenericInterfacestring{...}//实现多个泛型接口
class MultipleRepositoryT implements IRepositoryT, IAnotherRepositoryT {...}
2.4 泛型约束
2.4.1 C#中的泛型约束
//1、T必须是一个值类型包括结构和枚举
public class StructConstraintT where T : struct
{public T Value { get; set; }
}//2、T必须是一个引用类型包括类、接口、委托或数组
public class ClassConstraintT where T : class
{public T Value { get; set; }
}//3、T必须有一个无参构造函数
public class NewConstraintT where T : new()
{public T CreateInstance(){return new T();}
}//4、T必须是指定基类的派生类
public class BaseClass {}
public class BaseClassConstraintT where T : BaseClass
{public T Value { get; set; }
}//5、T必须实现指定的接口
public interface IMyInterface {}
public class InterfaceConstraintT where T : IMyInterface
{public T Value { get; set; }
}//7、多重约束
public class MultipleConstraintsT where T : class, IMyInterface, new()
{public T CreateInstance(){return new T();}
}
2.4.2 TS中的泛型约束
TS的类型约束和C#差异比较大更加灵活。差异原因主要是它们的类型系统不一样。
//1、类型约束
//T必须包含指定字面量对象、接口或类的属性类型兼容
//1.1 通过字面量对象传入的参数必须包含length属性-----------------------
function logLengthT extends { length: number }(arg: T): void {console.log(arg.length);
}
logLength(hello); // 输出: 5
logLength([1, 2, 3]); // 输出: 3
//调用logLength时省略了泛型等价于logLengthstring(hello)//1.2 通过接口来实现---------------------------------------------------
interface Lengthwise {length: number;
}
function loggingIdentityT extends Lengthwise(arg: T): T {console.log(arg.length);return arg;
}
loggingIdentity({ length: 10, value: 3 });//1.3 通过类来实现-----------------------------------------------------
//只要类型能够兼容就可以详见多态不举例了//2、联合类型约束
//T必须是多个类型之一
function combineT extends number | string(a: T, b: T): T {if (typeof a number typeof b number) {return (a b) as T;}if (typeof a string typeof b string) {return (a b) as T;}throw new Error(Invalid arguments);
}
console.log(combine(1, 2)); // 输出: 3
console.log(combine(hello, world)); // 输出: hello world//3、带有索引签名的约束
//keyof接收一个对象类型生成其键名的联合类型
//下列中keyof obj生成的是a|b|c
function getPropertyT, K extends keyof T(obj: T, key: K) {return obj[key];
}
let obj { a: 1, b: 2, c: 3 };
console.log(getProperty(obj, a)); // 输出: 1
2.5 泛型默认值C#和默认类型TS
2.5.1 C#中的泛型【默认值】
使用default关键字来为泛型类型参数提供默认值。default关键字根据类型参数是值类型还是引用类型返回相应的默认值。值类型返回值类型的默认值如int为0bool为false引用类型返回null public class GenericClassT
{private T _value;public GenericClass(){_value default(T); // 使用default关键字设置默认值}public T GetValue(){return _value;}}public class Program
{public static void Main(){// 对于值类型GenericClassint intInstance new GenericClassint();Console.WriteLine(intInstance.GetValue()); // 输出: 0// 对于引用类型GenericClassstring stringInstance new GenericClassstring();Console.WriteLine((stringInstance.GetValue() ?? null)); // 输出:null}
}2.5.2 TS中的泛型【默认类型】
TS没有默认值的概念但有一个默认类型。
class GenericClassT string {private value: T;constructor(value: T) {this.value value;}getValue(): T {return this.value;}setValue(value: T): void {this.value value;}
}// 使用默认类型
let defaultInstance new GenericClass(Hello);
console.log(defaultInstance.getValue()); // 输出: Hello// 显式提供类型参数
let numberInstance new GenericClassnumber(42);
console.log(numberInstance.getValue()); // 输出: 42
三、泛型的协变和逆变
C#中的协变和逆变是两个非常抽象的概念先硬记住下面两个用法至于如何定义试过两次没绕出来放弃了反正实际开发过程中从来没有自己定义过。本质上它是泛型的多态可以实现泛型类型之间的类型兼容。而TS的类型兼容是鸭子类型灵活很多不需要协变和逆变的特性。以下案例为C#中协变和逆变的使用不涉及定义环节。由于TS类型兼容的特性下面代码稍微改下放到TS中也是成立的。
//协变允许将泛型类型参数的子类型分配给泛型类型参数。泛型集合用到
IEnumerablestring strings new Liststring { Hello, World };
IEnumerableobject objects strings; // 协变
foreach (var obj in objects)
{Console.WriteLine(obj); // 输出: Hello World
}//逆变允许将泛型类型参数的基类型分配给泛型类型参数。泛型委托用到
Actionobject actObject obj Console.WriteLine(obj);
Actionstring actString actObject; // 逆变
actString(Hello, World!); // 输出: Hello, World!