<C#10 in a Nutshell> 读书笔记摘抄 和你一起成长,到我的主页,关注更多C#教程
空值运算符 Null Operators
C# 提供了三个运算符来简化处理空值的操作:空值合并运算符、空合并赋值运算符和空条件运算符(null-coalescing operator, the null-coalescing assignment operator, and the null-conditional operator)。
空合并运算符 Null-Coalescing Operator
?? 运算符是空合并运算符。也就是说,“如果左边的操作数是非空,给我;否则,给我另一个值。”例如:
string s1 = null;
string s2 = s1 ?? &#34;nothing&#34;; // s2 evaluates to &#34;nothing&#34;
如果左边的表达式不为空,则永远不会计算右边的表达式。 空合并运算符也适用于可为空的值类型。
空合并赋值运算符 Null-Coalescing Assignment Operator
??= 运算符(在 C# 8 中引入)是 null 合并赋值运算符。它表示,“如果左边的操作数为空,则将右边的操作数赋值给左边的操作数。” 考虑以下:
myVariable ??= someDefault;
这相当于:
if (myVariable == null) myVariable = someDefault;
??= 运算符在实现惰性计算属性时特别有用。
空条件运算符 Null-Conditional Operator
?. 运算符是空条件运算符或“Elvis”运算符(在Elvis表情符号之后)。 它允许您像标准点运算符一样调用方法和访问成员,除了如果左边的操作数为空,则表达式的计算结果为空,而不是抛出 NullReferenceException异常:
System.Text.StringBuilder sb = null;
string s = sb?.ToString(); // No error; s instead evaluates to null
最后一行等效于以下内容:
string s = (sb == null ? null : sb.ToString());
Null 条件表达式也适用于索引器:
string foo = null;
char? c = foo?[1]; // c is null
遇到空值时,Elvis 运算符将其余部分短路表达。在以下示例中,即使在ToString() 和 ToUpper() 之间的使用标准点运算符,s 的计算结果也是 null:
System.Text.StringBuilder sb = null;
string s = sb?.ToString().ToUpper(); // s evaluates to null without error
仅当紧靠其左侧的操作数可能时,才需要重复使用 Elvis为空。以下表达式对于 x 为 null 和 x.y 为 null 都是稳健的:
x?.y?.z
它等效于以下内容(除了 x.y 仅计算一次):
x == null ? null
: (x.y == null ? null : x.y.z)
最终表达式必须能够接受空值。以下是非法的:
System.Text.StringBuilder sb = null;
int length = sb?.ToString().Length; // Illegal : int cannot be null
我们可以通过使用可空值类型来解决这个问题:
int? length = sb?.ToString().Length; // OK: int? can be null
您还可以使用 null 条件运算符来调用 void 方法:
someObject?.SomeVoidMethod();
如果 someObject 为 null,这将变为“无操作”而不是抛出ReferenceException异常。 您可以将 null 条件运算符与常用的类型成员一起使用,包括方法、字段、属性和索引器。它 还与空合并运算符结合得很好:
System.Text.StringBuilder sb = null;
string s = sb?.ToString() ?? &#34;nothing&#34;; // s evaluates to &#34;nothing&#34;
语句 Statements
函数包含语句,这些语句按出现的顺序来顺序执行。语句块statement block是出现在大括号({} 标记)中间的一系列语句。
声明语句 Declaration Statements
变量声明引入了一个新变量,可以选择用一种表达初始化它。您可以在逗号分隔中声明多个相同类型的变量列表:
string someWord = &#34;rosebud&#34;;
int someNumber = 42;
bool rich = true, famous = false;
常量声明类似于变量声明,只是它在声明之后不能更改,初始化必须与声明一起发生:
const double c = 2.99792458E08;
c += 10; // Compile-time Error
局部变量 Local variables
局部变量或局部常量的范围扩展到整个当前块。您不能在当前块中或在任何嵌套块中声明另一个具有相同名称的局部变量:
int x;
{
int y;
int x; // Error - x already defined
}
{
int y; // OK - y not in scope
}
Console.Write (y); // Error - y is out of scope
变量的作用域在其整个代码块中向两个方向延伸。这意味着这个例子中如果我们移动初始声明 x 到方法的底部,我们会得到同样的错误。这与 C++ 形成对比,有点奇特,因为在声明一个变量或常量之前引用它是不合法的。 表达式语句 Expression Statements
表达式语句是同时也是有效语句的表达式。一种表达语句必须要么改变状态要么调用可能改变状态的东西。改变状态本质上意味着改变一个变量。以下是可能的表达式语句:
- 赋值表达式(包括递增和递减表达式)
- 方法调用表达式(无效和非无效)
- 对象实例化表达式
这里有些例子:
// Declare variables with declaration statements:
string s;
int x, y;
System.Text.StringBuilder sb;
// Expression statements
x = 1 + 2; // Assignment expression
x++; // Increment expression
y = Math.Max (x, 5); // Assignment expression
Console.WriteLine (y); // Method call expression
sb = new StringBuilder(); // Assignment expression
new StringBuilder(); // Object instantiation expression
当您调用构造函数或方法返回值一个值时,您没有义务使用这个返回值。但是,除非构造函数或方法改变状态,否则语句完全没用:
new StringBuilder(); // Legal, but useless
new string (&#39;c&#39;, 3); // Legal, but useless
x.Equals (y); // Legal, but useless
选择语句 Selection Statements
C#有以下机制来有条件地控制程序的流程执行:
- 选择语句(if、switch)
- 条件运算符 (?:)
- 循环语句(while、do-while、for、foreach)
本节介绍最简单的两种结构:if 语句和 switch语句。
The if statement
如果 bool 表达式为真,则 if 语句执行语句:
if (5 < 2 * 3)
Console.WriteLine (&#34;true&#34;); // true
该语句可以是一个代码块:
if (5 < 2 * 3)
{
Console.WriteLine (&#34;true&#34;);
Console.WriteLine (&#34;Let’s move on!&#34;);
}
The else clause
if 语句可以有选择地包含 else 子句:
if (2 + 2 == 5)
Console.WriteLine (&#34;Does not compute&#34;);
else
Console.WriteLine (&#34;False&#34;); // False
在 else 子句中,您可以嵌套另一个 if 语句:
if (2 + 2 == 5)
Console.WriteLine (&#34;Does not compute&#34;);
else
if (2 + 2 == 4)
Console.WriteLine (&#34;Computes&#34;); // Computes
用大括号改变执行流程
else 子句始终适用于紧接在前面的 if 语句语句块:
if (true)
if (false)
Console.WriteLine();
else
Console.WriteLine (&#34;executes&#34;);
这在语义上与以下内容相同:
if (true)
{
if (false)
Console.WriteLine();
else
Console.WriteLine (&#34;executes&#34;);
}
我们可以通过移动大括号来改变执行流程:
if (true)
{
if (false)
Console.WriteLine();
}
else
Console.WriteLine (&#34;does not execute&#34;);
使用大括号,您可以明确说明您的意图。这样可以提高可读性嵌套的 if 语句——即使编译器不需要。一个值得注意的例外具有以下模式:
void TellMeWhatICanDo (int age)
{
if (age >= 35)
Console.WriteLine (&#34;You can be president!&#34;);
else if (age >= 21)
Console.WriteLine (&#34;You can drink!&#34;);
else if (age >= 18)
Console.WriteLine (&#34;You can vote!&#34;);
else
Console.WriteLine (&#34;You can wait!&#34;);
}
在这里,我们安排了 if 和 else 语句来模仿其他语言的“elseif”结构(和 C# 的 #elif 预处理器指令)。 Visual Studio 的自动格式化识别这种模式并保留缩进。尽管在语义上,else 语句之后的每个 if 语句在功能上都嵌套在else 子句。
The switch statement
switch 语句让您可以根据可能的选择来分支程序执行变量可能具有的值。 switch 语句可以产生更清晰的代码而不是多个 if 语句,因为 switch 语句需要一个表达式只求值一次:
void ShowCard (int cardNumber){
switch (cardNumber)
{
case 13:
Console.WriteLine (&#34;King&#34;);
break;
case 12:
Console.WriteLine (&#34;Queen&#34;);
break;
case 11:
Console.WriteLine (&#34;Jack&#34;);
break;
case -1: // Joker is -1
goto case 12; // In this game joker counts as queen
default: // Executes for any other cardNumber
Console.WriteLine (cardNumber);
break;
}
}
此示例演示了最常见的场景,即switch常数。当你指定一个常量时,你只能使用内置的整数类型;bool、char 和枚举类型;和字符串类型。 在每个 case 子句的末尾,您必须明确指定执行的位置 接下来,使用某种跳转语句(除非您的代码以无限循环结束)。 以下是选项:
- break(跳转到 switch 语句的末尾)
- goto case x(跳转到另一个 case 子句)
- goto default(跳转到默认子句)
- 任何其他跳转语句——即 return、throw、continue 或 goto label
当多个值应该执行相同的代码时,您可以列出共同的case顺序:
switch (cardNumber)
{
case 13:
case 12:
case 11:
Console.WriteLine (&#34;Face card&#34;);
break;
default:
Console.WriteLine (&#34;Plain card&#34;);
break;
}
switch 语句的这一特性对于生成更简洁的代码而言至关重要而不是多个 if-else 语句。
Switching on types
你也可以对类型使用switch(从C# 7开始):
TellMeTheType (12);
TellMeTheType (&#34;hello&#34;);
TellMeTheType (true);
void TellMeTheType (object x) // object allows any type.
{
switch (x)
{
case int i:
Console.WriteLine (&#34;It&#39;s an int!&#34;);
Console.WriteLine ($&#34;The square of {i} is {i * i}&#34;);
break;
case string s:
Console.WriteLine (&#34;It&#39;s a string&#34;);
Console.WriteLine ($&#34;The length of {s} is {s.Length}&#34;);
break;
default:
Console.WriteLine (&#34;I don&#39;t know what x is&#34;);
break;
}
}
每个 case 子句指定一个匹配的类型,以及一个变量如果匹配成功,则分配键入的值(“模式”变量)。不同于常量,对您可以使用的类型没有限制。 您可以使用 when 关键字来断言一个case:
switch (x)
{
case bool b when b == true: // Fires only when b is true
Console.WriteLine (&#34;True!&#34;);
break;
case bool b:
Console.WriteLine (&#34;False!&#34;);
break;
}
case 子句的顺序在 switching on type 时很重要(不像 when开启常量)。如果我们调换这两个case的顺序,这个例子会给出不同的结果(事实上,它甚至不会编译,因为编译器会 确定第二种情况不可达)。该规则的一个例外是default 子句,无论它出现在哪里,它总是最后执行。
如果你想switch一个类型,但对其值不感兴趣,你可以使用*discard* (_):
case DateTime _:
Console.WriteLine (&#34;It&#39;s a DateTime&#34;);
您可以堆叠多个 case 子句。下面代码中的Console.WriteLine将对任何大于 1,000 的浮点类型执行:
switch (x)
{
case float f when f > 1000:
case double d when d > 1000:
case decimal m when m > 1000:
Console.WriteLine (&#34;**We can refer to x here but not f or d or m**&#34;); // <-- HERE
break;
}
在这个例子中,编译器让我们只在 when 子句中使用模式变量 f、d 和 m。当我们调用Console.WriteLine 时,它不知道是这三个变量的哪一个将被赋值,所以编译器认为它们全部都超出了作用域范围。 您可以在同一 switch 语句中混合和匹配常量和模式。并且您还可以switch空值:
case null:
Console.WriteLine (&#34;Nothing here&#34;);
break;
Switch expressions
从 C# 8 开始,您可以在表达式的上下文中使用 switch。假如说cardNumber 是 int 类型,下面说明它的使用:
string cardName = cardNumber switch
{
13 => &#34;King&#34;,
12 => &#34;Queen&#34;,
11 => &#34;Jack&#34;,
_ => &#34;Pip card&#34; // equivalent to &#39;default&#39;
};
请注意,switch 关键字出现在变量名之后,并且case子句是表达式(以逗号结尾)而不是语句。switch表达式比对应的 switch 语句更紧凑,你可以在 LINQ 查询中使用它们(第 8 章)。 如果省略默认表达式 (_) 并且switch匹配失败,则抛出异常。 您还可以对多个值switch(元组模式):
int cardNumber = 12;
string suite = &#34;spades&#34;;
string cardName = (cardNumber, suite) switch
{
(13, &#34;spades&#34;) => &#34;King of spades&#34;,
(13, &#34;clubs&#34;) => &#34;King of clubs&#34;,
...
};
通过使用模式可以有更多的选择。
迭代语句 Iteration Statements
C# 允许通过 while、do-while、for 和 foreach 语句重复执行一系列语句。
while and do-while loops
当 bool 表达式为真时,while 循环重复执行一段代码。在执行循环体之前测试表达式。例如, 下面写012:
int i = 0;
while (i < 3)
{
Console.Write (i);
i++;
}
do-while 循环在功能上与 while 循环的不同之处仅在于语句块执行后再测试表达式(确保该块始终 至少执行一次)。这是用 do-while 重写的前面的示例:
int i = 0;
do
{
Console.WriteLine (i);
i++;
}
while (i < 3);
for loops
for 循环类似于 while 循环,带有用于初始化和循环迭代变量的特殊子句。一个 for 循环包含三个子句,如下所示:
for (initialization-clause; condition-clause; iteration-clause)
statement-or-statement-block
以下是每个子句的作用: 初始化子句 在循环开始之前执行;用于初始化一个或多个迭代变量 条件子句 bool 表达式,如果为真,将执行主体 迭代子句 在语句块的每次迭代后执行;通常用于更新迭代变量 例如,以下打印数字 0 到 2
for (int i = 0; i < 3; i++)
Console.WriteLine (i);
下面打印前 10 个斐波那契数(其中每个数是前两项之和):
for (int i = 0, prevFib = 1, curFib = 1; i < 10; i++)
{
Console.WriteLine (prevFib);
int newFib = prevFib + curFib;
prevFib = curFib; curFib = newFib;
}
for 语句的三个部分中的任何一个都可以省略。你可以实现一个无限循环,如下所示(尽管可以使用 while(true) 代替):
for (;;)
Console.WriteLine (&#34;interrupt me&#34;);
foreach loops
foreach 语句迭代可枚举对象中的每个元素。大多数表示一组或一组元素的 .NET 类型是可枚举的。例如,数组和字符串都是可枚举的。这是一个枚举的例子,字符串中的字符从第一个字符到最后一个:
foreach (char c in &#34;beer&#34;) // c is the iteration variable
Console.WriteLine (c);
这是输出:
b
e
e
r我们在“枚举和迭代器”中定义了可枚举对象。
跳转语句 Jump Statements
C# 跳转语句有 break、continue、goto、return 和 throw。
The break statement
break 语句结束迭代主体或switch语句的执行:
int x = 0;
while (true)
{
if (x++ > 5)
break; // break from the loop
}
// execution continues here after break
...
The continue statement
continue 语句放弃循环中的剩余语句并尽早开始下一次迭代。以下循环跳过偶数:
for (int i = 0; i < 10; i++)
{
if ((i % 2) == 0) // If i is even,
continue; // continue with next iteration
Console.Write (i + &#34; &#34;);
}
// OUTPUT: 1 3 5 7 9
The goto statement
goto 语句将执行转移到语句块中的另一个标签。形式如下:
goto statement-label;
或者,在 switch 语句中使用时:
goto case case-constant; // (Only works with constants, not patterns)
标签是代码块中位于语句之前的占位符,用冒号后缀。下面的代码迭代数字 1 到 5,模仿 for 循环:
int i = 1;
startLoop:
if (i <= 5)
{
Console.Write (i + &#34; &#34;);
i++;
goto startLoop;
}
// OUTPUT: 1 2 3 4 5
goto case case-constant 将执行转移到 switch 中的另一个 case块。
The return statement
return 语句从方法退出,如果方法是非空的话必须返回值:
decimal AsPercentage (decimal d)
{
decimal p = d * 100m;
return p; // Return to the calling method with value
}
return 语句可以出现在方法的任何地方(finally 块除外)并且可以多次使用。
The throw statement
throw 语句抛出异常以指示发生了错误。
if (w == null)
throw new ArgumentNullException (...);
if (w == null)
throw new ArgumentNullException (...);
Miscellaneous Statements
using 语句提供了一种优雅的语法在实现 了IDisposable接口的对象上调用 Dispose方法,相当于在try…catch…finally的 finally 块中。
lock 语句是调用Monitor类的 Enter 和 Exit 方法的快捷方式。
命名空间 Namespaces
名称空间是类型名称的域。类型通常组织成层级名空间,使它们更容易找到并防止冲突。例如,处理公钥加密的 RSA 类型定义在以下命名空间:
System.Security.Cryptography
名称空间构成类型名称的组成部分。以下代码调用 RSA 的创建方法:
System.Security.Cryptography.RSA rsa = System.Security.Cryptography.RSA.Create();
命名空间独立于程序集,程序集是 .dll 文件作为部署单位。命名空间对成员可见性也没有影响——public、internal、private 等。 namespace 关键字为该块中的类型定义了一个命名空间;例子:
namespace Outer.Middle.Inner
{
class Class1 {}
class Class2 {}
}
命名空间中的点表示嵌套命名空间的层次结构。以下代码在语义上与前面的示例相同:
namespace Outer
{
namespace Middle
{
namespace Inner
{
class Class1 {}
class Class2 {}
}
}
}
您可以使用其完全限定名称引用类型,其中包括从最外层到最内层的所有命名空间。例如,我们可以参考前面的示例 Outer.Middle.Inner.Class1。 未在任何命名空间中定义的类型被称为驻留在全局命名空间中。全局命名空间还包括顶级命名空间,例如我们示例中的 Outer。
文件作用域命名空间 File-Scoped Namespaces (C# 10)
通常,您会希望一个文件中的所有类型都定义在一个命名空间中:
namespace MyNamespace
{
class Class1 {}
class Class2 {}
}
从 C# 10 开始,您可以使用文件范围的命名空间来完成此操作:
namespace MyNamespace; // Applies to everything that follows in the file.
class Class1 {} // inside MyNamespace
class Class2 {} // inside MyNamespace
文件范围的命名空间减少了混乱并消除了不必要的缩进级别。
使用using指令 The using Directive
using 指令导入一个命名空间,允许您引用类型而无需他们的完全限定名称。下面导入前面例子的 Outer.Middle.Inner 命名空间:
using Outer.Middle.Inner;
Class1 c; // Don’t need fully qualified name
在不同的命名空间中定义相同的类型名称是合法的(并且通常是可取的)。但是,这通常是您假定使用者不太可能同时导入两个同样名字的命名空间时才这样做。一个很好的例子是 TextBox 类,在 System.Windows.Controls (WPF) 中定义和 System.Windows.Forms(Windows 窗体)。 using 指令可以嵌套在命名空间本身中以限制其范围指示。
全局使用指令 The global using Directive (C# 10)
从 C# 10 开始,如果您在 using 指令前加上 global 关键字,该指令将应用于项目或编译单元中的所有文件:
global using System;
global using System.Collection.Generic;
这使您可以集中常见的导入并避免在中重复相同的指令每个文件。 全局使用指令必须在非全局指令之前并且不能出现内部命名空间声明。 global 指令可以与 using static 一起使用。
隐式全局usings Implicit global usings
从 .NET 6 开始,项目文件允许隐式全局使用指令。如果ImplicitUsings 元素在项目文件中设置为 true(新的默认值projects),会自动导入以下命名空间:
System
System.Collections.Generic
System.IO
System.Linq
System.Net.Http
System.Threading
System.Threading.Tasks
根据项目 SDK(Web、Windows窗体、WPF 等)。
使用静态 using static
using static 指令导入类型type而不是命名空间。然后可以无限制地使用所有导入类型的静态成员。在下面的例如,我们调用 Console 类的静态 WriteLine 方法而不需要参考类型:
using static System.Console;
WriteLine (&#34;Hello&#34;);
using static 指令导入该类型的所有可访问静态成员,包括字段、属性和嵌套类型。你也可以对枚举类型应用这个指令(第 3 章),在这种情况下它们的成员被导入。所以,如果我们导入以下枚举类型:
using static System.Windows.Visibility;
我们可以指定 Hidden 而不是 Visibility.Hidden:
var textBox = new TextBox { Visibility = Hidden }; // XAML-style
如果多个静态导入之间出现歧义,C# 编译器未能足够聪明的从上下文中推断出正确的类型,则会产生错误。
命名空间内的规则 Rules Within a Namespace
命名范围 Name scoping
在外部名称空间中声明的名称可以在内部名称空间中不加限定地使用。 在此示例中,Class1 不需要在 Inner 中进行限定:
namespace Outer
{
class Class1 {}
namespace Inner
{
class Class2 : Class1 {}
}
}
如果你想在命名空间层次结构的不同分支中引用一个类型,你可以使用部分限定的名称。在下面的示例中,我们的 SalesReport 基于 Common.ReportBase :
namespace MyTradingCompany
{
namespace Common
{
class ReportBase {}
}
namespace ManagementReporting
{
class SalesReport : Common.ReportBase {}
}
}
名字隐藏 Name hiding
如果相同的类型名称同时出现在内部和外部命名空间中,则内部名胜出。要引用外部命名空间中的类型,您必须限定其名称:
namespace Outer
{
class Foo { }
namespace Inner
{
class Foo { }
class Test
{
Foo f1; // = Outer.Inner.Foo
Outer.Foo f2; // = Outer.Foo
}
}
}
在编译时所有类型名称都转换为完全限定名称。中间语言 (IL) 代码不包含不限定或部分限定的名称。 重复命名空间 Repeated namespaces
您可以重复命名空间声明,只要名称空间不冲突:
namespace Outer.Middle.Inner
{
class Class1 {}
}
namespace Outer.Middle.Inner
{
class Class2 {}
}
我们甚至可以将示例分成两个源文件,这样我们就可以把每个类编译入不同的程序集。
源文件1:
namespace Outer.Middle.Inner
{
class Class1 {}
}
源文件2:
namespace Outer.Middle.Inner
{
class Class2 {}
}
嵌套使用指令 Nested using directives
您可以在命名空间中嵌套 using 指令。这允许您在命名空间声明中确定使用指令的使用范围。在下面的示例中,Class1 是在一个范围内可见但在另一个范围内不可见:
namespace N1
{
class Class1 {}
}
namespace N2
{
using N1; // <-- HERE**
class Class2 : Class1 {}
}
namespace N2
{
class Class3 : Class1 {} // Compile-time error
}
为类型和命名空间指定别名 Aliasing Types and Namespaces
导入命名空间可能会导致类型名称冲突。你可以只导入你需要的特定类型而不是导入整个命名空间,只需要给每个类型一个别名:
using PropertyInfo2 = System.Reflection.PropertyInfo;
class Program { PropertyInfo2 p; }
整个命名空间可以是别名,如下所示:
using R = System.Reflection;
class Program { R.PropertyInfo p; }
高级命名空间功能 Advanced Namespace Features
Extern
外部别名允许您的程序引用两个完全相同的类型的限定名称(即名称空间和类型名称相同)。这是一个不寻常的场景,并且仅当两种类型来自不同的程序集时才会发生。考虑以下示例。 库1,编译为Widgets1.dll:
namespace Widgets
{
public class Widget {}
}
库 2,编译为 Widgets2.dll:
namespace Widgets
{
public class Widget {}
}
引用 Widgets1.dll 和 Widgets2.dll 的应用程序:
using Widgets;
Widget w = new Widget();
应用程序无法编译,因为 Widget 不明确。外部别名可以解决歧义。第一步是修改应用程序的 .csproj 文件,分配每个引用的唯一别名:
<ItemGroup>
<Reference Include=&#34;Widgets1&#34;>
<Aliases>W1</Aliases>
</Reference>
<Reference Include=&#34;Widgets2&#34;>
<Aliases>W2</Aliases>
</Reference>
</ItemGroup>第二步是使用 extern 别名指令:
extern alias W1;
extern alias W2;
W1.Widgets.Widget w1 = new W1.Widgets.Widget();
W2.Widgets.Widget w2 = new W2.Widgets.Widget();
命名空间别名限定符 Namespace alias qualifiers
正如我们之前提到的,内部命名空间中的名称隐藏了外部命名空间中的名称。然而,有时即使使用完全限定的类型名称也无法解析冲突。考虑以下示例:
namespace N
{
class A
{
static void Main() => new A.B(); // Instantiate class B
public class B {} // Nested type
}
}
namespace A
{
class B {}
}
Main 方法可以实例化嵌套类 B 或在命名空间 A 中的类 B。编译器总是给予在当前命名空间中的标识符以更高的优先级,在本例中是嵌套的 B 类。 要解决此类冲突,可以相对于下列的其中一个名称空间名称进行限定:
- 全局命名空间——所有命名空间的根(用上下文关键字global标识)
- 外部别名集
:: 标记执行名称空间别名限定。在这个例子中,我们限定使用全局命名空间(这在自动生成的代码中避免名称冲突时最常见):
namespace N
{
class A
{
static void Main()
{
System.Console.WriteLine (new A.B());
System.Console.WriteLine (new global::A.B()); // <-- HERE**
}
public class B {}
}
}
namespace A
{
class B {}
}
这是一个使用别名限定的示例(改编自中的示例“Extern”):
extern alias W1;
extern alias W2;
W1::Widgets.Widget w1 = new W1::Widgets.Widget();
W2::Widgets.Widget w2 = new W2::Widgets.Widget(); |