C# 泛型接口和泛型类+泛型约束

一、泛型接口核心语法规则

  1. 泛型接口定义格式:接口名后加 <T>,方法声明处不需要加泛型标识。

  2. 核心作用:延迟确定数据类型,摒弃普通接口固定类型的弊端,一套接口规范适配任意数据类型。

  3. 接口内所有占位符 T,最终类型由实现类决定。

1.1 泛型接口完整定义(Ical<T>)

复制代码
internal interface Ical<T>
{
    //通用泛型相加方法:参数、返回值均为泛型T,支持任意类型
    T Add(T a, T b);
    //通用泛型相减方法:参数、返回值均为泛型T,支持任意类型
    T Sub(T a, T b);
}

逐句解析

1.泛型接口格式:接口名后加 <T>

普通接口:固定类型,通用性差

泛型接口:接口定义时不固定类型,由实现类决定类型

  1. 方法中全部使用占位符 T

参数、返回值全部为泛型类型,适配任意数据类型

  1. 解决了普通接口弊端:

普通接口写死 int 只能做数字运算;泛型接口可以支持 int、string、自定义类等所有类型。

1.2 新旧接口对比解析

普通固定类型接口弊端:如定义 int Add(int a,int b),只能实现数字相加,无法适配字符串、其他自定义类型,扩展性极差。

泛型接口优势:使用 T 作为类型占位符,不固定具体类型,实现类可根据需求自定义 int、string 等类型的业务逻辑,通用性极强。

二、泛型接口的两种实现方式(核心考点)

方式一:实现类指定【固定具体类型】(非泛型类实现)

核心规则:实现接口时直接写明具体数据类型(int/string),接口中所有 T 会被替换为指定类型,实现类无需定义泛型。

案例1:Calc2 实现 Ical<int>(整型运算)实现接口时【直接写死类型】(固定类型实现)

复制代码
// 明确指定接口泛型类型为int,所有T替换为int
public class Calc2 : Ical<int>
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public int Sub(int a, int b)
    {
        return a - b;
    }
}
代码解析
  1. 继承接口 Ical<int>,锁定 T 为整型,方法参数、返回值全部固定为 int。

  2. 重写接口方法,实现整型专属的加减运算逻辑。

  3. 特点:逻辑专一、执行高效,但仅支持int类型,无法复用其他类型

调用代码
复制代码
Calc2 c2 = new Calc2();
Console.WriteLine(c2.Add(10, 20)); //30
Console.WriteLine(c2.Sub(10, 20)); //-10

方式2:固定 string 类型实现(自定义逻辑)

示例:Calc3 实现 Ical<string>
复制代码
// 明确指定接口泛型类型为string,所有T替换为string
public class Calc3 : Ical<string>
{
    //字符串相加:直接拼接
    public string Add(string a, string b)
    {
        return a + b;
    }

    //字符串没有原生减法,自定义替换逻辑模拟相减
    public string Sub(string a, string b)
    {
        if (a.Contains(b))
        {
            // 将匹配到的子串替换为x,模拟减法效果
            return a.Replace(Convert.ToChar(b) ,'x');
        }
        return a;
    }
}
解析
  1. T 被锁定为 string,方法全部变成字符串操作

  2. 字符串没有减法运算,所以自定义替换逻辑模拟减法

  3. 充分体现泛型接口优势:不同类型可以自定义不同实现逻辑

调用结果
复制代码
Calc3 c3 = new Calc3();
c3.Add("a", "a") //输出 aa
c3.Sub("a", "a") //把a替换成x,输出 x

方式二:泛型类实现泛型接口(通用万能实现)

核心规则:若实现接口时不指定具体类型,继续使用占位符 T,当前类必须定义为泛型类(类名后加 <T>),最终类型由对象实例化时决定。

示例:Calc1<T> : Ical<T>
复制代码
// 类定义泛型T,沿用接口泛型,类型延迟到实例化时确定
public class Calc1<T> : Ical<T>
{
    public T Add(T a, T b)
    {
        // 任意类型不统一支持加减运算,使用默认值兜底
        return default(T);
    }

    public T Sub(T a, T b)
    {
        return default(T);
    }
}
核心规则(必背)
  1. 如果实现接口时不写死具体类型,仍然用 T

  2. 类必须也定义成泛型类 <T>

  3. 最终类型由 创建对象时决定

  4. default(T):返回当前泛型类型的默认值

int默认0、string默认null、引用类型默认null

为什么返回默认值?(重难点)

泛型 T 不确定是什么类型:

int 可以 + 、string 可以拼接、但 bool、自定义类 没有加减规则

不是所有类型都支持运算,所以通用泛型类只能返回默认值

重点难点解析
  1. 为什么返回 default(T)

泛型 T 可以是 int、string、bool、自定义类等任意类型,并非所有类型都支持加减运算,无法编写统一的运算逻辑,因此用类型默认值兜底,保证语法合法。

  1. default(T) 默认值规则:值类型默认0/false,引用类型默认null。

  2. 特点:全类型通用,实例化时指定什么类型,就适配什么类型。

调用演示
复制代码
//指定int类型
Calc1<int> c = new Calc1<int>();
c.Add(10,20); //返回 0

//指定string类型
Calc1<string> c1 = new Calc1<string>();
c1.Sub("ss", "sss"); //返回 null

三、Main 方法调用测试 & 运行结果解析

复制代码
static void Main(string[] args)
{
    // 1.整型运算测试 Calc2
    Calc2 c2 = new Calc2();
    Console.WriteLine(c2.Add(10, 20));  // 10+20=30
    Console.WriteLine(c2.Sub(10, 20));  // 10-20=-10

    // 2.字符串运算测试 Calc3
    Calc3 c3 = new Calc3();
    Console.WriteLine(c3.Sub("a", "a"));// 替换a为x,输出x
    Console.WriteLine(c3.Add("a", "a"));// 字符串拼接,输出aa

    // 3.泛型类 整型实例测试
    Calc1<int> c = new Calc1<int>();
    Console.WriteLine(c.Add(10, 20));   // int默认值 0

    // 4.泛型类 字符串实例测试
    Calc1<string> c1 = new Calc1<string>();
    Console.WriteLine(c1.Sub("ss", "sss"));// string默认值 null
}

四.1三种实现方式对比总结

实现方式 写法 特点 场景
固定值类型实现 class Calc2:Ical<int> 类型固定、逻辑专属、效率高 只处理单一类型
固定引用类型实现 class Calc3:Ical<string> 可自定义特殊逻辑 特殊类型需要自定义运算
泛型类实现接口 class Calc1<T>:Ical<T> 全局通用、类型灵活、可适配所有类型 通用工具类

四.2两种实现方式对比总结(必背)

实现方式 代码写法 特点 适用场景
固定具体类型实现 类 : 接口<具体类型> 类型固定、可自定义专属逻辑、效率高、不可复用 单一类型专属业务逻辑
泛型类通用实现 类<T> : 接口<T> 类型灵活、全类型通用、无专属逻辑 通用工具类、不确定类型的公共逻辑

五、核心考点标准答案(简答题必背)

1. 泛型接口是什么?

泛型接口是在接口名称后加 <T> 定义的可延迟确定类型的接口,定义接口时不固定参数和返回值类型,由实现类确定具体类型,解决普通接口只能固定单一类型的弊端。

2. 泛型接口两种实现方式?

具体类型实现:实现接口时直接填写具体类型(int/string),类中方法全部锁定对应类型。

泛型类实现:实现接口仍然使用T,当前类必须定义为泛型类,在实例化对象时确定具体类型。

3. 为什么通用泛型方法只能返回 default(T)?

因为泛型 T 可以是任意类型,不是所有类型都支持加减运算,为了保证语法通用合法,只能返回该类型的默认值。


六、核心知识点总结(简答题满分答案)

1. 泛型接口定义规范:接口名称后添加 <T> 声明泛型,接口内方法仅用 T 占位,无需重复声明泛型。

2. 泛型接口实现规则

① 实现时指定具体类型:普通类实现,接口所有 T 被固定类型替换;

② 实现时沿用 T 占位:类必须定义为泛型类,类型延迟至实例化时确定。

3. 泛型接口优势:解决普通接口类型固定、扩展性差的问题,实现接口规范复用,适配任意数据类型。

七、终极背诵口诀

接口加T变泛型,类型延迟不锁定

实现写死固定型,专属逻辑跑得通

实现沿用T占位,类必须带T通用

万物类型不统一,默认兜底保兼容

------------------------泛型约束------------------------

一、泛型约束核心概念(必背)

泛型默认特点:<T> 不限制类型,可以是值类型、引用类型、自定义类、接口等任意类型。

泛型约束作用 :通过 where T : 约束条件缩小泛型类型范围,强制 T 必须满足指定规则,否则编译报错。

语法格式

复制代码
方法(参数) where T : 约束条件

二、无约束泛型方法(Test1)

代码

复制代码
//没有任何限制的泛型方法
static void Test1<T>( T a)
{
    Console.WriteLine("Test1");
}

解析

