C#基础5
命名空间:
用来在大范围上区别各个类名,可以防止类名重复的带来的问题
using关键字:
导入命名空间
这是最常见的用法。通过导入一个命名空间,你就可以直接使用该命名空间下的类型,而无需每次都写上完整的命名空间前缀。
// 不使用 using 指令
System.Console.WriteLine("Hello World");
// 使用 using 指令
using System;
// ...
Console.WriteLine("Hello World"); // 代码更简洁
创建别名
当一个类型的名称非常长,或者两个不同命名空间下有同名的类型时,你可以使用 using 为它创建一个简短的别名。
// 为冗长的类型名创建别名
using CustomerList = System.Collections.Generic.List<MyApp.Models.Customer>;
// 现在可以直接使用
CustomerList CustomerList customers = new CustomerList();
导入静态成员
使用 using static 可以直接导入某个类型的静态成员(如方法、常量),调用时无需再写类型名。
using static System.Math;
// 直接使用 PI 和 Pow,而不用写 Math.PI
double area = PI * Pow(radius, 2);
进阶用法:全局和隐式导入
global using: 在 C# 10 及更高版本中,你可以使用global using指令。它的作用范围是整个项目,意味着你只需要在一个文件中声明一次,项目中的所有其他文件都可以使用它,避免了在每个文件中重复书写相同的using。- 隐式
using: 从 .NET 6 开始,项目模板可以启用“隐式 using”,SDK 会自动为项目添加一组最常用的global using指令(如System,System.Collections.Generic等),进一步减少样板代码。
作为语句 (Statement):管理资源
using 语句是 C# 中用于资源管理的核心机制。它的主要作用是确保像文件、网络连接、数据库连接这类“非托管资源”在使用后能被及时、正确地释放,即使在使用期间发生了异常也是如此。
任何实现了 IDisposable 接口的对象,都应该在 using 语句中使用。
工作原理
using 语句定义了一个作用域。当程序执行离开这个作用域时(无论是正常结束还是因为异常),编译器会自动调用对象的 Dispose() 方法来释放资源。这实际上是一种语法糖,编译器会将其转换为 try...finally 块。
标准语法
这是最传统的写法,用一个代码块 {} 包裹资源的使用过程
using (StreamWriter writer = new StreamWriter("example.txt"))
{
writer.WriteLine("Hello, file!");
// 当代码块执行完毕,writer.Dispose() 会被自动调用,文件被安全关闭。
}
简洁语法 (C# 8.0+)
从 C# 8.0 开始,你可以使用更简洁的 using 声明。它不需要代码块,资源会在当前作用域(例如整个方法)结束时被自动释放。
void WriteToFile()
{
using StreamWriter writer = new StreamWriter("example.txt");
writer.WriteLine("Hello, file!");
// 方法结束时,writer 会被自动释放。
}
异步资源管理
对于实现了 IAsyncDisposable 接口的对象,可以使用 await using 语句来异步地释放资源。
await using (var resource = new MyAsyncResource())
{
// 使用异步资源
}
// 离开作用域时,会自动调用 resource.DisposeAsync()
泛型
允许我们延迟编写类或方法中的数据类型,直到真正使用时确定类型的一种规范
1、可以创建自己的泛型接口、泛型类、泛型方法、泛型集合,泛型事件和泛型委托
2、泛型的格式:结构<泛型的名称>在定义泛型时T通常用作变量类型名称,但实际上T可以用任何有效名称代替
3、泛型方法在定义时提供泛型的类型 在调用方法时 提供具体的延迟编写的类型
4、泛型无法直接使用运算符比如+-<>等 但是能Object中的属性和方法
泛型方法
public static void Test<T>(T a,T b)
{
string temp =a.ToString();
string temp1 =b.ToString();
Console.WriteLine(temp1+temp);
}
public static void Test1<T,鸡>(T a,T b,鸡 c)
{
string temp =a.ToString();
string temp1=b.ToString();
Console.WriteLine(c.GetType().ToString());
Console.WriteLine(temp1+temp);
}
泛型类
C#语言中泛型类的定义与泛型方法类似,是在泛型类的名称后面加上<T>,当然,也可以定义多个类型,即"T1,T2,·····"
class people<T,C>
{
private T_age;
public T Age { get;set;}
public T Eat(C a,C b){
return _age;
}
}
泛型接口
interface INter<T>
{
void show(T t);
}
//定义接口Inter的子类InterImpA,明确泛型类型为string (2)
public class InterImpA : Inter<int>
{
//子类InterImpA重写方法show,指明参数类型为string
public void show(int t)
{
Console.WriteLine(t);
}
}
//定义接口Inter的子类InterImpB,直接声明 (1)
public class InterImpB<T> :Inter<T>
{
public void show(T t)
{
Console.WriteLine(t);
}
}
C#内置了IComparable接口用于在要比较的对象的类中实现,可以比较任意两个对象
public class Stydent : Icomparable<Student>
{
int Age {get;set}
//定义比较方法,按照学生的年龄比较
public int CompareTo(Student other)
{
if(this.Age==other.Age)
{
return 1;
}
else if (this.Age<other.Age)
{
return 2;
}
else
{
return 3;
}
}
}
2.泛型可以提供代码性能,避免了装箱和拆箱
泛型和Object类型的区别
值类型转换成引用类型。这个转换称为装箱。相反的过程称为拆箱
int number=10;
//装箱
object obj=number;
//拆箱
number = (int) obj;
C#中 Object是一切类型的基类,可以用来表示所有类型
Object类型
>优点:
>1、object 类型可以用来引用任何类型的实例;
>2、object 类型可以存储任何类型的值;
>3、可以定义object类型的参数
>4、可以把object作为返回类型
>缺点:
>1、会因为程序员没有记住使用的类型而出错,造成类型不兼容;
>2、值类型和引用类型的互化即装箱使系统性能下降
装箱(从值类型转换到引用类型)需要经历如下几个步骤:
int a=10;
object ob=a;
首先在堆上分配内存。这些内存主要用于存储值类型的数据。
接着发生一次内存拷贝动作,将当前存储位置的值类型数据拷贝到堆上分配好的位置。
最后返回对堆上的新存储位置的引用。
拆箱(从引用类型转换为值类型)的步骤则相反:
//拆箱
number =(int) obj;
首先检查已装箱的值的类型兼容目标类型。
接着发生一次内存拷贝动作,将堆中存储的值拷贝到栈上的值类型实例中。
最后返回这个新的值。
频繁拆装箱导致性能问题
由于拆箱和装箱都会涉及到一次内存拷贝动作,因此频繁地进行拆装箱回大幅影响性能
泛型和装箱拆箱 对比
public void abTest(object a,object b)
{
Console.WriteLine(a.ToString());
Console.WriteLine(b.ToString());
}
public void abTest(T a,T b)
{
Console.WriteLine(a.ToString());
Console.WriteLine(b.ToString());
}
3、泛型中地数据约束可以指定泛型类型的范围
泛型约束总共有五种.
约束 说明
T:结构 类型参数必须是值类型
T:类 类型参数必须是引用类型:这一点也适用于任何类、接口、委托或数组类型。
T:new() 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new()约束必须最后指定。
T:<基类名> 类型参数必须时指定的基类或派生自指定的基类。
T:<接口名称> 类型参数必须时指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以时泛型的。
where T:struct //约束T必须时值类型
个人理解
T是类型占位符 ,表示还没有决定具体是什么类型,先把这个位置站住
//占位符不一定非要用T,也可以用其他字符或者名字
对泛型类名字的特化,特化为数据类型的类
概念:使用占位符T来代表某种类型,编译期间决定其 具体类型
格式:class My<T>
使用:My<int> mg= new My<T>();
原理:编译器在编译的时候,会使用特化的类型替代掉类型占位符,生成绝体的class代码
通过 ”dynamic(动态)“ 将类型校验推迟到运行时
public static T add<T>(T a,T b)
{
dynamic? da=a;
dynamic? db=b;
return da+db;
}
泛型细节
泛型可以同时提供多种数据类型的占位符(类/方法均有效)
泛型类可以被继承,子类可以指定父类泛型的具体类型(特化)或者子类也作为泛型类
泛型类继承泛类类要泛型占位符名字一致
先将父类进行特化,然后在继承
泛型约束
泛型约束:对泛型中传入的类型进行“校验”,规定其必须满足的条件
格式
public class 名字<T> where T:条件
{}
条件:
where<T>:class 指泛型必须是引用类型
where<T>:struct 指泛型必须是值类型
where<T>:new() 指泛型必须包含无参构造方法的class 如果和其他约束共用,则放在最后面
where<T>:类名 指泛型必须是某个类或派生自某类 如果和接口使用 要放在接口前面
where<T>:接口名字,…… 指泛型必须实现一个或多个接口
where<T,B>where T:条件:where B:条件 多占位符多条件约束
泛型约束与继承
拥有泛型约束的类作为父类,则子类可以选择特化父类,或者 使用与父类同样/更严格的约束
class Ma{}
class Mb:Ma{}
class AA<T>:where T:Ma{}
特化父类
public class MTest:AA<Ma>{}
public class MTest:AA<Mb>{}
与父类相同约束
public class MTest<T>:AA<B>:where T:Ma{}
更加严格的约束
public class MTest<T>:AA<B>:where T:Mb{}
更多推荐

所有评论(0)