• C#中的using关键字


    一、前言

    前段时间开始接触在VS下用C#开发(其实是WPF,只是说到VS,往往会扯上C#,而且WPF框架的后台语言确实主要是C#),新建一个WPF应用程序后,在MainWindow.xaml.cs文件开头会有一堆using xxx的语句。
    在这里插入图片描述
    因为以前接触过C++,所以对using也不陌生,毕竟using namespace std这样的语句在C++中非常常见。所以很自然的,也很想当然的认为这边的用法和C++一样,就是引入命名空间,不必深究。

    不过,在最近学习EF Core的过程中,在上下文的使用时,看到了这样的语句,觉得很陌生:

    using (var context = new MyContext ())
    {
        ...
    }
    
    • 1
    • 2
    • 3
    • 4

    其实,这个用法的含义也不难猜,我当时觉得应该是跟context的作用域/生命周期有关,大概是想用完就销毁,不再占用资源。但是呢,知识点这种东西不能一直猜啊,万一猜错了呢,所以我决定还是系统地学习一下。

    二、using学习

    首先C#中的using是个关键字

    啥是关键字呢?
    一开始接触编程时,老师就有教,编程的时候,并不是所有的单词都能拿来命名用的,有一部分单词是保留的,
    对编译器有着特殊含义的,那这些保留的、预定义的单词你可以认为就是关键字(但不绝对)。
    
    • 1
    • 2
    • 3

    using关键字有两个主要的用途:

    • using语句定义了一个作用域,在作用域的尾部将会释放对象
    • using指令为命名空间创建别名或导入其他命名空间中定义的类型

    这边两个用途就对应于前言部分的两种情况。接下来将两种用途展开讲讲。

    1. using指令

    using指令允许你使用在命名空间中定义的类型,而无需指定该类型的全名(有点C系语言中include的味道)。using指令会导入命名空间中的所有类型,如下所示:

    using System.Text;
    
    • 1

    还可以给using指令加上两个修饰符:

    • global,该修饰符与向项目中每个源文件添加相同的using指令具有相同的效果。该修饰符是在C# 10中引入的。
    • static,该修饰符从单个类型导入静态成员和嵌套类型,而不是导入所有类型。它是C#6.0中引入的。

    你可以结合使用这两个修饰符,来达到从项目所有源文件中的类型中导入静态成员的目的。
    你还可以使用using alias directive(别名指令)为命名空间或类型创建别名。

    // using alias = namespace
    using Project = PC.MyCompany.Project;
    
    • 1
    • 2

    它也可以结合global来使用。

    不带global修饰符的using指令的作用域是它所在的文件。using指令可以出现在以下位置:

    • 源码文件的开头,在任何命名空间或类型声明之前。(一般文件中开头一大堆using就是这么用的)
    • 在任意的命名空间中(即空间中套空间),但必须在该命名空间中声明任何命名空间或类型之前(空间中的开头部分),除非使用了global修饰符,在这种情况下,指令必须出现在所有命名空间和类型声明之前。

    否则,会报编译错误CS1529。

    建一个using指令来使用命名空间中的类型时,不必指定命名空间。
    using指令不能访问嵌套在指定的命名空间中的任何命名空间。

    比如 using x;只能访问x中直接包含的内容,
    namespace x 中有 namespace y,那y命名空间中的东西就访问不了了。
    需要你using x.y。
    
    • 1
    • 2
    • 3

    命名空间分两类:用户定义的和系统定义的。
    用户定义的命名空间是在你的代码中定义的。系统定义的命名空间有许多,请参考.NET API Browser.

    1.1. global修饰符

    向using指令添加global修饰符表示将using应用于编译的所有文件(通常是整个项目)。
    它的使用语法:

    // fully-qualified-namespace可以理解为命名空间的全名
    global using <fully-qualified-namespace>;
    
    • 1
    • 2

    一个global using指令可以出现在任意源码文件的开头位置。在单个文件中的所有global using指令必须出现以下部分之前:

    • 所有没有global修饰符的using指令
    • 文件中所有命名空间和类型声明

    一般在使用的时候,往往会将它们放在一个地方。global using指令的顺序并不重要。

    global修饰符可以与static修饰符组合使用。global修饰符可以应用于using alias 指令。在这两种情况下,指令的作用域都是当前编译的所有文件。下面示例使用了System.Math中声明的所有方法:

    global using static System.Math;
    
    • 1

    还可以通过添加<using>项,例如使用<Using Include=“My.Awesome.Namespace” />来包含一个命名空间。

    .NET 6的C#模板使用顶级语句(top level statements)。如果你已经升级到.NET 6,那代码可能与本文的不匹配。

    1.2. static修饰符

    using static指令一个类型,该类型的static成员和嵌套类型无需指定类型名即可访问。
    该指令在C# 6中引入,它的语法如下:

    using static 全名;
    
    • 1

    using static指令适用于任何具有static成员(或嵌套类型)的类型,即使它也具有实例成员。不过,它的实例成员只能由类型实例来调用。

    你可以访问某个类型的静态成员,而不需要指定类型名来访问。

    using static System.Console;
    using static System.Math;
    class Program
    {
        static void Main()
        {
            WriteLine(Sqrt(3*3 + 4*4));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通常,在调用静态成员时,需要提供类型名和成员名。重复输入相同的类型名来调用该类型的成员会导致冗长、晦涩的代码。例如,下面的Circle类定义引用Math类的成员:

    using System;
    
    public class Circle
    {
       public Circle(double radius)
       {
          Radius = radius;
       }
    
       public double Radius { get; set; }
    
       public double Diameter
       {
          get { return 2 * Radius; }
       }
    
       public double Circumference
       {
          get { return 2 * Radius * Math.PI; }
       }
    
       public double Area
       {
          get { return Math.PI * Math.Pow(Radius, 2); }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    通过消除每次引用成员时显式引用Math类,using static指令产生更精简、清晰的代码:

    using System;
    using static System.Math;
    
    public class Circle
    {
       public Circle(double radius)
       {
          Radius = radius;
       }
    
       public double Radius { get; set; }
    
       public double Diameter
       {
          get { return 2 * Radius; }
       }
    
       public double Circumference
       {
          get { return 2 * Radius * PI; }
       }
    
       public double Area
       {
          get { return PI * Pow(Radius, 2); }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    using static导入时,只能访问指定类型中静态成员和嵌套类型。继承的成员不导入。你可以用 using static指令从任何的命名类型导入,包括VB模块。如果F#顶级函数以指定类型的静态成员的形式出现在元数据中,该类型的名称是有效的C#标识符,那么F#函数也能被导入。

    using static使在指定类型中声明的扩展方法可用于扩展方法查找。但是,扩展方法的名称不会被导入到代码中非限定引用范围中。

    从不同的类型中导入相同名称的方法,这些类型在同一编译单元或命名空间中使用不同的using static指令导入的,形成一个方法组(这tm原句妥妥的考研英语长难句,定语好几个,翻译出来贼拗口,反正就是指不同命名空间中方法重名的情况)。这些方法组中的重载解析遵循正常的C#规则。

    下列示例使用using static指令使Console、Math和String类的静态成员可用,而无需指定它们的类型名称:

    using System;
    using static System.Console;
    using static System.Math;
    using static System.String;
    
    class Program
    {
       static void Main()
       {
          Write("Enter a circle's radius: ");
          var input = ReadLine();
          if (!IsNullOrEmpty(input) && double.TryParse(input, out var radius)) {
             var c = new Circle(radius);
    
             string s = "\nInformation about the circle:\n";
             s = s + Format("   Radius: {0:N2}\n", c.Radius);
             s = s + Format("   Diameter: {0:N2}\n", c.Diameter);
             s = s + Format("   Circumference: {0:N2}\n", c.Circumference);
             s = s + Format("   Area: {0:N2}\n", c.Area);
             WriteLine(s);
          }
          else {
             WriteLine("Invalid input...");
          }
       }
    }
    
    public class Circle
    {
       public Circle(double radius)
       {
          Radius = radius;
       }
    
       public double Radius { get; set; }
    
       public double Diameter
       {
          get { return 2 * Radius; }
       }
    
       public double Circumference
       {
          get { return 2 * Radius * PI; }
       }
    
       public double Area
       {
          get { return PI * Pow(Radius, 2); }
       }
    }
    // The example displays the following output:
    //       Enter a circle's radius: 12.45
    //
    //       Information about the circle:
    //          Radius: 12.45
    //          Diameter: 24.90
    //          Circumference: 78.23
    //          Area: 486.95
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    在本例中,using static指令也可以用于Double类型。添加该指令后,可以使得调用TryParse(String, Double)方法而不需要指定类型名。但是,使用不带类型名的TryParse会导致代码的可读性变差,因为需要检查using static指令以确定调用了哪个数字类型的TryParse方法。

    using static也可以应用于enum类型(枚举类型)。通过对enum添加using static引用,该枚举类型不再需要使用enum成员的方式。

    using static Color;
    
    enum Color
    {
        Red,
        Green,
        Blue
    }
    
    class Program
    {
        public static void Main()
        {
            Color color = Green;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    △1.3. 使用别名

    创建using 别名指令,来使标识符更易被识别(用你自己熟悉的名称)。在任何using指令中,必须使用完全限定的名称空间或类型,无论在它之前有哪些using指令。无法在using指令的声明中使用using别名。例如,以下示例会产生一个编译错误:

    using s = System.Text;
    using s.RegularExpressions; // Generates a compiler error.
    
    • 1
    • 2

    以下示例展示了如何为一个命名空间定义并使用一个using别名:

    namespace PC
    {
        // Define an alias for the nested namespace.
        using Project = PC.MyCompany.Project;
        class A
        {
            void M()
            {
                // Use the alias
                var mc = new Project.MyClass();
            }
        }
        namespace MyCompany
        {
            namespace Project
            {
                public class MyClass { }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    using别名指令不能在其右侧有开放的泛型类型(就是未明确定义类型的)。例如,你无法为List<T>创建别名,但你可以为List<int>创建别名。

    以下示例展示了如何为一个类定义一个using指令和一个using别名:

    using System;
    
    // Using alias directive for a class.
    using AliasToMyClass = NameSpace1.MyClass;
    
    // Using alias directive for a generic class.
    using UsingAlias = NameSpace2.MyClass<int>;
    
    namespace NameSpace1
    {
        public class MyClass
        {
            public override string ToString()
            {
                return "You are in NameSpace1.MyClass.";
            }
        }
    }
    
    namespace NameSpace2
    {
        class MyClass<T>
        {
            public override string ToString()
            {
                return "You are in NameSpace2.MyClass.";
            }
        }
    }
    
    namespace NameSpace3
    {
        class MainClass
        {
            static void Main()
            {
                var instance1 = new AliasToMyClass();
                Console.WriteLine(instance1);
    
                var instance2 = new UsingAlias();
                Console.WriteLine(instance2);
            }
        }
    }
    // Output:
    //    You are in NameSpace1.MyClass.
    //    You are in NameSpace2.MyClass.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    △1.4. 如何使用VB My命名空间


    VB部分我大概是不会用到的。

    2. using语句

    提供了便捷的语法来确保正确使用IDisposable对象。从C# 8.0开始,using语句还保证IAsyncDisposable对象的正确使用。

    ???
    我相信初学者看到这一脸懵逼,IDisposable是啥?IAsyncDisposable又是啥?
    跟using语句有啥关系啊?
    我看到这也是这种感觉,mdzz
    但微软文档介绍using语句的时候,就是这么写的,没办法,只能先将IDisposable简单学习下。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.1. IDisposable接口

    IDisposable从命名来看就是个接口。
    Disposable:可任意使用的、一次性的、用完即丢弃的。
    该接口的定义是,提供了一种释放非托管资源的机制

    IDisposable的文档部分非常直白地介绍了该接口的用途:
    此接口的主要用途是释放非托管资源
    当管理的对象不再使用时,GC(.NET平台的垃圾回收器)会自动释放分配给该对象的内存。但是呢,到底何时回收垃圾是无法被预测的。而且,GC也不了解非托管资源,如窗口句柄、或打开的文件和流。
    使用此接口的Dispose方法可以用GC显式地释放非托管的资源。当某个对象不再被需要时,对象的使用者可以调用此方法。

    那么使用实现了IDisposable接口的对象通常有两种方式,
    其中一种就是使用一种语言结构,如C#和VB中的using语句,以及F#中的use语句或using函数。当环境支持该语言结构时,你可以使用它来代替显式地调用IDisposable.Dispose。

    重点看一下C#中的using语句,下面示例用这种方法定义了一个WordCount类,它保留了关于文件和单词数量的信息:

    using System;
    using System.IO;
    using System.Text.RegularExpressions;
    
    public class WordCount
    {
        private String filename = String.Empty;
        private int nWords = 0;
        private String pattern = @"\b\w+\b";
    
        public WordCount(string filename)
        {
            if (!File.Exists(filename))
                throw new FileNotFoundException("The file does not exist.");
    
            this.filename = filename;
            string txt = String.Empty;
            using (StreamReader sr = new StreamReader(filename))
            {
                txt = sr.ReadToEnd();
            }
            nWords = Regex.Matches(txt, pattern).Count;
        }
    
        public string FullName
        { get { return filename; } }
    
        public string Name
        { get { return Path.GetFileName(filename); } }
    
        public int Count
        { get { return nWords; } }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    using语句实际上是一种语法上的便利(语法糖?)。在编译时,语言编译器为try/finally块实现了中间语言(IL)。

    好,现在IDisposable部分到此打住了,可以大致知道using语句其实是隐式地用了IDisposable.Dispose。对不用的对象进行了清理(新手理解到这层应该可以了)。

    2.2. 示例

    现在,回过来看using语句的示例:

    string manyLines = @"This is line one
    This is line two
    Here is line three
    The penultimate line is line four
    This is the final, fifth line.";
    
    using (var reader = new StringReader(manyLines))
    {
        string? item;
        do
        {
            item = reader.ReadLine();
            Console.WriteLine(item);
        } while (item != null);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在C# 8.0引入的using声明是不需要大括号的(我个人觉得还是加了大括号可读性强一点,作用域给标注了嘛):

    string manyLines = @"This is line one
    This is line two
    Here is line three
    The penultimate line is line four
    This is the final, fifth line.";
    
    using var reader = new StringReader(manyLines);
    string? item;
    do
    {
        item = reader.ReadLine();
        Console.WriteLine(item);
    } while (item != null);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.3. 附注

    File和Font是访问非托管资源(这里指的是文件句柄和设备上下文)的托管类型的例子。还有许多其他类型的非托管资源和封装它们的类库类型。所有这些类型都必须实现IDisposable接口,或者IAyncDisposable接口。

    当IDisposable对象的生命周期被限制在单个方法时,你应该在using语句或using声明中声明并实例化它。这句话我觉得就是using语句使用的精髓了,就是using中把你需要限制生命周期的对象给加进去,那么在你大括号框的那段区域就是它的活动空间,超出区域就被回收了。using声明在对象超出作用域时,以正确的方式调用对象的Dispose方法。一旦调用Dispose,using语句就会造成对象本身超出作用域。在using块中,该对象是只读的,不能修改或重新分配。使用using声明声明的变量是只读的。如果对象实现了IAsyncDisposable接口而不是IDisposable,则using 结构调用DisposeAsync并等待返回的ValueTask。

    两种using结构都会保证即使在using块中发生异常也会调用Dispose(或DisposeAsync)。你可以通过将对象放在try块中,然后在finally块中调用Dispose(或DisposeAsync)来实现相同的效果。实际上,编译器就是这样转换using语句和using声明的。上面的代码示例在编译时会展开为以下代码(注意,额外的花括号为对象创建限制范围):

    string manyLines = @"This is line one
    This is line two
    Here is line three
    The penultimate line is line four
    This is the final, fifth line.";
    
    {
        var reader = new StringReader(manyLines);
        try
        {
            string? item;
            do
            {
                item = reader.ReadLine();
                Console.WriteLine(item);
            } while (item != null);
        }
        finally
        {
            reader?.Dispose();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    较新的using语句语法转变成了类似的写法。try块在声明变量的地方打开。finally块添加在封闭块的末尾,通常是方法的末尾。

    一个类型的多个实例也可以在一个using语句中声明,如下示例所示。要注意的是,当你在一条语句中声明多个变量时,你不能使用隐式类型变量(var):

    string numbers = @"One
    Two
    Three
    Four.";
    string letters = @"A
    B
    C
    D.";
    
    using (StringReader left = new StringReader(numbers),
        right = new StringReader(letters))
    {
        string? item;
        do
        {
            item = left.ReadLine();
            Console.Write(item);
            Console.Write("    ");
            item = right.ReadLine();
            Console.WriteLine(item);
        } while (item != null);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    你也可以使用C# 8中引入的声明语法来组合相同类型的多个声明,如下所示:

    string numbers = @"One
    Two
    Three
    Four.";
    string letters = @"A
    B
    C
    D.";
    
    using StringReader left = new StringReader(numbers),
        right = new StringReader(letters);
    string? item;
    do
    {
        item = left.ReadLine();
        Console.Write(item);
        Console.Write("    ");
        item = right.ReadLine();
        Console.WriteLine(item);
    } while (item != null);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    你可以实例化资源对象,然后将变量传给using语句,但这不是最好的使用方式。该情况下,在控制离开using块之后,对象仍然在范围内,但可能无法访问其非托管资源。换句话说,它没有完全初始化。如果你试图在using块外使用对象,就有引发异常的风险。因为这个原因,最好在using语句中实例化对象,并将其作用域限制在using块。

    string manyLines = @"This is line one
    This is line two
    Here is line three
    The penultimate line is line four
    This is the final, fifth line.";
    
    var reader = new StringReader(manyLines);
    using (reader)
    {
        string? item;
        do
        {
            item = reader.ReadLine();
            Console.WriteLine(item);
        } while (item != null);
    }
    // reader is in scope here, but has been disposed
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    三、总结

    C#中的using有两种用法,

    • using指令用于引入命名空间。
    • using语句用于便捷地使用实现了IDisposable接口的对象,使之生命周期限制在指定的作用域内。
  • 相关阅读:
    【分立元件】贴片电阻过电压故障机理
    前端--性能优化【下篇】--框架优化与webpack优化
    【排序算法】计数排序(CountSort)
    如何为集中认证安装OpenLDAP服务器
    【普通人题解】LeetCode 三数之和
    打开记事本
    windows虚拟机中docker运行springboot容器报错:Unable to access jarfile /app.jar
    Python与pycharm-----windows安装与运行
    SpringBoot整合RabbitMQ(最新笔记)
    免费小程序商城搭建之b2b2c o2o 多商家入驻商城 直播带货商城 电子商务b2b2c o2o 多商家入驻商城 直播带货商城 电子商务
  • 原文地址:https://blog.csdn.net/BadAyase/article/details/125495974