一、泛型接口核心语法规则
-
泛型接口定义格式:接口名后加 <T>,方法声明处不需要加泛型标识。
-
核心作用:延迟确定数据类型,摒弃普通接口固定类型的弊端,一套接口规范适配任意数据类型。
-
接口内所有占位符 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>
普通接口:固定类型,通用性差
泛型接口:接口定义时不固定类型,由实现类决定类型
- 方法中全部使用占位符 T
参数、返回值全部为泛型类型,适配任意数据类型
- 解决了普通接口弊端:
普通接口写死 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;
}
}
代码解析
-
继承接口
Ical<int>,锁定 T 为整型,方法参数、返回值全部固定为 int。 -
重写接口方法,实现整型专属的加减运算逻辑。
-
特点:逻辑专一、执行高效,但仅支持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;
}
}
解析
-
T 被锁定为 string,方法全部变成字符串操作
-
字符串没有减法运算,所以自定义替换逻辑模拟减法
-
充分体现泛型接口优势:不同类型可以自定义不同实现逻辑
调用结果
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);
}
}
核心规则(必背)
-
如果实现接口时不写死具体类型,仍然用 T
-
类必须也定义成泛型类 <T>
-
最终类型由 创建对象时决定
-
default(T):返回当前泛型类型的默认值
int默认0、string默认null、引用类型默认null
为什么返回默认值?(重难点)
泛型 T 不确定是什么类型:
int 可以 + 、string 可以拼接、但 bool、自定义类 没有加减规则
不是所有类型都支持运算,所以通用泛型类只能返回默认值
重点难点解析
- 为什么返回
default(T)?
泛型 T 可以是 int、string、bool、自定义类等任意类型,并非所有类型都支持加减运算,无法编写统一的运算逻辑,因此用类型默认值兜底,保证语法合法。
-
default(T) 默认值规则:值类型默认0/false,引用类型默认null。
-
特点:全类型通用,实例化时指定什么类型,就适配什么类型。
调用演示
//指定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. 基类约束特点?
限定泛型只能是指定父类或派生子类,实现多态约束。