第壹章第15节 C#和TS语言对比-泛型

C#提供了泛型的完整支持,不仅在编译时,运行时仍然保留泛型的类型信息,同时提供了更加丰富的泛型约束和更加全面的协变逆变支持。TS的泛型,在语法表现形式上,和C#差不多,但本质上两者是不一样的。TS的泛型,和Java一样,使用类型擦除机制,泛型只存在于编译时,在运行时,泛型的类型信息会被移除。

一、泛型究竟有啥意义

1.1 泛型应用的经典案例

正常情况下,我们还没真正懂泛型的时候,已经大量使用到(注意,是使用,不是定义),比如List、Func、Repository<MyClass,long>等等。下面是一个经典例子,大家参考一下,我觉得要到自己去封装一个泛型仓储时,才会真正领会到泛型的强大,这部分内容要到AspNetCore一章。下例中,假设我们需要创建一个数据存储类,它可以存储和检索任何类型的数据,我们使用泛型来实现这个需求。

//1、C#实现==========================================================================
//定义类时,使用泛型(类型的占位符)
public class Storage<T>
{
    //存储数据的字段_data
    private 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()
    {
        //保存和读取整数类型数据
        Storage<int> intStorage = new Storage<int>();
        intStorage.Store(42);
        int retrievedInt = intStorage.Retrieve();
        //保存和读取字符串类型数据
        Storage<string> stringStorage = new Storage<string>();
        stringStorage.Store("Hello, World!");
        string retrievedString = stringStorage.Retrieve();
    }
}


//2、TS实现==========================================================================
class Storage<T> {
    //存储数据的字段_data
    private 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 Storage<number>();
intStorage.store(42);
const retrievedInt = intStorage.retrieve();
//保存和读取字符串类型数据
const stringStorage = new Storage<string>();
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 GenericClass<T>
{
    private T _value; //字段类型

    public GenericClass(T value) //构造方法的参数
    {
        _value = value;
    }

    public T GetValue() //方法返回值
    {
        return _value;
    }
    public void SetValue(T value) //方法参数
    {
        _value = value;
    }
}
//使用泛型类
GenericClass<int> intInstance = new GenericClass<int>(10);
int value = intInstance.GetValue();  // 10

2.1.1 TS中定义和使用泛型类
//定义泛型类
class GenericClass<T> {
    private value: T;
  
    constructor(value: T) {
        this.value = value;
    }
  
    getValue(): T {
        return this.value;
    }
    setValue(value: T): void {
        this.value = value;
    }
}
//使用泛型类
let intInstance = new GenericClass<number>(10);
let value = intInstance.getValue();  // 10

2.2 泛型方法的定义和使用

2.2.1 C#中定义和使用泛型方法
//定义泛型方法
public class GenericMethods
{
    public T GenericMethod<T>(T parameter)
    {
        return parameter;
    }
}
//使用泛型方法
GenericMethods gm = new GenericMethods();
int intValue = gm.GenericMethod<int>(5);  // 5
string stringValue = gm.GenericMethod<string>("hello");  // hello

2.2.2 TS中定义和使用泛型方法
//定义泛型方法
class GenericMethods {
    genericMethod<T>(parameter: T): T {
        return parameter;
    }
}
//使用泛型方法
let gm = new GenericMethods();
let intValue = gm.genericMethod<number>(5);  // 5
let stringValue = gm.genericMethod<string>("hello");  // hello

2.3 泛型接口的定义和使用

2.3.1 C#中定义和使用泛型接口
//定义泛型接口=======================================================================
public interface IGenericInterface<T>
{
    T GetValue();
    void SetValue(T value);
}

//实现泛型接口时,类依然是泛型类,接口的类型依然没有确定===============================
//类的泛型T和接口泛型T是对应着的
public class GenericClass<T> : IGenericInterface<T>
{
    private T _value;

    public GenericClass(T value)
    {
        _value = value;
    }

    public T GetValue()
    {
        return _value;
    }
    public void SetValue(T value)
    {
        _value = value;
    }
}

//实现泛型接口时,并确定接口具体的类型================================================
public class SpecificClass :  IGenericInterface<string>
{
    private string _value;

    public GenericClass(string value)
    {
        _value = value;
    }

