C#中的using用法总结
using一般有两个作用:
1、作为语句,用于定义一个范围,在此范围的末尾将释放对象(IDisposable和IAsyncDisposable接口)
2、作为指令,用于引入命名空间或者类型,或者为引入的命名空间或者类型定义别名
using语句
using语句应该都很熟悉了吧,从最早的ADO.net,或者对文件、流的操作,一般都会使用using语句吧。
using(await using)语句的作用对象是实现了IDisposable(IAsyncDisposable)接口的对象,而实现IDisposable或者IAsyncDisposable接口的对象一般都是一些非托管对象,比如文件,我们在操作完非托管对象后,一般需要释放它们,而using(await using)就类似一个语法糖,它为IDisposable(IAsyncDisposable)接口的对象的提供了一个作用范围,在退出这个作用返回的时候,会自动调用Dispose(DisposeAsync)方法。
IAsyncDisposable是IDisposable的异步形式,一个简单的使用例子:
public static async Task Main(string[] args)
{
//using针对IDisposable接口对象
using (var reader = new StreamReader("text.log"))
{
var text = reader.ReadToEnd();
}
//await using针对IAsyncDisposable接口对象
await using (var myUnmanagedObject = new MyUnmanagedObject())
{
var str = myUnmanagedObject.ToString();
}
}
class MyUnmanagedObject : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.CompletedTask;
Console.WriteLine("DisposeAsync");
}
}
其实using(await using)的作用等价于下面的形式:
public static async Task Main(string[] args)
{
//using针对IDisposable接口对象
var reader = new StreamReader("text.log");
try
{
var text = reader.ReadToEnd();
}
finally
{
reader.Dispose();
}
//await using针对IAsyncDisposable接口对象
var myUnmanagedObject = new MyUnmanagedObject();
try
{
var str = myUnmanagedObject.ToString();
}
finally
{
await myUnmanagedObject.DisposeAsync();
}
}
也就是说,在using(await using)的范围内,无论有没有发生错误,都会自动调用Dispose(DisposeAsync)方法。
为了更方便的使用using(await using),我们还可以省略大括号,这意味着,using(await using)的作用区间就是从using(await using)开始至变量退出的区间,可以简单的理解为可以使用这个变量的范围区间,比如:
public static async Task Main(string[] args)
{
//using针对IDisposable接口对象
using var reader = new StreamReader("text.log");
var text = reader.ReadToEnd();
//await using针对IAsyncDisposable接口对象
await using var myUnmanagedObject = new MyUnmanagedObject();
var str = myUnmanagedObject.ToString();
//退出方法前会自动调用Dispose或者DisposeAsync方法
//也就是说using的范围是using开始至方法结尾
}
using指令
using指令常用于引入命名空间或者类,目前(C#10)一般有4种格式:
1、引入命名空间:using [命名空间]
比如:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ClassLibrary
{
using System.IO;
using System.Drawing;
//其它代码
}
using的位置
1、源代码文件的开头,位于任何命名空间或类型声明之前。
2、如果在任何命名空间中,则必须在该命名空间中的所有命名空间和类型之前
2、与global结合(C#10提供):global using [命名空间]
比如:
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Text;
global using System.Threading.Tasks;
global using和using的区别是,using只是将命名空间作用于当前的源文件,而global using就是将命名空间作用于当前项目的所有源文件,这样就不需要我们每个文件都去引入了。
这是很好用的一个语法糖,我们可以在任何一个C#的源文件中使用global using,但是global using必须出现在文件开头,包括在那些没有global的using开头!
注:global using的作用,我们也可以通过修改csproj文件还完成:
<ItemGroup>
<Using Include="System"/>
<Using Include="System.Collections.Generic"/>
<Using Include="System.Linq"/>
<Using Include="System.Text"/>
<Using Include="System.Threading.Tasks"/>
</ItemGroup>
3、与static结合:using static [全限定名称的类型]
using static的作用就是将指定的类型的静态成员展开,这样我们在用的时候就不需要再写类型了,而是直接使用方法就可以了:
using static System.Console;
namespace ClassLibrary
{
class Program
{
void Main(string[] args)
{
WriteLine("Hello World!");
//等价于
Console.WriteLine("Hello World!");
}
}
}
使用using static时,需要注意几点:
1、using static可以作用于任何类型,包括结构体、嵌套类型等,但是使用时必须使用全限定名称(命名空间+类型名成)
2、当using static导致方法名称冲突时,需要使用通常的类型来指明,比如类中已经定义了一个WriteLine方法,那么就需要显示的用类型来指明
3、using static只会导入自定义的静态成员,不会导入父类的静态成员
此外,static也可以与global一起使用,表示往项目中的每个源文件都导入指定类型的静态成员,比如:
global using static System.Console;
namespace ClassLibrary
{
class Program
{
void Main(string[] args)
{
WriteLine("Hello World!");
//现在你可以在当前项目的其它原文件中使用Console中的静态成员了
}
}
}
注:同样,我们可以通过修改csproj项目文件来实现global using static 的相关:
<ItemGroup>
<Using Include="System.Console" Static="true"/>
</ItemGroup>
4、定义别名:using [别名]=[命名空间|类型]
别名用于简化我们的代码,using定义的别名可以作用于命名空间,或者类型,比如:
namespace ClassLibrary
{
using s = System;
using c = System.Console;
class Program
{
void Main(string[] args)
{
var now = s.DateTime.Now;
c.WriteLine("Hello World!");
}
}
}
需要注意的是,这里的using,无论是对命名空间,还是类型,都必须使用全限定名称!
当然,using别名也可以与global结合,这里就不重复叙述了。
结语
说到using引入命名空间,不知道大家有没有考虑过这么一个问题,假如有两个dll,里面都有一个相同名称,相同命名空间的类型,如果我们同一个项目中引用这两个dll,我们怎么指定使用哪个类型?
例如:LibraryA项目有个类Library.MyClass,LibraryB项目有个类Library.MyClass,如果我们有一个LibraryC项目同时引用了LibraryA和LibraryB,那么在LibraryC中要使用LibraryA的Library.MyClass,如果直接引用Library.MyClass,那么肯定会报错,因为程序不知道到底是LibraryA还是LibraryB。这个时候,我们需要定义一个命名来区分。
在我们引用项目或者dll文件的依赖项中,右键依赖项=》属性,开发属性面板,有个别名的栏位,我们可以将LibraryA和LibraryB定义两个不一样的别名:
此外,我们也可以通过修改csproj项目文件来添加别名:
<ItemGroup>
<ProjectReference Include="..\LibraryA\LibraryA.csproj">
<Aliases>A</Aliases>
</ProjectReference>
<ProjectReference Include="..\LibraryB\LibraryB.csproj">
<Aliases>B</Aliases>
</ProjectReference>
</ItemGroup>
接着在代码中使用extern alias声明一个外面的别名,就可以正常使用了:
extern alias A;
extern alias B;
namespace ClassLibrary
{
class Program
{
void Main(string[] args)
{
var ClassA = new A::Library.MyClass();
var ClassB = new B::Library.MyClass();
}
}
}
此时,using别名就提供了一个很好的作用:
extern alias A;
extern alias B;
global using LibraryA = A::Library;
global using LibraryB = B::Library;
namespace ClassLibrary
{
class Program
{
void Main(string[] args)
{
var ClassA = new LibraryA.MyClass();
var ClassB = new LibraryB.MyClass();
}
}
}
剩下的工作就交给LibraryA、LibraryB...
参考文档:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive