天天看点

C# using用法

作者:Edwin2802

using 关键字两个主要用途:

  • using 语句定义一个范围,在此范围的末尾将释放对象。
  • using 指令为命名空间创建别名,或导入在其他命名空间中定义的类型。

一、using语句

using 语句可确保正确使用 IDisposable 实例:

var numbers = new List<int>();
using (StreamReader reader = File.OpenText("numbers.txt"))
{
    string line;
    while ((line = reader.ReadLine()) is not null)
    {
        if (int.TryParse(line, out int number))
        {
            numbers.Add(number);
        }
    }
}           

当控件离开 using 语句块时,将释放获取的 IDisposable 实例。 using 语句可确保即使在 using 语句块内发生异常的情况下也会释放可释放实例。 在前面的示例中,打开的文件在处理完所有行后关闭。

使用 await using 语句来正确使用 IAsyncDisposable 实例:

await using (var resource = new AsyncDisposableExample())
{
    // Use the resource
}           

还可使用不需要大括号的 using 声明:

static IEnumerable<int> LoadNumbers(string filePath)
{
    using StreamReader reader = File.OpenText(filePath);
    
    var numbers = new List<int>();
    string line;
    while ((line = reader.ReadLine()) is not null)
    {
        if (int.TryParse(line, out int number))
        {
            numbers.Add(number);
        }
    }
    return numbers;
}           

在 using 声明中进行声明时,局部变量在声明它的作用域末尾释放。 在前面的示例中,会在某个方法的末尾释放对象。由 using 语句或声明进行声明的变量是只读的。 无法重新分配该变量或将其作为 ref 或 out 参数传递。

可在 using 语句中声明多个相同类型的实例:

using (StreamReader numbersFile = File.OpenText("numbers.txt"), wordsFile = File.OpenText("words.txt"))
{
    // Process both files
}           

在一个 using 语句中声明多个实例时,它们将按声明的相反顺序释放。还可以将 using 语句和声明与适用于可释放模式的 ref 结构的实例一起使用。 也就是说,它有一个实例 Dispose 方法,该方法是可访问、无参数的并且具有 void 返回类型。

using 语句也可以采用expression形式:

StreamReader reader = File.OpenText(filePath);

using (reader)
{
    // Process file content
}           

其中 expression(此例中的reader) 会生成可释放实例。

二、using 指令

using 指令允许使用在命名空间中定义的类型,而无需指定该类型的完全限定命名空间。 using 指令以基本形式从单个命名空间导入所有类型,如下例所示:

using System.Text;           

可以通过 using 别名指令为名称空间或类型创建别名。

using Project = PC.MyCompany.Project;           

可对 using 别名指令使用 global 修饰符。

没有 global 修饰符的 using 指令的作用域是显示它的文件。

可能出现 using 指令的位置:

  • 源代码文件的开头,位于任何命名空间或类型声明之前。
  • 在任何命名空间中,但在该命名空间中声明的任何命名空间或类型之前,除非使用 global 修饰符,在这种情况下,指令必须出现在所有命名空间和类型声明之前。

否则,将生成编译器错误 CS1529。

创建 using 指令,以便在命名空间中使用类型而不必指定命名空间。 using 指令不为你提供对嵌套在指定命名空间中的任何命名空间的访问权限。 命名空间分为两类:用户定义的命名空间和系统定义的命名空间。 用户定义的命名空间是在代码中定义的命名空间。

可以将两个修饰符应用于 using 指令:

  • global 修饰符与将同一 using 指令添加到项目中的每个源文件具有相同的效果。 此修饰符是在 C# 10 中引入的。
  • static 修饰符从单个类型中导入该 static 成员和嵌套类型,而不是导入命名空间中的所有类型。

global 修饰符

向 using 指令添加 global 修饰符意味着 using 将应用于编译中的所有文件(通常是一个项目)。 global using 指令被添加到 C# 10 中。 其语法为:

global using <fully-qualified-namespace>;           

其中 fully-qualified-namespace 是命名空间的完全限定名称,其类型可以在不指定命名空间的情况下引用。

global using 指令可以出现在任何源代码文件的开头。 单个文件中的所有 global using 指令必须出现在以下内容之前:

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

可以将 global using 指令添加到任何源文件中。 通常,你需要将它们保留在一个位置。 global using 指令的顺序并不重要,无论是在单个文件中,还是在文件之间。

global 修饰符可以与 static 修饰符合并。 global 修饰符可以应用于 using 别名指令。 在这两种情况下,指令的作用域都是当前编译中的所有文件。

以下示例允许在项目中的所有文件中使用 System.Math 中声明的所有方法:

global using static System.Math;           

还可以通过将 <Using> 项添加到项目文件(例如 <Using Include="My.Awesome.Namespace" />)来全局包含命名空间。

static 修饰符

using static 指令命名了一种类型,无需指定类型名称即可访问其静态成员和嵌套类型。 其语法为:

using static <fully-qualified-type-name>;           

<fully-qualified-type-name> 是无需指定类型名称即可访问其静态成员和嵌套类型的类型名称。 如果不提供完全限定的类型名称(完整的命名空间名称以及类型名称),则 C# 将生成编译器错误 CS0246:“无法找到类型或命名空间名称‘type/namespace’(是否缺少 using 指令或程序集引用?)”。

using static 指令适用于任何具有静态成员(或嵌套类型)的类型,即使该类型还具有实例成员。 但是,只能通过类型实例来调用实例成员。

你可以访问类型的静态成员,而无需限定使用类型名称进行访问:

using static System.Console;
using static System.Math;
class Program
{
    static void Main()
    {
        WriteLine(Sqrt(3*3 + 4*4));
    }
}           

通常,调用某个静态成员时,即会提供类型名称以及成员名称。 重复输入相同的类型名称来调用该类型的成员将生成详细的晦涩代码。 例如,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); }
   }
}           

通过消除每次引用成员时,显式引用 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); }
   }
}           

using static 仅导入可访问的静态成员和指定类型中声明的嵌套类型。 不导入继承的成员。 可以从任何带 using static 指令的已命名类型导入,包括 Visual Basic 模块。 如果 F# 顶级函数在元数据中显示为一个已命名类型(其名称是有效的 C# 标识符)的静态成员,则可以导入该 F# 函数。

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

同一编译单元或命名空间中通过不同 using static 命令从不同类型导入的具有相同名称的方法组成一个方法组。 这些方法组内的重载解决方法遵循一般 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           

在此示例中,using static 指令也已经应用于 Double 类型。 添加该指令使得在未指定类型名称情况下调用 TryParse(String, Double) 方法成为可能。 但是,使用没有类型名称的 TryParse 创建的代码可读性较差,因为有必要检查 using static 指令,以确定所调用的数值类型的 TryParse 方法。

using static 也适用于 enum 类型。 通过在枚举中添加 using static,该类型不再需要使用枚举成员。

using static Color;

enum Color
{
    Red,Green,Blue
}

class Program
{
    public static void Main()
    {
        Color color = Green;
    }
}           

using 别名

创建 using 别名指令,以便更易于将标识符限定为命名空间或类型。 在任何 using 指令中,都必须使用完全限定的命名空间或类型,而无需考虑它之前的 using 指令。 using 指令的声明中不能使用 using 别名。

例如,以下示例生成一个编译器错误:

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

下面的示例显示如何为命名空间定义和使用 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 { }
        }
    }
}           

using 别名指令的右侧不能有开放式泛型类型。 例如,不能为 List<T> 创建 using 别名,但可以为 List<int> 创建 using 别名。

下面的示例显示如何为类定义 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.           

继续阅读