命名空间:

用来在大范围上区别各个类名,可以防止类名重复的带来的问题

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 指令(如 SystemSystem.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{}

更多推荐