    public string GetValue()
    {
        return _value;
    }
    public void SetValue(string value)
    {
        _value = value;
    }
}

//实现多个泛型接口===================================================================
public class MultipleRepository<T> : IRepository<T>, IAnotherRepository<T>{...}

2.3.2 TS中定义和使用泛型接口
//定义泛型接口=======================================================================
interface IGenericInterface<T> {
    getValue(): T;
    setValue(value: T): void;
}

//实现泛型接口时,类依然是泛型类,接口的类型依然没有确定===============================
//类的泛型T和接口泛型T是对应着的
class GenericClass<T> implements IGenericInterface<T> {
    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 IGenericInterface<string>{...}

//实现多个泛型接口===================================================================
class MultipleRepository<T> implements IRepository<T>, IAnotherRepository<T> {...}
  

2.4 泛型约束

2.4.1 C#中的泛型约束
//1、T必须是一个值类型,包括结构和枚举================================================
public class StructConstraint<T> where T : struct
{
    public T Value { get; set; }
}

//2、T必须是一个引用类型,包括类、接口、委托或数组=====================================
public class ClassConstraint<T> where T : class
{
    public T Value { get; set; }
}

//3、T必须有一个无参构造函数==========================================================
public class NewConstraint<T> where T : new()
{
    public T CreateInstance()
    {
        return new T();
    }
}

//4、T必须是指定基类的派生类==========================================================
public class BaseClass {}
public class BaseClassConstraint<T> where T : BaseClass
{
    public T Value { get; set; }
}

//5、T必须实现指定的接口=============================================================
public interface IMyInterface {}
public class InterfaceConstraint<T> where T : IMyInterface
{
    public T Value { get; set; }
}

//7、多重约束=======================================================================
public class MultipleConstraints<T> where T : class, IMyInterface, new()
{
    public T CreateInstance()
    {
        return new T();
    }
}

2.4.2 TS中的泛型约束

TS的类型约束和C#差异比较大,更加灵活。差异原因,主要是它们的类型系统不一样。

//1、类型约束========================================================================
//T必须包含指定字面量对象、接口或类的属性(类型兼容)
//1.1 通过字面量对象,传入的参数必须包含length属性-----------------------
function logLength<T extends { length: number }>(arg: T): void {
    console.log(arg.length);
}
logLength("hello"); // 输出: 5
logLength([1, 2, 3]); // 输出: 3
//调用logLength时,省略了泛型,等价于:logLength<string>("hello")

//1.2 通过接口来实现---------------------------------------------------
interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
loggingIdentity({ length: 10, value: 3 });

//1.3 通过类来实现-----------------------------------------------------
//只要类型能够兼容就可以,详见多态,不举例了



//2、联合类型约束====================================================================
//T必须是多个类型之一
function combine<T 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 getProperty<T, 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为0,bool为false;引用类型返回null


public class GenericClass<T>
{
    private T _value;

    public GenericClass()
    {
        _value = default(T); // 使用default关键字设置默认值
    }

    public T GetValue()
    {
        return _value;
    }

}

public class Program
{
    public static void Main()
    {
        // 对于值类型
        GenericClass<int> intInstance = new GenericClass<int>();
        Console.WriteLine(intInstance.GetValue()); // 输出: 0
        // 对于引用类型
        GenericClass<string> stringInstance = new GenericClass<string>();
        Console.WriteLine((stringInstance.GetValue() ?? "null")); // 输出:null
    }
}
2.5.2 TS中的泛型【默认类型】

TS没有默认值的概念,但有一个默认类型。

class GenericClass<T = 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 GenericClass<number>(42);
console.log(numberInstance.getValue()); // 输出: 42

三、泛型的协变和逆变

C#中的协变和逆变是两个非常抽象的概念,先硬记住下面两个用法,至于如何定义,试过两次,没绕出来,放弃了,反正实际开发过程中,从来没有自己定义过。本质上,它是泛型的多态,可以实现泛型类型之间的类型兼容。而TS的类型兼容是鸭子类型,灵活很多,不需要协变和逆变的特性。以下案例为C#中,协变和逆变的使用,不涉及定义环节。由于TS类型兼容的特性,下面代码稍微改下,放到TS中,也是成立的。

//协变,允许将泛型类型参数的子类型分配给泛型类型参数。泛型集合用到======================
IEnumerable<string> strings = new List<string> { "Hello", "World" };
IEnumerable<object> objects = strings; // 协变
foreach (var obj in objects)
{
    Console.WriteLine(obj); // 输出: Hello World
}

//逆变,允许将泛型类型参数的基类型分配给泛型类型参数。泛型委托用到======================
Action<object> actObject = obj => Console.WriteLine(obj);
Action<string> actString = actObject; // 逆变
actString("Hello, World!"); // 输出: Hello, World!

  • 33
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值