无 where 约束:T 可以是任意类型(int、string、数组、自定义类、接口)。


三、约束1:struct 值类型约束

代码

复制代码
//1要求泛型只能是值类型
static void Test2<T>(T a) where T : struct
{
    Console.WriteLine("Test1");
}

规则

where T : struct

T 只能是值类型:int、double、bool、枚举、结构体

禁止:所有引用类型(string、数组、自定义类、接口)

代码报错解释

复制代码
Test2(10); //正确 int值类型
//Test2(new int[] {1}); //报错!数组是引用类型

四、约束2:class 引用类型约束

代码

复制代码
//2 要求泛型只能是【引用类型】
static void Test3<T>(T a) where T : class
{
    Console.WriteLine("Test1");
}

规则

where T : class

T 只能是引用类型:string、数组、自定义类、接口

禁止:所有值类型(int、double、bool)

代码报错解释

复制代码
//Test3(10); 报错!int是值类型
Test3("ss"); 正确!string是引用类型

五、约束3:new() 无参构造函数约束(重难点)

代码

复制代码
//3 要求类里面必须有【无参构造函数】
static void Test4<T>(T a) where T : new()
{
    Console.WriteLine("Test1");
}

规则

where T : new()

要求:传入的类型 必须拥有无参构造函数

适用:自定义类、系统类

注意:值类型天生有无参构造,默认满足;自定义类如果只写了有参构造,会报错。

本案例匹配

People 类手动写了有参构造,但是保留了无参构造,所以可以正常传入。

复制代码
Test4(new People()); //正确

六、约束4:基类约束(父类约束)

代码

复制代码
//4 要求泛型必须是指定类 或者 该类的派生类
static void Test5<T>(T a) where T : People
{
    Console.WriteLine("Test1");
}

规则

where T : 父类名

T 只能是:当前类本身 + 所有子类

本案例结构

复制代码
class People { }
class Stu : People { }

调用解析

复制代码
Test5(new People()); //父类本身 正确
Test5(new Stu());    //子类派生类 正确

七、约束5:接口约束

代码

复制代码
interface IPeople { }
class SS:IPeople { }

//5 要求泛型必须实现该接口 或接口的派生类
static void Test6<T>(T a) where T : IPeople
{
    Console.WriteLine("Test1");
}

规则

where T : 接口名

T 必须是:实现了该接口的类 / 接口本身

调用解析

复制代码
Test6(new SS()); //SS实现了IPeople 正确

八、所有约束总表(必考背诵)

  • where T : struct → 只能是值类型

  • where T : class → 只能是引用类型

  • where T : new() → 必须有无参构造函数

  • where T : 父类名 → 只能是该类或其子类

  • where T : 接口名 → 必须实现该接口


九、运行代码全部结果汇总

复制代码
Test2(10);          //通过 值类型
//Test2(数组)         //报错 引用类型

//Test3(10)          //报错 值类型
Test3("ss");         //通过 引用类型

Test4(new People()); //通过 有无参构造

Test5(new People()); //通过 父类本身
Test5(new Stu());    //通过 子类

Test6(new SS());     //通过 实现接口

十、满分简答题总结

1. 为什么需要泛型约束?

泛型默认任意类型,范围太广。通过 where 约束可以限制泛型类型范围,保证传入类型符合业务要求,实现代码安全、逻辑可控。

2. new() 约束特点?

强制类型必须有无参构造函数,常用于需要在方法内部创建对象的场景。

3. 基类约束特点?

限定泛型只能是指定父类或派生子类,实现多态约束。

相关推荐
阿正的梦工坊1 小时前
【Rust】02-变量、不可变性与基础类型
开发语言·后端·rust
阿正的梦工坊1 小时前
【Rust】08-集合类型、字符串与迭代器入门
开发语言·rust·c#
FuckPatience2 小时前
C# 使用泛型协变将派生类类型替换为基类类型
开发语言·c#
张忠琳2 小时前
【Go 1.26.4】(Part 1) Go 1.26.4 超深度源码分析 — 总体架构与模块全景
开发语言·golang
guygg882 小时前
C# 生成中间带 Logo 头像的二维码
开发语言·c#
闪电悠米2 小时前
黑马点评-Redis 消息队列-03_stream_consumer_group
开发语言·数据库·redis·分布式·缓存·junit·lua
8125035332 小时前
第 9 篇:子网掩码:如何划分“小区”
开发语言·php
Jun6262 小时前
QT(12)-制作lib库
开发语言·qt
Java面试题总结2 小时前
C#12 中的 Using Alias
开发语言·windows·c#