C# 编程指南

[image]

C# 编程指南
C# 编程指南
另请参见 发送反馈意见

更新：2007 年 11 月

本节提供有关关键的 C# 语言功能和 C# 可通过 .NET Framework 访问的功能的详细信息。

本节中大部分内容都假定您已了解有关 C# 和一般编程概念的一些知识。如果您刚开始学习编程或 C#，则应从 Visual C# 指导教程开始。您可能还想访问 Visual C# 开发中心，此处提供很多教程、示例和视频，可帮助您入门。

有关特定的关键字、运算符和预处理器指令的信息，请参见 C# 参考。有关 C# 语言规范的信息，请参见 C# 语言规范。

语言部分

深入 C# 程序

Main() 和命令行参数（C# 编程指南）

类型（C# 编程指南）

数组（C# 编程指南）

字符串（C# 编程指南）

语句、表达式和运算符（C# 编程指南）

类和结构（C# 编程指南）

属性（C# 编程指南）

索引器（C# 编程指南）

委托（C# 编程指南）

事件（C# 编程指南）

泛型（C# 编程指南）

LINQ 查询表达式（C# 编程指南）

Lambda 表达式（C# 编程指南）

迭代器（C# 编程指南）

命名空间（C# 编程指南）

可空类型（C# 编程指南）

不安全代码和指针（C# 编程指南）

XML 文档注释（C# 编程指南）

平台部分

应用程序域（C# 编程指南）

程序集和全局程序集缓存（C# 编程指南）

属性（C# 编程指南）

集合类（C# 编程指南）

异常和异常处理（C# 编程指南）

互操作性（C# 编程指南）

线程处理（C# 编程指南）

性能（C# 编程指南）

反射（C# 编程指南）

C# DLL（C# 编程指南）

安全性（C# 编程指南）

请参见

其他资源

C# 参考
Visual C#
Visual C# 指导教程
将有关本主题的反馈意见发送给 Microsoft。


深入 C# 程序

[image]

C# 编程指南
深入 C# 程序
另请参见 发送反馈意见

更新：2007 年 11 月

本节讨论 C# 程序的一般结构，还包括标准的“Hello, World!”示例。

本节内容

  • Hello World -- 您的第一个程序（C# 编程指南）

  • C# 程序的通用结构（C# 编程指南）

相关章节

  • Visual C# 入门

  • 迁移到 Visual C#

  • C# 编程指南

  • C# 参考

  • Visual C# 示例

C# 语言规范

有关更多信息，请参见 C# 语言规范 中的以下各章节：

  • 1 简介

请参见

概念

C# 编程指南


Hello World -- 您的第一个程序（C# 编程指南）

[image]

C# 编程指南
Hello World -- 您的第一个程序（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

以下控制台程序是传统“Hello World!”程序的 C# 版，该程序显示字符串 Hello World!。

C# 复制代码
// A Hello World! program in C#
using System;
namespace HelloWorld
{
    class Hello
    {
        static void Main()
        {
            System.Console.WriteLine("Hello World!");

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }
}

现在，让我们依次分析此程序的重要组成部分。

注释

第一行包含注释语句：

C# 复制代码
// A Hello World! program in C#

// 字符将这行的其余内容转换为注释内容。还可通过将文本块置于 /* 和 */ 字符之间使其变成注释，例如：

C# 复制代码
/* A "Hello World!" program in C#.
This program displays the string "Hello World!" on the screen. */

Main 方法

C# 程序必须包含一个 Main 方法，用于控制程序的开始和结束。在 Main 方法中创建对象和执行其他方法。

Main 方法是驻留在类或结构内的静态方法。在前面的“Hello World!”示例中，此方法驻留在一个名为 Hello 的类中。用下列方式之一声明 Main 方法：

  • 该方式返回 void：

    C# 复制代码
    static void Main()
{
    //...
}

  • 它还可以返回 int：

    C# 复制代码
    static int Main()
{
    //...
    return 0;
}

  • 由于有这两个返回类型，它可以带有参数：

    C# 复制代码
    static void Main(string[] args)
{
    //...
}

    - 或 -

    C# 复制代码
    static int Main(string[] args)
{
    //...
    return 0;
}

Main 方法的参数是 string 数组，该数组表示用于激活程序的命令行参数。请注意，不像 C++，该数组不包含可执行 (exe) 文件名。

通过按 F5 在调试模式下运行时，在 Main 方法的末尾调用 ReadKey 将使得控制台窗口无法退出，从而使您无法阅读输出。

有关使用命令行参数的更多信息，请参见 Main() 和命令行参数（C# 编程指南）中的示例和如何：创建和使用 C# DLL（C# 编程指南）。

输入和输出

C# 程序通常使用 .NET Framework 的运行库提供的输入/输出服务。语句 System.Console.WriteLine("Hello World!"); 使用了 WriteLine 方法，该方法是运行库中的 Console 类的输出方法之一。它显示了标准输出流使用的字符串参数，输出流后面跟一个新行。其他 Console 方法用于不同的输入和输出操作。如果程序开始处包含 using System; 指令，则无需完全限定 System 类和方法即可直接使用它们。例如，您可以改为调用 Console.WriteLine，而不必指定 System.Console.Writeline：

C# 复制代码
using System;
C# 复制代码
Console.WriteLine("Hello World!");

有关输入/输出方法的更多信息，请参见 System.IO。

编译和执行

可以通过在 Visual Studio IDE 中创建项目或使用命令行来编译“Hello World!”程序。使用 Visual Studio 命令提示符窗口或调用 vsvars32.bat 将 Visual C# 工具集放置在命令提示符下的路径中。

从命令行编译程序：

  • 使用任何文本编辑器创建源文件，并将其保存为名如 Hello.cs 的文件。C# 源代码文件使用的扩展名是 .cs。

  • 若要激活编译器，请输入命令：

    csc Hello.cs

    如果程序没有包含任何编译错误，则将创建一个 Hello.exe 文件。

  • 若要运行程序，请输入命令：

    Hello

有关 C# 编译器及其选项的更多信息，请参见 C# 编译器选项。

请参见

概念

C# 编程指南
Visual C# 示例

参考

深入 C# 程序

其他资源

C# 参考


C# 程序的通用结构（C# 编程指南）

[image]

C# 编程指南
C# 程序的通用结构（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 程序可由一个或多个文件组成。每个文件都可以包含零个或零个以上的命名空间。一个命名空间除了可包含其他命名空间外，还可包含类、结构、接口、枚举、委托等类型。以下是 C# 程序的主干，它包含所有这些元素。

C# 复制代码
// A skeleton of a C# program
using System;
namespace YourNamespace
{
    class YourClass
    {
    }

    struct YourStruct
    {
    }

    interface IYourInterface
    {
    }

    delegate int YourDelegate();

    enum YourEnum
    {
    }

    namespace YourNestedNamespace
    {
        struct YourStruct
        {
        }
    }

    class YourMainClass
    {
        static void Main(string[] args)
        {
            //Your program starts here...
        }
    }
}

相关章节

更多信息：

  • 类（C# 编程指南）

  • 结构（C# 编程指南）

  • 命名空间（C# 编程指南）

  • 接口（C# 编程指南）

  • 委托（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范 中的以下各章节：

  • 1.2 程序结构

  • 9.1 编译单元（命名空间）

请参见

概念

C# 编程指南
Visual C# 示例

参考

深入 C# 程序

其他资源

C# 参考


Main() 和命令行参数（C# 编程指南）

[image]

C# 编程指南
Main() 和命令行参数（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

Main 方法是 C# 控制台应用程序或窗口应用程序的入口点。（库和服务不要求将 Main 方法作为入口点。）应用程序启动时，Main 方法是第一个调用的方法。

C# 程序中只能有一个入口点。如果您有多个类都包含 Main 方法，则必须使用 /main 编译器选项编译您的程序，以指定用作入口点的 Main 方法。有关更多信息，请参见 /main（指定 Main 方法的位置）（C# 编译器选项）。

C# 复制代码
class TestClass
{
    static void Main(string[] args)
    {
        // Display the number of command line arguments:
        System.Console.WriteLine(args.Length);
    }
}

概述

  • Main 方法是 .exe 程序的入口点，程序控制流在该处开始和结束。

  • Main 在类或结构的内部声明。Main 必须是静态的，且不应该是公用的。（在前面的示例中，它接受默认访问级别 private。）但不要求封闭类或结构是静态的。

  • Main 的返回类型有两种：void 或 int。

  • 所声明的 Main 方法可以具有包含命令行实参的 string[] 形参，也可以不具有这样的形参。使用 Visual Studio 创建 Windows 窗体应用程序时，可以手动添加形参，也可以使用 Environment 类获取命令行实参。形参读取为从零开始编制索引的命令行实参。与 C 和 C++ 不同，在 C# 中不将程序名称视为第一个命令行实参。

本节内容

  • 命令行参数（C# 编程指南）

  • 如何：显示命令行参数（C# 编程指南）

  • 如何：使用 foreach 访问命令行参数（C# 编程指南）

  • Main() 返回值（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.1 Hello World

请参见

概念

C# 编程指南
Visual C# 示例

参考

深入 C# 程序


命令行参数（C# 编程指南）

[image]

C# 编程指南
命令行参数（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

Main 方法可以使用参数，在这种情况下它采用下列形式之一：

C# 复制代码
static int Main(string[] args)

C# 复制代码
static void Main(string[] args)
说明：

若要在 Windows 窗体应用程序中的 Main 方法中启用命令行参数，必须手动修改 program.cs 中 Main 的签名。Windows 窗体设计器生成的代码创建没有输入参数的 Main。也可以使用 Environment..::.CommandLine 或 Environment..::.GetCommandLineArgs 从控制台或 Windows 应用程序中的任何位置访问命令行参数。

Main 方法的参数是表示命令行参数的 String 数组。一般是通过测试 Length 属性来确定参数是否存在，例如：

C# 复制代码
if (args.Length == 0)
{
    System.Console.WriteLine("Please enter a numeric argument.");
    return 1;
}

还可以使用 Convert 类或 Parse 方法将字符串参数转换为数值类型。例如，下面的语句使用 Parse 方法将 string 转换为 long 数字：

复制代码
long num = Int64.Parse(args[0]);

也可以使用别名为 Int64 的 C# 类型 long：

复制代码
long num = long.Parse(args[0]);

还可以使用 Convert 类的方法 ToInt64 完成同样的工作：

复制代码
long num = Convert.ToInt64(s);

有关更多信息，请参见 Parse 和 Convert。

示例

下面的示例演示如何在控制台应用程序中使用命令行参数。程序在运行时采用一个参数，将该参数转换为整数，并计算该数的阶乘。如果没有提供参数，则程序发出一条消息来解释程序的正确用法。

说明：

在 Visual Studio 中运行应用程序时，可以在“项目设计器” ->“调试”页中指定命令行参数。

有关如何使用命令行参数的更多示例，请参见如何：创建和使用 C# DLL（C# 编程指南）。

C# 复制代码
public class Functions
{
    public static long Factorial(int n)
    {
        // Test for invalid input
        if ((n <= 0) || (n > 256))
        {
            return -1;
        }

        // Calculate the factorial iteratively rather than recursively:
        long tempResult = 1;
        for (int i = 1; i <= n; i++)
        {
            tempResult *= i;
        }
        return tempResult;
    }
}

class MainClass
{
    static int Main(string[] args)
    {
        // Test if input arguments were supplied:
        if (args.Length == 0)
        {
            System.Console.WriteLine("Please enter a numeric argument.");
            System.Console.WriteLine("Usage: Factorial <num>");
            return 1;
        }

        // Try to convert the input arguments to numbers. This will throw
        // an exception if the argument is not a number.
        // num = int.Parse(args[0]);
        int num;
        bool test = int.TryParse(args[0], out num);
        if(test == false)
        {
            System.Console.WriteLine("Please enter a numeric argument.");
            System.Console.WriteLine("Usage: Factorial <num>");
            return 1;
        }

        // Calculate factorial.
        long result = Functions.Factorial(num);

        // Print result.
        if(result == -1)
            System.Console.WriteLine("Input must be > 0 and < 256.");
        else
            System.Console.WriteLine("The Factorial of {0} is {1}.", num, result);

        return 0;
    }
}
// If 3 is entered on command line, the
// output reads: The factorial of 3 is 6.

请参见

任务

如何：显示命令行参数（C# 编程指南）
如何：使用 foreach 访问命令行参数（C# 编程指南）

概念

C# 编程指南
Main() 和命令行参数（C# 编程指南）

参考

Main() 返回值（C# 编程指南）
类（C# 编程指南）
System..::.Environment


如何：显示命令行参数（C# 编程指南）

[image]

C# 编程指南
如何：显示命令行参数（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

可以通过 Main 的可选参数来访问通过命令行提供给可执行文件的参数。参数以字符串数组的形式提供。数组的每个元素都包含一个参数。参数之间的空白被移除。例如，下面是对一个假想的可执行文件的命令行调用：

命令行输入 | 传递给 Main 的字符串数组
executable.exe a b c | "a" "b" "c"
executable.exe one two | "one" "two"
executable.exe “one two” three | "one two" "three"
说明：

在 Visual Studio 中运行应用程序时，可以在“项目设计器” ->“调试”页中指定命令行参数。

示例

本示例显示了传递给命令行应用程序的命令行参数。显示的输出对应于上表中的第一项。

C# 复制代码
class CommandLine
{
    static void Main(string[] args)
    {
        // The Length property provides the number of array elements
        System.Console.WriteLine("parameter count = {0}", args.Length);

        for (int i = 0; i < args.Length; i++)
        {
            System.Console.WriteLine("Arg[{0}] = [{1}]", i, args[i]);
        }
    }
}
/* Output (assumes 3 cmd line args):
    parameter count = 3
    Arg[0] = [a]
    Arg[1] = [b]
    Arg[2] = [c]
*/

请参见

任务

如何：使用 foreach 访问命令行参数（C# 编程指南）

概念

C# 编程指南
Main() 和命令行参数（C# 编程指南）

参考

Main() 返回值（C# 编程指南）

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 添加了有关项目设计器“调试”页的说明。 | 信息补充。


如何：使用 foreach 访问命令行参数（C# 编程指南）

[image]

C# 编程指南
如何：使用 foreach 访问命令行参数（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

循环访问数组的另一种方法是使用 foreach 语句，如下面的示例所示。foreach 语句可以用于循环访问数组、.NET Framework 集合类或任何实现 IEnumerable 接口的类或结构。

说明：

在 Visual Studio 中运行应用程序时，可以在“项目设计器” ->“调试”页中指定命令行参数。

示例

下面的示例演示如何使用 foreach 输出命令行参数。

C# 复制代码
// arguments: John Paul Mary
C# 复制代码
class CommandLine2
{
    static void Main(string[] args)
    {
        System.Console.WriteLine("Number of command line parameters = {0}", args.Length);

        foreach (string s in args)
        {
            System.Console.WriteLine(s);
        }
    }
}
/* Output:
    Number of command line parameters = 3
    John
    Paul
    Mary
*/

请参见

任务

如何：显示命令行参数（C# 编程指南）

概念

C# 编程指南
Main() 和命令行参数（C# 编程指南）

参考

foreach，in（C# 参考）
Main() 返回值（C# 编程指南）
Array
System.Collections


Main() 返回值（C# 编程指南）

[image]

C# 编程指南
Main() 返回值（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

Main 方法可以是 void 类型：

C# 复制代码
static void Main()
{
    //...
}

它还可以返回 int：

C# 复制代码
static int Main()
{
    //...
    return 0;
}

如果不需要使用 Main 的返回值，则返回 void 可以使代码变得略微简单。但是，返回整数可使程序将状态信息与调用该可执行文件的其他程序或脚本相关。下面的示例演示使用 Main 的返回值。

示例

在此示例中，使用了一个批处理文件来执行程序并测试 Main 函数的返回值。在 Windows 中执行程序时，Main 函数返回的任何值都将存储在名为 ERRORLEVEL 的环境变量中。通过检查 ERRORLEVEL 变量，批处理文件可以确定执行的结果。通常，返回值为零指示执行成功。下面是一个非常简单的程序，其 Main 函数返回零。

C# 复制代码
class MainReturnValTest
{
    static int Main()
    {
        //...
        return 0;
    }
}

由于此示例使用了批处理文件，因此最好从命令行编译这段代码，如 如何：设置环境变量 中所示。

然后，使用批处理文件调用前面的代码示例所生成的可执行文件。由于代码返回零，因此批处理文件将报告成功。但如果前面的代码更改为返回非零值，然后重新编译，则批处理文件的后续执行将指示失败。

复制代码
rem test.bat
@echo off
MainReturnValueTest
@if "%ERRORLEVEL%" == "0" goto good

:fail
    echo Execution Failed
    echo return value = %ERRORLEVEL%
    goto end

:good
    echo Execution Succeded
    echo return value = %ERRORLEVEL%
    goto end

:end

示例输出

Execution Succeded

return value = 0

请参见

任务

如何：显示命令行参数（C# 编程指南）
如何：使用 foreach 访问命令行参数（C# 编程指南）

概念

C# 编程指南
Main() 和命令行参数（C# 编程指南）

其他资源

C# 参考


类型（C# 编程指南）

[image]

C# 编程指南
类型（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

类型、变量和值

C# 是一种强类型语言。每个变量和常量都有一个类型，每个计算为值的表达式也是如此。每个方法签名为每个输入参数和返回值指定一个类型。.NET Framework 类库定义了一组内置数值类型以及表示各种逻辑构造的更复杂的类型，例如文件系统、网络连接、对象的集合和数组及日期。典型 C# 程序使用类库中的类型，还使用为特定于该程序问题域的概念建模的用户定义类型。

类型中存储的信息可以包括：

  • 该类型的变量所需的存储空间。

  • 该类型可以表示的最大值和最小值。

  • 该类型包含的成员（方法、字段、事件等）。

  • 该类型所继承的基类型。

  • 将在运行时为其分配变量内存的位置。

  • 允许的运算种类。

编译器使用类型信息确保代码中执行的所有运算都是类型安全的。例如，如果声明了一个 int 类型的变量，则编译器允许您在加法和减法运算中使用此变量。如果尝试在一个 bool 类型的变量上执行相同的运算，则编译器会产生错误，如下面的示例所示：

C# 复制代码
int a = 5;
int b = a + 2; //OK

bool test = true;
 *
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
说明：

请 C 和 C++ 开发人员注意，在 C# 中，bool 不能转换为 int。

编译器将类型信息作为元数据嵌入到可执行文件中。公共语言运行时 (CLR) 会在运行时使用该元数据，以进一步确保它在分配和回收内存时类型安全。

在变量声明中指定类型

在程序中声明变量或常量时，必须指定其类型或者使用关键字 var 让编译器可以推断其类型。下面的示例演示了一些使用内置数值类型和复杂的用户定义类型的变量声明：

C# 复制代码
// Declaration only:
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
            where item <= limit
            select item;

在方法签名中指定方法参数和返回值的类型。下面的签名中演示的方法需要用 int 作为输入参数并返回一个字符串：

C# 复制代码
public string GetName(int ID)
{
    if (ID < names.Length)
        return names[ID];
    else
        return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };

声明了一个变量后，不能使用新类型重新对它进行声明，也不能向它赋与它的声明类型不兼容的值。例如，不能声明 int，然后向它赋予布尔值 true。但是，值可以转换为其他类型，例如将值赋给新变量或者作为方法参数传递时。编译器会自动执行不会导致数据丢失的类型转换。可能导致数据丢失的转换需要源代码内有强制转换。

有关更多信息，请参见强制转换和类型转换（C# 编程指南）。

内置类型

C# 提供了一组标准的内置数值类型，用来表示整数、浮点值、布尔表达式、文本字符、十进制值和其他数据类型。还有内置的 string 和 object 类型。您可以在任何 C# 程序中使用这些类型。有关内置类型的更多信息，请参见类型参考表（C# 参考）。

自定义类型

您可以使用 struct、class、interface 和 enum 结构创建自己的自定义类型。.NET Framework 类库本身是 Microsoft 提供的自定义类型的集合，您可以在自己的应用程序中使用它们。默认情况下，类库中最常用的类型在所有 C# 程序中均可用。而对于其他类型，则仅当您显式添加定义这些类型的程序集的项目引用后它们才可用。编译器拥有对该程序集的引用后，才可以在源代码中声明在该程序集中声明的类型的变量（和常量）。有关更多信息，请参见 .NET Framework 类库。

通用类型系统

请务必了解有关 .NET Framework 中的类型系统的以下两个基本点：

  • 它支持继承原则。类型可从称为基类型的其他类型派生。派生类型继承基类型的方法、属性和其他成员（存在一些限制）。之后，基类型可从某些其他类型派生，这种情况下，派生类型继承其层次结构中这两个基类型的成员。包括如 System..::.Int32（C# 关键字：int）等内置数值类型在内的所有类型，最终都是从一个基类派生得到的，该基类即 System..::.Object （C# 关键字：object）。这种统一的类型层次结构称为通用类型系统 (CTS)。有关 C# 中的继承的更多信息，请参见继承（C# 编程指南）。

  • CTS 中的每一个类型都被定义成了值类型或引用类型。这包括 .NET Framework 类库中的所有自定义类型以及您自己的用户定义类型。使用关键字 struct 定义的类型是值类型；所有内置数值类型都是 structs。使用关键字 class 定义的类型是引用类型。引用类型和值类型有不同的编译时规则和不同的运行时行为。

下图演示了 CTS 中的值类型和引用类型之间的关系。

CTS 中的值类型和引用类型

说明：

可以看到，最常用的类型都组织到了 System 命名空间。但是，类型所在的命名空间与类型是值类型还是引用类型没有关系。

值类型

值类型是从派生自 System..::.Object 的 System..::.ValueType 派生的。派生自 System..::.ValueType 的类型在 CLR 中有特殊行为。值类型变量直接包含它们的值，这意味着内存在声明变量的任意上下文中都是以内联方式分配的。值类型变量没有单独的堆分配或垃圾回收开销。

值类型分为两个类别：struct 和 enum。

内置数值类型是结构，它们具有可以访问的属性和方法。

C# 复制代码
// Static method on type Byte.
byte b = Byte.MaxValue();

但是，您可以声明它们并向它们赋值，就如同它们是简单的非聚合类型一样：

C# 复制代码
byte num = 0xA;
int i = 5;
char c = 'Z';

例如，值类型是密封的，这意味着您不能从 System..::.Int32 派生类型，并且不能定义一个结构以便从任何用户定义的类或结构继承，因为结构只能从 System..::.ValueType 继承。但是，一个结构可以实现一个或更多个接口。可以将结构类型强制转换为接口类型；但这会使装箱操作在托管堆上的一个引用类型对象内包装该结构。在将值类型传递给将 System..::.Object 作为输入参数的方法时会发生装箱操作。有关更多信息，请参见装箱和取消装箱（C# 编程指南）。

可以使用 struct 关键字创建自己的自定义值类型。通常，结构可作为一小组相关变量的容器，如下面的示例所示：

C# 复制代码
public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

有关结构的更多信息，请参见结构（C# 编程指南）。有关 .NET Framework 中的值类型的更多信息，请参见通用类型系统中的值类型。

另一类值类型是 enum。枚举定义一组已命名的整数常量。例如，.NET Framework 类库中的 System.IO..::.FileMode 枚举包含一组已命名的整数常量，用来指定应如何打开文件。其定义方式如下面的示例所示：

C# 复制代码
public enum FileMode
{
    CreateNew = 1,
    Create = 2,
    Open = 3,
    OpenOrCreate = 4,
    Truncate = 5,
    Append = 6,
}

Create 常量具有值 2。但是，在阅读源代码时，名称可为人们提供更多信息，因此最好使用枚举而不使用常量文本数字。

所有枚举均是从继承自 System..::.ValueType 的 System..::.Enum 继承的。所有适用于结构的规则同样也适用于枚举。有关枚举的更多信息，请参见枚举类型（C# 编程指南）。

引用类型

定义为类、委托、数组或接口的类型是引用类型。在运行时，当您声明引用类型的变量时，该变量会一直包含值 null，直至您使用 new 运算符显式创建对象的实例，或者为该变量分配已经使用 new, as shown in the following example: 在其他位置创建的对象

C# 复制代码
MyClass mc = new MyClass();
MyClass mc2 = mc;

接口必须与实现它的类对象一起初始化。如果 MyClass 实现 IMyInterface，则您创建了 IMyInterface 的实例，如下面的示例所示：

C# 复制代码
IMyInterface iface = new MyClass();

创建对象时，将在托管堆上分配内存，变量只保存对对象位置的引用。对于托管堆上的类型，在 CLR 的自动内存管理功能（称为“垃圾回收”）对它们进行分配和回收时都需要系统开销。但是，也对垃圾回收进行了高度优化，在大多数情况下它不会引起性能问题。有关垃圾回收的更多信息，请参见自动内存管理。

所有数组都是引用类型，即使其元素是值类型也不例外。数组是从 System..::.Array 类隐式派生的，但可以通过 C# 提供的简化语法来声明和使用它们，如下面的示例所示：

C# 复制代码
// Declare and initialize an array of integers.
int[] nums = { 1, 2, 3, 4, 5 };

// Access an instance property of System.Array.
int len = nums.Length;

引用类型完全支持继承。创建类时，可以从没有定义为 sealed 的任何其他接口或类继承，而其他类则可以从您创建的类继承并重写虚方法。有关如何创建自己的类的更多信息，请参见类和结构（C# 编程指南）。有关继承和虚方法的更多信息，请参见继承（C# 编程指南）。

文本值类型

在 C# 中，文本值从编译器接收类型。您可以通过在数字末尾追加一个字母来指定应如何类型化该数字文本。例如，若要指定应按浮点数来处理值 4.56，则在该数字后追加一个“f”或“F”：4.56f。如果没有追加字母，则编译器将为该文本推断一个类型。有关可以使用字母后缀指定的类型的更多信息，请参见值类型（C# 参考）中各个类型的参考页。

由于文本已类型化，且所有类型最终都是从 System..::.Object 派生，因此您可以编写和编译如下所示的代码：

C# 复制代码
string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);

Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);

泛型类型

一个类型可以通过一个或多个类型参数声明，而这些类型参数作为客户端代码在创建该类型的实例时提供的实际类型（具体类型）的占位符。这种类型称为“泛型类型”。例如，.NET Framework 类型 System.Collections.Generic..::.List<(Of <(T>)>) 有一个类型参数，按照约定该类型参数的名称为 T。在创建该类型的实例时，会指定列表将包含的对象的类型，例如字符串：

C# 复制代码
List<string> strings = new List<string>();

使用类型参数便可以重复使用相同的类存放任意类型的元素，而不必将每个元素都转换为对象。泛型集合类称为“强类型集合”，因为编译器知道集合中元素的特定类型，举例来说，如果尝试向上面示例中的 strings 对象添加整数，编译器会在编译时引发错误。有关更多信息，请参见泛型（C# 编程指南）。

隐式类型、匿名类型和可以为 null 的类型

如前面所述，您可以使用关键字 var 隐式类型化一个局部变量（非类成员）。该变量在编译时仍然会接收一个类型，但该类型是由编译器提供的。有关更多信息，请参见隐式类型的局部变量（C# 编程指南）。

某些情况下为相关值的简单集合创建命名类型是不方便的，因为这些相关值不准备在方法边界外存储或传递。可以创建匿名类型来实现此目的。有关更多信息，请参见 匿名类型（C# 编程指南）。

普通的值类型不能有 null 值。但是，可以通过在类型后面附加 ? 来创建可以为 null 值的类型。例如，int? 是一个也可以具有 null 值的 int 类型。在 CTS 中，可以为 null 的类型是泛型结构类型 System..::.Nullable<(Of <(T>)>) 的实例。在向其数值可能为 null 的数据库传入数据和从中传出数据时，可以为 null 的类型尤其有用。有关更多信息，请参见可空类型（C# 编程指南）。

相关章节

有关更多信息，请参见下列主题：

  • 强制转换和类型转换（C# 编程指南）

  • 装箱和取消装箱（C# 编程指南）

  • 值类型（C# 参考）

  • 引用类型（C# 参考）

  • 类和结构（C# 编程指南）

  • 匿名类型（C# 编程指南）

  • 泛型（C# 编程指南）

C# 语言规范

有关类型的更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 3.8 命名空间和类型名称

  • 4.1 值类型

  • 4.2 引用类型

  • 4.3 装箱和取消装箱

请参见

概念

C# 编程指南
XML 数据类型的转换

参考

不同语言的数据类型比较
整型表（C# 参考）

其他资源

C# 参考

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 增加了介绍性信息及有关类型声明、通用类型系统、值类型和引用类型、文本和泛型类型的信息。 | 信息补充。


强制转换和类型转换（C# 编程指南）

[image]

C# 编程指南
强制转换和类型转换（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

由于 C# 是在编译时静态类型化的，因此变量在声明后就无法再次声明，或者无法用于存储其他类型的值，除非该类型可以转换为变量的类型。例如，不存在从整数到任意字符串的转换。因此，将 i 声明为整数后，就无法将字符串“Hello”赋予它，如下面的代码中所示。

复制代码
int i;
i = "Hello"; // Error: "Cannot implicitly convert type 'string' to 'int'"

但有时可能需要将值复制到其他类型的变量或方法参数中。例如，您可能有一个整数变量，您需要将该变量传递给参数类型化为 double 的方法。或者可能需要将类变量赋给接口类型的变量。这些类型的操作称为“类型转换”。在 C# 中，可以执行以下几种类型的转换：

  • 隐式转换：由于该转换是一种安全类型的转换，不会导致数据丢失，因此不需要任何特殊的语法。例如，从较小整数类型到较大整数类型的转换以及从派生类到基类的转换都是这样的转换。

  • 显式转换（强制转换）：显式转换需要强制转换运算符。源变量和目标变量兼容，但由于目标变量的类型大小比源变量小（或者目标变量是源变量的一个基类），因此存在数据丢失的风险。

  • 用户定义的转换：可以定义一些特殊的方法来执行用户定义的转换，从而使不具有基类–派生类关系的自定义类型之间可以显式和隐式转换。有关更多信息，请参见转换运算符（C# 编程指南）。

  • 使用帮助程序类的转换：若要在不兼容的类型之间进行转换，例如在整数与 System..::.DateTime 对象之间转换，或者在十六进制字符串与字节数组之间转换，则可以使用 System..::.BitConverter 类、System..::.Convert 类和内置数值类型的 Parse 方法，例如 Int32..::.Parse。有关更多信息，请参见如何：将字节数组转换为 int（C# 编程指南）、如何：将字符串转换为整型（C# 编程指南）和如何：在十六进制字符串与数值类型之间转换（C# 编程指南）。

隐式转换

对于内置数值类型，如果要存储的值无需截断或四舍五入即可适应变量，则可以进行隐式转换。例如，longlong（C# 参考）类型的变量（8 字节整数）能够存储 intint（C# 参考）（在 32 位计算机上为 4 字节）可存储的任何值。在下面的示例中，编译器先将右侧的值隐式转换为 long 类型，再将它赋给 bigNum。

C# 复制代码
// Implicit conversion. num long can
// hold any value an int can hold, and more!
int num = 2147483647;
long bigNum = num;

有关所有隐式数值转换的完整列表，请参见隐式数值转换表（C# 参考）。

对于引用类型，隐式转换始终存在于从一个类转换为该类的任何一个直接或间接的基类或接口的情况。由于派生类始终包含基类的所有成员，因此不必使用任何特殊语法。

复制代码
Derived d = new Derived();
Base b = d; // Always OK.

显式转换

但是，如果进行转换可能会导致信息丢失，则编译器会要求执行显式转换，显式转换也称为“强制转换”。强制转换是显式通知编译器您打算进行转换且您知道可能会发生数据丢失的一种方式。若要执行强制转换，请在要转换的值或变量前面的圆括号中指定要强制转换到的类型。下面的程序将 doubledouble（C# 参考） 强制转换为 intint（C# 参考）。如不强制转换则该程序不会进行编译。

C# 复制代码
class Test
{
    static void Main()
    {
        double x = 1234.7;
        int a;
        // Cast double to int.
        a = (int)x;
        System.Console.WriteLine(a);
    }
}
// Output: 1234

有关支持的显式数值转换的列表，请参见显式数值转换表（C# 参考）。

对于引用类型，如果需要从基类型转换为派生类型，则必须进行显式强制转换：

复制代码
        // Create a new derived type.
        Giraffe g = new Giraffe();

        // Implicit conversion to base type is safe.
        Animal a = g;

        // Explicit conversion is required to cast
        // back to derived type. Note: This will compile but
        // throw an exception at run time if the right-side
        // object is not in fact a Giraffe.
        Giraffe g2 = (Giraffe) a;

引用类型之间的强制转换操作不会更改基础对象的运行时类型；它只更改用作对该对象的引用的值的类型。有关更多信息，请参见多态性（C# 编程指南）。

运行时的类型转换异常

在某些引用类型转换中，编译器无法确定强制转换是否会有效。正确进行编译的强制转换操作有可能在运行时失败。如下面的示例所示，类型强制转换在运行时失败将导致引发 InvalidCastException。

C# 复制代码
class Animal
{
    public void Eat() { Console.WriteLine("Eating."); }
    public override string ToString()
    {
        return "I am an animal.";
    }
}
class Reptile : Animal { }
class Mammal : Animal { }

class UnSafeCast
{
    static void Main()
    {
        Test(new Mammal());

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }

    static void Test(Animal a)
    {
        // Cause InvalidCastException at run time
        // because Mammal is not convertible to Reptile.
        Reptile r = (Reptile)a;
    }

}

C# 提供 is 和 as 运算符，使您可以在实际执行强制转换之前测试兼容性。有关更多信息，请参见如何：使用 as 和 is 运算符安全地进行强制转换（C# 编程指南）。

C# 语言规范

有关强制转换和类型转换的更多信息，请参见 C# 语言规范中的以下部分。

  • 7.6.6 强制转换表达式

  • 6.1 隐式转换

  • 6.2 显式转换

请参见

任务

如何：将字符串转换为整型（C# 编程指南）

概念

C# 编程指南
通用类型转换
显式转换
导出类型转换

参考

类型（C# 编程指南）
() 运算符（C# 参考）
explicit（C# 参考）
implicit（C# 参考）
转换运算符（C# 编程指南）

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 在简介和别的部分增加了新内容。 | 内容 Bug 修复


装箱和取消装箱（C# 编程指南）

[image]

C# 编程指南
装箱和取消装箱（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

装箱是将值类型转换为 object 类型或由此值类型实现的任一接口类型的过程。当 CLR 对值类型进行装箱时，会将该值包装到 System.Object 内部，再将后者存储在托管堆上。取消装箱将从对象中提取值类型。在下面的示例中，整型变量 i 装箱并赋给对象 o。

C# 复制代码
int i = 123;
object o = (object)i;  // boxing

然后，可以对对象 o 取消装箱并将其赋值给整型变量 i：

C# 复制代码
o = 123;
i = (int)o;  // unboxing

性能

相对于简单的赋值而言，装箱和取消装箱过程需要进行大量的计算。对值类型进行装箱时，必须分配并构造一个新对象。次之，取消装箱所需的强制转换也需要进行大量的计算。有关更多信息，请参见性能。

装箱

装箱用于在垃圾回收堆中存储值类型。装箱是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一个对象实例，并将该值复制到新的对象中。

请看以下值类型变量的声明：

C# 复制代码
int i = 123;

以下语句对变量 i 隐式应用装箱操作：

C# 复制代码
object o = i;  // Implicit boxing

此语句的结果是在堆栈上创建对象引用 o，而在堆上则引用 int 类型的值。该值是赋给变量 i 的值类型值的一个副本。下图说明了两个变量 i 和 o 之间的差异。

装箱转换

还可以像下面的示例一样显式执行装箱，不过显式装箱从来不是必需的：

C# 复制代码
int i = 123;
object o = (object)i;  // explicit boxing

说明

此示例使用装箱将整数变量 i 转换为对象 o。这样，存储在变量 i 中的值就从 123 更改为 456。该示例表明原始值类型和装箱的对象使用不同的内存位置，因此能够存储不同的值。

示例

C# 复制代码
class TestBoxing
{
    static void Main()
    {
        int i = 123;
        object o = i;  // Implicit boxing

        i = 456;  // Change the contents of i

        System.Console.WriteLine("The value-type value = {0}", i);
        System.Console.WriteLine("The object-type value = {0}", o);
    }
}
/* Output:
    The value-type value = 456
    The object-type value = 123
*/

下面的示例演示无效的取消装箱及引发的 InvalidCastException。使用 try 和 catch，在发生错误时显示错误信息。

C# 复制代码
class TestUnboxing
{
    static void Main()
    {
        int i = 123;
        object o = i;  // implicit boxing

        try
        {
            int j = (short)o;  // attempt to unbox

            System.Console.WriteLine("Unboxing OK.");
        }
        catch (System.InvalidCastException e)
        {
            System.Console.WriteLine("{0} Error: Incorrect unboxing.", e.Message);
        }
    }
}

此程序输出：

Specified cast is not valid. Error: Incorrect unboxing.

如果将下列语句：

复制代码
int j = (short) o;

更改为：

复制代码
int j = (int) o;

将执行转换，并将得到以下输出：

Unboxing OK.

取消装箱

取消装箱是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括：

  • 检查对象实例，以确保它是给定值类型的装箱值。

  • 将该值从实例复制到值类型变量中。

以下语句同时说明了装箱和取消装箱操作：

C# 复制代码
int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;  // unboxing

下图演示了上述语句的结果。

取消装箱转换

要在运行时成功取消装箱值类型，被取消装箱的项必须是对一个对象的引用，该对象是先前通过装箱该值类型的实例创建的。尝试取消装箱 null 或对不兼容值类型的引用会导致 InvalidCastException。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各节：

  • 4.3.1 装箱转换

相关章节

更多信息：

  • 引用类型

  • 值类型

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各节：

  • 4.3 装箱和取消装箱

请参见

概念

C# 编程指南

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 将以前的“装箱”和“取消装箱”两个独立主题合并为了本主题。 | 内容 Bug 修复


如何：使用 as 和 is 运算符安全地进行强制转换（C# 编程指南）

[image]

C# 编程指南
如何：使用 as 和 is 运算符安全地进行强制转换（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

由于对象是多态的，因此基类类型的变量可以保存派生类型。若要访问派生类型的方法，需要将值强制转换回该派生类型。不过，在这些情况下，如果只尝试进行简单的强制转换，会导致引发 InvalidCastException 的风险。这就是 C# 提供 is 和 as 运算符的原因。您可以使用这两个运算符来测试强制转换是否会成功，而没有引发异常的风险。通常，as 运算符更高效一些，因为如果可以成功进行强制转换，它会实际返回强制转换值。而 is 运算符只返回一个布尔值。因此，如果只想确定对象的类型，而无需对它进行实际强制转换，则可以使用 is 运算符。

示例

下面的示例演示如何使用 is 和 as 运算符从一个引用类型强制转换为另一个引用类型，而没有引发异常的风险。此示例还演示如何对 as 运算符使用可以为 null 值的类型。

C# 复制代码
class SafeCasting
{
    class Animal
    {
        public void Eat() { Console.WriteLine("Eating."); }
        public override string ToString()
        {
            return "I am an animal.";
        }
    }
    class Mammal : Animal { }
    class Giraffe : Mammal { }

    class SuperNova { }

    static void Main()
    {
        SafeCasting app = new SafeCasting();

        // Use the is operator to verify the type.
        // before performing a cast.
        Giraffe g = new Giraffe();
        app.UseIsOperator(g);

        // Use the as operator and test for null
        // before referencing the variable.
        app.UseAsOperator(g);

        // Use the as operator to test
        // an incompatible type.
        SuperNova sn = new SuperNova();
        app.UseAsOperator(sn);

        // Use the as operator with a value type.
        // Note the implicit conversion to int? in
        // the method body.
        int i = 5;
        app.UseAsWithNullable(i);

        double d = 9.78654;
        app.UseAsWithNullable(d);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }

    void UseIsOperator(Animal a)
    {
        if (a is Mammal)
        {
            Mammal m = (Mammal)a;
            m.Eat();
        }
    }

    void UseAsOperator(object o)
    {
        Mammal m = o as Mammal;
        if (m != null)
        {
            Console.WriteLine(m.ToString());
        }
        else
        {
            Console.WriteLine("{0} is not a Mammal", o.GetType().Name);
        }
    }

    void UseAsWithNullable(System.ValueType val)
    {
        int? j = val as int?;
        if (j != null)
        {
            Console.WriteLine(j);
        }
        else
        {
            Console.WriteLine("Could not convert " + val.ToString());
        }
    }
}

请参见

参考

类型（C# 编程指南）
强制转换和类型转换（C# 编程指南）
可空类型（C# 编程指南）

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


如何：将字节数组转换为 int（C# 编程指南）

[image]

C# 编程指南
如何：将字节数组转换为 int（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例演示如何使用 BitConverter 类将字节数组转换为 int 并返回字节数组。例如，在从网络读取字节之后，可能需要将字节转换为内置数据类型。除了示例中的 ToInt32(array<Byte>[]()[], Int32) 方法之外，下表列出了 BitConverter 类中将字节（来自字节数组）转换为其他内置类型的方法。

返回类型 | 方法
bool | ToBoolean(array<Byte>[]()[], Int32)
char | ToChar(array<Byte>[]()[], Int32)
double | ToDouble(array<Byte>[]()[], Int32)
short | ToInt16(array<Byte>[]()[], Int32)
int | ToInt32(array<Byte>[]()[], Int32)
long | ToInt64(array<Byte>[]()[], Int32)
float | ToSingle(array<Byte>[]()[], Int32)
ushort | ToUInt16(array<Byte>[]()[], Int32)
uint | ToUInt32(array<Byte>[]()[], Int32)
ulong | ToUInt64(array<Byte>[]()[], Int32)

示例

此示例实例化字节数组，并在计算机结构为 little-endian 的情况下反转数组（即首先存储最低有效字节），然后调用 ToInt32(array<Byte>[]()[], Int32) 方法以将数组中的四个字节转换为 int。ToInt32(array<Byte>[]()[], Int32) 的第二个参数指定字节数组的起始索引。

说明：

输出可能会根据计算机结构的 endian 设置而不同。

C# 复制代码
byte[] bytes = { 0, 0, 0, 25 };

// If the system architecture is little-endian (that is, little end first),
// reverse the byte array.
if (BitConverter.IsLittleEndian)
    Array.Reverse(bytes);

int i = BitConverter.ToInt32(bytes, 0);
Console.WriteLine("int: {0}", i);
// Output: int: 25

在此示例中，调用 BitConverter 类的 GetBytes(Int32) 方法以将 int 转换为字节数组。

说明：

输出可能会根据计算机结构的 endian 设置而不同。

C# 复制代码
byte[] bytes = BitConverter.GetBytes(201805978);
Console.WriteLine("byte array: " + BitConverter.ToString(bytes));
// Output: byte array: 9A-50-07-0C

请参见

参考

BitConverter
IsLittleEndian
类型（C# 编程指南）


如何：将字符串转换为整型（C# 编程指南）

[image]

C# 编程指南
如何：将字符串转换为整型（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

这些示例演示了一些用于将 string 转换为 int 的不同方法。例如，当从命令行参数获取数值输入时，此类转换会很有用。还存在一些将字符串转换为其他数值类型（如 float 或 long）的类似方法。下表列出了其中的一些方法。

数值类型 | 方法
decimal | ToDecimal(String)
float | ToSingle(String)
double | ToDouble(String)
short | ToInt16(String)
long | ToInt64(String)
ushort | ToUInt16(String)
uint | ToUInt32(String)
ulong | ToUInt64(String)

示例

此示例调用 ToInt32(String) 方法以将字符串“29”转换为 int，然后将结果增加 1 并打印输出。

C# 复制代码
int numVal = Convert.ToInt32("29");
numVal++;

Console.WriteLine(numVal);
// Output: 30

将 string 转换为 int 的另一种方法是使用 System..::.Int32 结构的 Parse 或 TryParse 方法。ToUInt32 方法在内部使用 Parse。如果字符串的格式无效，则 Parse 会引发一个异常；而 TryParse 不会引发异常，而是返回 false。下面的示例演示了对 Parse 和 TryParse 的成功调用和不成功的调用。

C# 复制代码
int numVal = Int32.Parse("-105");
Console.WriteLine(numVal);
// Output: -105
C# 复制代码
int j;
Int32.TryParse("-105", out j);
Console.WriteLine(j);
// Output: -105
C# 复制代码
try
{
    int m = Int32.Parse("abc");
}
catch (FormatException e)
{
    Console.WriteLine(e.Message);
}
// Output: Input string was not in a correct format.
C# 复制代码
string inputString = "abc";
int numValue;
bool parsed = Int32.TryParse(inputString, out numValue);

if (!parsed)
    Console.WriteLine("Int32.TryParse could not parse '{0}' to an int.\n", inputString);

// Output: Int32.TryParse could not parse 'abc' to an int.

请参见

任务

如何：确定字符串是否表示数值（C# 编程指南）

参考

类型（C# 编程指南）


如何：在十六进制字符串与数值类型之间转换（C# 编程指南）

[image]

C# 编程指南
如何：在十六进制字符串与数值类型之间转换（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

以下示例演示如何执行下列任务：

  • 获取字符串中每个字符的十六进制值。

  • 获取与十六进制字符串中的每个值对应的字符。

  • 将十六进制 string 转换为整型。

  • 将十六进制 string 转换为浮点型。

  • 将字节数组转换为十六进制 string。

示例

此示例输出 string 中的每个字符的十六进制值。首先，它将 string 分析为字符数组，然后对每个字符调用 ToInt32(Char) 以获取相应的数字值。最后，在 string 中将数字的格式设置为十六进制表示形式。

C# 复制代码
string input = "Hello World!";
char[] values = input.ToCharArray();
foreach (char letter in values)
{
    // Get the integral value of the character.
    int value = Convert.ToInt32(letter);
    // Convert the decimal value to a hexadecimal value in string form.
    string hexOutput = String.Format("{0:X}", value);
    Console.WriteLine("Hexadecimal value of {0} is {1}", letter, hexOutput);
}
/* Output:
   Hexadecimal value of H is 48
    Hexadecimal value of e is 65
    Hexadecimal value of l is 6C
    Hexadecimal value of l is 6C
    Hexadecimal value of o is 6F
    Hexadecimal value of   is 20
    Hexadecimal value of W is 57
    Hexadecimal value of o is 6F
    Hexadecimal value of r is 72
    Hexadecimal value of l is 6C
    Hexadecimal value of d is 64
    Hexadecimal value of ! is 21
 */

此示例分析十六进制值的 string 并输出对应于每个十六进制值的字符。首先，它调用 Split(array<Char>[]()[]) 方法以获取每个十六进制值作为数组中的单个 string。然后调用 ToInt32(String, Int32) 以将十六进制转换为表示为 int 的十进制值。示例中演示了用于获取对应于该字符代码的字符的两种不同方法。第一种方法是使用 ConvertFromUtf32(Int32)，它将对应于整型参数的字符作为 string 返回。第二种方法是将 int 显式转换为 char。

C# 复制代码
string hexValues = "48 65 6C 6C 6F 20 57 6F 72 6C 64 21";
string[] hexValuesSplit = hexValues.Split(' ');
foreach (String hex in hexValuesSplit)
{
    // Convert the number expressed in base-16 to an integer.
    int value = Convert.ToInt32(hex, 16);
    // Get the character corresponding to the integral value.
    string stringValue = Char.ConvertFromUtf32(value);
    char charValue = (char)value;
    Console.WriteLine("hexadecimal value = {0}, int value = {1}, char value = {2} or {3}",
                        hex, value, stringValue, charValue);
}
/* Output:
    hexadecimal value = 48, int value = 72, char value = H or H
    hexadecimal value = 65, int value = 101, char value = e or e
    hexadecimal value = 6C, int value = 108, char value = l or l
    hexadecimal value = 6C, int value = 108, char value = l or l
    hexadecimal value = 6F, int value = 111, char value = o or o
    hexadecimal value = 20, int value = 32, char value =   or
    hexadecimal value = 57, int value = 87, char value = W or W
    hexadecimal value = 6F, int value = 111, char value = o or o
    hexadecimal value = 72, int value = 114, char value = r or r
    hexadecimal value = 6C, int value = 108, char value = l or l
    hexadecimal value = 64, int value = 100, char value = d or d
    hexadecimal value = 21, int value = 33, char value = ! or !
*/

此示例演示了将十六进制 string 转换为整数的另一种方法，即调用 Parse(String, NumberStyles) 方法。

C# 复制代码
string hexString = "8E2";
int num = Int32.Parse(hexString, System.Globalization.NumberStyles.HexNumber);
Console.WriteLine(num);
//Output: 2274

下面的示例演示如何使用 System..::.BitConverter 类和 Int32..::.Parse 方法将十六进制 string 转换为浮点型。

C# 复制代码
string hexString = "43480170";
uint num = uint.Parse(hexString, System.Globalization.NumberStyles.AllowHexSpecifier);

byte[] floatVals = BitConverter.GetBytes(num);
float f = BitConverter.ToSingle(floatVals, 0);
Console.WriteLine("float convert = {0}", f);

// Output: 200.0056

下面的示例演示如何使用 System..::.BitConverter 类将字节数组转换为十六进制字符串。

C# 复制代码
byte[] vals = { 0x01, 0xAA, 0xB1, 0xDC, 0x10, 0xDD };

string str = BitConverter.ToString(vals);
Console.WriteLine(str);

str = BitConverter.ToString(vals).Replace("-", "");
Console.WriteLine(str);

/*Output:
  01-AA-B1-DC-10-DD
  01AAB1DC10DD
 */

请参见

任务

如何：确定字符串是否表示数值（C# 编程指南）

概念

标准数字格式字符串

参考

类型（C# 编程指南）

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 增加了最后两个代码示例。 | 内容 Bug 修复


数组（C# 编程指南）

[image]

C# 编程指南
数组（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

数组是一种数据结构，它包含若干相同类型的变量。数组是使用类型声明的：

type[] arrayName;

下面的示例创建一维、多维和交错数组：

C# 复制代码
class TestArraysClass
{
    static void Main()
    {
        // Declare a single-dimensional array
        int[] array1 = new int[5];

        // Declare and set array element values
        int[] array2 = new int[] { 1, 3, 5, 7, 9 };

        // Alternative syntax
        int[] array3 = { 1, 2, 3, 4, 5, 6 };

        // Declare a two dimensional array
        int[,] multiDimensionalArray1 = new int[2, 3];

        // Declare and set array element values
        int[,] multiDimensionalArray2 = { { 1, 2, 3 }, { 4, 5, 6 } };

        // Declare a jagged array
        int[][] jaggedArray = new int[6][];

        // Set the values of the first array in the jagged array structure
        jaggedArray[0] = new int[4] { 1, 2, 3, 4 };
    }
}

数组概述

数组具有以下属性：

  • 数组可以是一维、多维或交错的。

  • 数值数组元素的默认值设置为零，而引用元素的默认值设置为 null。

  • 交错数组是数组的数组，因此其元素是引用类型并初始化为 null。

  • 数组的索引从零开始：具有 n 个元素的数组的索引是从 0 到 n-1。

  • 数组元素可以是任何类型，包括数组类型。

  • 数组类型是从抽象基类型 Array 派生的引用类型。由于此类型实现了 IEnumerable 和 IEnumerable<(Of <(T>)>)，因此可以对 C# 中的所有数组使用 foreach 迭代。

相关章节

  • 作为对象的数组（C# 编程指南）

  • 对数组使用 foreach（C# 编程指南）

  • 将数组作为参数传递（C# 编程指南）

  • 使用 ref 和 out 传递数组（C# 编程指南）

  • “数组”示例

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.8 数组

  • 12 数组

请参见

概念

C# 编程指南
数组集合类型
通用类型系统中的数组

参考

集合类（C# 编程指南）
数组使用指南


作为对象的数组（C# 编程指南）

[image]

C# 编程指南
作为对象的数组（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 中，数组实际上是对象，而不只是像 C 和 C++ 中那样的可寻址连续内存区域。Array 是所有数组类型的抽象基类型。可以使用 Array 具有的属性以及其他类成员。这种用法的一个示例是使用 Length 属性来获取数组的长度。下面的代码将 numbers 数组的长度（为 5）赋给名为 lengthOfNumbers 的变量：

C# 复制代码
int[] numbers = { 1, 2, 3, 4, 5 };
int lengthOfNumbers = numbers.Length;

System.Array 类提供了许多其他有用的方法和属性，用于排序、搜索和复制数组。

示例

此示例使用 Rank 属性来显示数组的维数。

C# 复制代码
class TestArraysClass
{
    static void Main()
    {
        // Declare and initialize an array:
        int[,] theArray = new int[5, 10];
        System.Console.WriteLine("The array has {0} dimensions.", theArray.Rank);
    }
}
// Output: The array has 2 dimensions.

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）
一维数组（C# 编程指南）
多维数组（C# 编程指南）
交错数组（C# 编程指南）


一维数组（C# 编程指南）

[image]

C# 编程指南
一维数组（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

可以如下例所示声明一个由 5 个整数组成的数组：

C# 复制代码
int[] array = new int[5];

此数组包含从 array[0] 到 array[4] 的元素。new 运算符用于创建数组并将数组元素初始化为它们的默认值。在此例中，所有数组元素都初始化为零。

可以用相同的方式声明存储字符串元素的数组。例如：

C# 复制代码
string[] stringArray = new string[6];

数组初始化

可以在声明数组时将其初始化，在这种情况下不需要级别说明符，因为级别说明符已经由初始化列表中的元素数提供。例如：

C# 复制代码
int[] array1 = new int[] { 1, 3, 5, 7, 9 };

可以用相同的方式初始化字符串数组。下面声明一个字符串数组，其中每个数组元素用每天的名称初始化：

C# 复制代码
string[] weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

如果在声明数组时将其初始化，则可以使用下列快捷方式：

C# 复制代码
int[] array2 = { 1, 3, 5, 7, 9 };
string[] weekDays2 = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

可以声明一个数组变量但不将其初始化，但在将数组分配给此变量时必须使用 new 运算符。例如：

C# 复制代码
int[] array3;
array3 = new int[] { 1, 3, 5, 7, 9 };   // OK
//array3 = {1, 3, 5, 7, 9};   // Error

C# 3.0 引入了隐式类型的数组。有关更多信息，请参见隐式类型的数组（C# 编程指南）。

值类型数组和引用类型数组

请看下列数组声明：

C# 复制代码
SomeType[] array4 = new SomeType[10];

该语句的结果取决于 SomeType 是值类型还是引用类型。如果是值类型，则该语句将创建一个由 10 个 SomeType 类型的实例组成的数组。如果 SomeType 是引用类型，则该语句将创建一个由 10 个元素组成的数组，其中每个元素都初始化为空引用。

有关值类型和引用类型的更多信息，请参见类型（C# 参考）。

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）
多维数组（C# 编程指南）
交错数组（C# 编程指南）
Array


多维数组（C# 编程指南）

[image]

C# 编程指南
多维数组（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

数组可以具有多个维度。例如，下列声明创建一个四行两列的二维数组：

C# 复制代码
int[,] array = new int[4, 2];

另外，下列声明创建一个三维（4、2 和 3）数组：

C# 复制代码
int[, ,] array1 = new int[4, 2, 3];

数组初始化

可以在声明数组时将其初始化，如下例所示：

C# 复制代码
int[,] array2D = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
int[, ,] array3D = new int[,,] { { { 1, 2, 3 } }, { { 4, 5, 6 } } };

也可以初始化数组但不指定级别：

C# 复制代码
int[,] array4 = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };

如果选择声明一个数组变量但不将其初始化，必须使用 new 运算符将一个数组分配给此变量。例如：

C# 复制代码
int[,] array5;
array5 = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };   // OK
//array5 = {{1,2}, {3,4}, {5,6}, {7,8}};   // Error

也可以给数组元素赋值，例如：

C# 复制代码
array5[2, 1] = 25;

下面的代码示例将数组变量初始化为默认值（交错数组除外）：

C# 复制代码
int[,] array6 = new int[10, 10];

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）
一维数组（C# 编程指南）
交错数组（C# 编程指南）


交错数组（C# 编程指南）

[image]

C# 编程指南
交错数组（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

交错数组是元素为数组的数组。交错数组元素的维度和大小可以不同。交错数组有时称为“数组的数组”。以下示例说明如何声明、初始化和访问交错数组。

下面声明一个由三个元素组成的一维数组，其中每个元素都是一个一维整数数组：

C# 复制代码
int[][] jaggedArray = new int[3][];

必须初始化 jaggedArray 的元素后才可以使用它。可以如下例所示初始化该元素：

C# 复制代码
jaggedArray[0] = new int[5];
jaggedArray[1] = new int[4];
jaggedArray[2] = new int[2];

每个元素都是一个一维整数数组。第一个元素是由 5 个整数组成的数组，第二个是由 4 个整数组成的数组，而第三个是由 2 个整数组成的数组。

也可以使用初始值设定项用值填充数组元素，在这种情况下不需要数组大小。例如：

C# 复制代码
jaggedArray[0] = new int[] { 1, 3, 5, 7, 9 };
jaggedArray[1] = new int[] { 0, 2, 4, 6 };
jaggedArray[2] = new int[] { 11, 22 };

还可以在声明数组时将其初始化，如：

C# 复制代码
    int[][] jaggedArray2 = new int[][]
{
    new int[] {1,3,5,7,9},
    new int[] {0,2,4,6},
    new int[] {11,22}
};

可以使用下面的速记格式。请注意：不能从元素初始化中省略 new 运算符，因为不存在元素的默认初始化：

C# 复制代码
    int[][] jaggedArray3 =
{
    new int[] {1,3,5,7,9},
    new int[] {0,2,4,6},
    new int[] {11,22}
};

交错数组是数组的数组，因此其元素是引用类型并初始化为 null。

可以如下例所示访问个别数组元素：

C# 复制代码
// Assign 77 to the second element ([1]) of the first array ([0]):
jaggedArray3[0][1] = 77;

// Assign 88 to the second element ([1]) of the third array ([2]):
jaggedArray3[2][1] = 88;

可以混合使用交错数组和多维数组。下面声明和初始化一个一维交错数组，该数组包含大小不同的二维数组元素：

C# 复制代码
int[][,] jaggedArray4 = new int[3][,]
{
    new int[,] { {1,3}, {5,7} },
    new int[,] { {0,2}, {4,6}, {8,10} },
    new int[,] { {11,22}, {99,88}, {0,9} }
};

可以如本例所示访问个别元素，该示例显示第一个数组的元素 [1,0] 的值（值为 5）：

C# 复制代码
System.Console.Write("{0}", jaggedArray4[0][1, 0]);

方法 Length 返回包含在交错数组中的数组的数目。例如，假定您已声明了前一个数组，则此行：

C# 复制代码
System.Console.WriteLine(jaggedArray4.Length);

返回值 3。

示例

本例生成一个数组，该数组的元素为数组自身。每一个数组元素都有不同的大小。

C# 复制代码
class ArrayTest
{
    static void Main()
    {
        // Declare the array of two elements:
        int[][] arr = new int[2][];

        // Initialize the elements:
        arr[0] = new int[5] { 1, 3, 5, 7, 9 };
        arr[1] = new int[4] { 2, 4, 6, 8 };

        // Display the array elements:
        for (int i = 0; i < arr.Length; i++)
        {
            System.Console.Write("Element({0}): ", i);

            for (int j = 0; j < arr[i].Length; j++)
            {
                System.Console.Write("{0}{1}", arr[i][j], j == (arr[i].Length - 1) ? "" : " ");
            }
            System.Console.WriteLine();
        }
        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    Element(0): 1 3 5 7 9
    Element(1): 2 4 6 8
*/

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）
一维数组（C# 编程指南）
多维数组（C# 编程指南）
Array


对数组使用 foreach（C# 编程指南）

[image]

C# 编程指南
对数组使用 foreach（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 还提供 foreach 语句。该语句提供一种简单、明了的方法来循环访问数组的元素。例如，下面的代码创建一个名为 numbers 的数组，并用 foreach 语句循环访问该数组：

C# 复制代码
int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 };
foreach (int i in numbers)
{
    System.Console.Write("{0} ", i);
}
//Output: 4 5 6 1 2 3 -2 -1 0

由于有了多维数组，可以使用相同方法来循环访问元素，例如：

C# 复制代码
int[,] numbers2D = new int[3, 2] { { 9, 99 }, { 3, 33 }, { 5, 55 } };
// Or use the short form:
// int[,] numbers2D = { { 9, 99 }, { 3, 33 }, { 5, 55 } };

foreach (int i in numbers2D)
{
    System.Console.Write("{0} ", i);
}
// Output: 9 99 3 33 5 55

然而，对于多维数组，使用嵌套的 for 循环可以更好地控制数组元素。

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）
一维数组（C# 编程指南）
多维数组（C# 编程指南）
交错数组（C# 编程指南）
Array


将数组作为参数传递（C# 编程指南）

[image]

C# 编程指南
将数组作为参数传递（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

数组可作为参数传递给方法。因为数组是引用类型，所以方法可以更改元素的值。

将一维数组作为参数传递

可以将初始化的一维数组传递给方法。例如：

C# 复制代码
PrintArray(theArray);

上面的行中调用的方法可定义为：

C# 复制代码
void PrintArray(int[] arr)
{
    // method code
}

也可以在一个步骤中初始化并传递新数组。例如：

C# 复制代码
PrintArray(new int[] { 1, 3, 5, 7, 9 });

示例

在下例中，初始化一个字符串数组并将其作为参数传递给 PrintArray 方法（该数组的元素显示在此方法中）：

C# 复制代码
class ArrayClass
{
    static void PrintArray(string[] arr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            System.Console.Write(arr[i] + "{0}", i < arr.Length - 1 ? " " : "");
        }
        System.Console.WriteLine();
    }

    static void Main()
    {
        // Declare and initialize an array:
        string[] weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

        // Pass the array as a parameter:
        PrintArray(weekDays);
    }
}
// Output: Sun Mon Tue Wed Thu Fri Sat

在此示例中，初始化一个二维数组并将其传递给 PrintArray 方法（该数组的元素显示在此方法中）。

C# 复制代码
class ArrayClass2D
{
    static void PrintArray(int[,] arr)
    {
        // Display the array elements:
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 2; j++)
            {
                System.Console.WriteLine("Element({0},{1})={2}", i, j, arr[i, j]);
            }
        }
    }
    static void Main()
    {
        // Pass the array as a parameter:
        PrintArray(new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } });

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();

    }
}
    /* Output:
        Element(0,0)=1
        Element(0,1)=2
        Element(1,0)=3
        Element(1,1)=4
        Element(2,0)=5
        Element(2,1)=6
        Element(3,0)=7
        Element(3,1)=8
    */

将多维数组作为参数传递

可以将初始化的多维数组传递给方法。例如，如果 theArray 是二维数组：

C# 复制代码
PrintArray(theArray);

上面的行中调用的方法可定义为：

C# 复制代码
void PrintArray(int[,] arr)
{
    // method code
}

也可以在一个步骤中初始化并传递新数组。例如：

C# 复制代码
PrintArray(new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } });

// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）
一维数组（C# 编程指南）
多维数组（C# 编程指南）
交错数组（C# 编程指南）


使用 ref 和 out 传递数组（C# 编程指南）

[image]

C# 编程指南
使用 ref 和 out 传递数组（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

与所有的 out 参数一样，在使用数组类型的 out 参数前必须先为其赋值，即必须由被调用方为其赋值。例如：

C# 复制代码
static void TestMethod1(out int[] arr)
{
    arr = new int[10];   // definite assignment of arr
}

与所有的 ref 参数一样，数组类型的 ref 参数必须由调用方明确赋值。因此不需要由接受方明确赋值。可以将数组类型的 ref 参数更改为调用的结果。例如，可以为数组赋以 null 值，或将其初始化为另一个数组。例如：

C# 复制代码
static void TestMethod2(ref int[] arr)
{
    arr = new int[10];   // arr initialized to a different array
}

下面的两个示例说明 out 与 ref 在将数组传递给方法时的用法差异。

示例

在此例中，在调用方（Main 方法）中声明数组 theArray，并在 FillArray 方法中初始化此数组。然后将数组元素返回调用方并显示。

C# 复制代码
class TestOut
{
    static void FillArray(out int[] arr)
    {
        // Initialize the array:
        arr = new int[5] { 1, 2, 3, 4, 5 };
    }

    static void Main()
    {
        int[] theArray; // Initialization is not required

        // Pass the array to the callee using out:
        FillArray(out theArray);

        // Display the array elements:
        System.Console.WriteLine("Array elements are:");
        for (int i = 0; i < theArray.Length; i++)
        {
            System.Console.Write(theArray[i] + " ");
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
    /* Output:
        Array elements are:
        1 2 3 4 5
    */

在此例中，在调用方（Main 方法）中初始化数组 theArray，并通过使用 ref 参数将其传递给 FillArray 方法。在 FillArray 方法中更新某些数组元素。然后将数组元素返回调用方并显示。

C# 复制代码
class TestRef
{
    static void FillArray(ref int[] arr)
    {
        // Create the array on demand:
        if (arr == null)
        {
            arr = new int[10];
        }
        // Fill the array:
        arr[0] = 1111;
        arr[4] = 5555;
    }

    static void Main()
    {
        // Initialize the array:
        int[] theArray = { 1, 2, 3, 4, 5 };

        // Pass the array using ref:
        FillArray(ref theArray);

        // Display the updated array:
        System.Console.WriteLine("Array elements are:");
        for (int i = 0; i < theArray.Length; i++)
        {
            System.Console.Write(theArray[i] + " ");
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
    /* Output:
        Array elements are:
        1111 2 3 4 5555
    */

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）
一维数组（C# 编程指南）
多维数组（C# 编程指南）
交错数组（C# 编程指南）


隐式类型的数组（C# 编程指南）

[image]

C# 编程指南
隐式类型的数组（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

可以创建隐式类型的数组，在这样的数组中，数组实例的类型是从数组初始值设定项中指定的元素推断而来的。有关任何隐式类型变量的规则也适用于隐式类型的数组。有关更多信息，请参见隐式类型的局部变量（C# 编程指南）。

在查询表达式中，隐式类型的数组通常与匿名类型以及对象初始值设定项和集合初始值设定项一起使用。

下面的示例演示如何创建隐式类型的数组：

C# 复制代码
class ImplicitlyTypedArraySample
{
    static void Main()
    {
        var a = new[] { 1, 10, 100, 1000 }; // int[]
        var b = new[] { "hello", null, "world" }; // string[]

        // single-dimension jagged array
        var c = new[]
        {
            new[]{1,2,3,4},
            new[]{5,6,7,8}
        };

        // jagged array of strings
        var d = new[]
        {
            new[]{"Luca", "Mads", "Luke", "Dinesh"},
            new[]{"Karen", "Suma", "Frances"}
        };
    }
}

请注意，在上一个示例中，没有在初始化语句的左侧对隐式类型的数组使用方括号。另请注意，交错数组就像一维数组那样使用 new [] 进行初始化。不支持隐式类型的多维数组。

对象初始值设定项中的隐式类型的数组

创建包含数组的匿名类型时，必须在该类型的对象初始值设定项中对数组进行隐式类型化。在下面的示例中，contacts 是一个隐式类型的匿名类型数组，其中每个匿名类型都包含一个名为 PhoneNumbers 的数组。请注意，对象初始值设定项内部未使用 var 关键字。

C# 复制代码
var contacts = new[]
{
    new {
            Name = " Eugene Zabokritski",
            PhoneNumbers = new[] { "206-555-0108", "425-555-0001" }
        },
    new {
            Name = " Hanying Feng",
            PhoneNumbers = new[] { "650-555-0199" }
        }
};

请参见

概念

C# 编程指南
LINQ 查询表达式（C# 编程指南）

参考

隐式类型的局部变量（C# 编程指南）
数组（C# 编程指南）
匿名类型（C# 编程指南）
对象和集合初始值设定项（C# 编程指南）
var（C# 参考）


字符串（C# 编程指南）

[image]

C# 编程指南
字符串（C# 编程指南）
发送反馈意见

更新：2007 年 11 月

以下章节讨论 string 数据类型，它是 String 类的别名。

本节内容

字符串基础（C# 编程指南）

描述 C# 中的字符串概念。

如何：修改字符串内容（C# 编程指南）

提供一个代码示例来演示如何修改字符串的内容。

如何：串联多个字符串（C# 编程指南）

阐释如何使用 + 运算符和 Stringbuilder 类在编译时和运行时将字符串联接在一起。

如何：比较字符串（C# 编程指南）

演示如何执行字符串的序号比较。

如何：拆分字符串（C# 编程指南）

包含一个代码示例，该示例演示如何使用 String.Split 方法来分析字符串。

如何：使用字符串方法搜索字符串（C# 编程指南）

解释如何使用特定方法来搜索字符串。

如何：使用正则表达式搜索字符串（C# 编程指南）

解释如何使用正则表达式来搜索字符串。

如何：确定字符串是否表示数值（C# 编程指南）

演示如何安全地分析字符串以了解其是否具有有效数值。

如何：将字符串转换为 DateTime（C# 编程指南）

演示如何将诸如“01/24/2008”这样的字符串转换为 System..::.DateTime 对象。

相关章节

基本字符串操作

提供指向解释基本字符串操作的主题的链接。

比较字符串

包含有关如何比较字符串的信息，并且提供使用 C# 和 Visual Basic 编写的示例。

LINQ 和字符串

提供有关如何使用 LINQ 查询来执行各种字符串操作的信息。

C# 编程指南

提供指向解释 C# 中编程构造的主题的链接。


字符串基础（C# 编程指南）

[image]

C# 编程指南
字符串基础（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

字符串是 String 类型的对象，它的值是文本。在内部，这些文本存储为 Char 对象的只读集合，其中每个对象都表示一个以 UTF-16 编码的 Unicode 字符。C# 字符串末尾没有以 null 结尾的字符（这一点与 C 和 C++ 不同），因此 C# 字符串可以包含任意数目的嵌入式 null 字符（“\0”）。字符串长度表示字符数目，无论这些字符是否是由 Unicode 代理项对形成的。若要访问字符串中的各个 Unicode 码位，请使用 StringInfo 对象。

string 与 System.String 的对比

在 C# 中，string 关键字是 String 的别名。因此，String 与 string 等效，您可以根据自己的喜好选择命名约定。String 类提供了很多用于安全地创建、操作和比较字符串的方法。此外，C# 语言还重载某些运算符来简化常见的字符串操作。有关关键字的更多信息，请参见string（C# 参考）。有关类型及其方法的更多信息，请参见 String。

声明和初始化字符串

可以通过各种方式来声明和初始化字符串，如下面的示例所示：

C# 复制代码
// Declare without initializing.
string message1;

// Initialize to null.
string message2 = null;

// Initialize as an empty string.
// Use the Empty constant instead of the literal "".
string message3 = System.String.Empty;

//Initialize with a regular string literal.
string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";

// Initialize with a verbatim string literal.
string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";

// Use System.String if you prefer.
System.String greeting = "Hello World!";

// In local variables (i.e. within a method body)
// you can use implicit typing.
var temp = "I'm still a strongly-typed System.String!";

// Use a const string to prevent 'message4' from
// being used to store another string value.
const string message4 = "You can't get rid of me!";

// Use the String constructor only when creating
// a string from a char*, char[], or sbyte*. See
// System.String documentation for details.
char[] letters = { 'A', 'B', 'C' };
string alphabet = new string(letters);

注意，除了在使用字符数组初始化字符串时以外，不要使用 new 运算符创建字符串对象。

使用 Empty 常量值初始化字符串可新建字符串长度为零的 String 对象。零长度字符串的字符串表示形式为 ""。使用 Empty 值（而不是 null）初始化字符串可以降低发生 NullReferenceException 的可能性。请在尝试访问字符串之前使用静态 IsNullOrEmpty(String) 方法验证字符串的值。

字符串对象的不可变性

字符串对象是不可变的：即它们创建之后就无法更改。所有看似修改字符串的 String 方法和 C# 运算符实际上都以新字符串对象的形式返回结果。在下面的示例中，当连接 s1 和 s2 的内容以形成一个字符串时，不会修改两个原始字符串。+= 运算符会创建一个包含组合内容的新字符串。这个新对象赋给变量 s1，而最初赋给 s1 的对象由于没有其他任何变量包含对它的引用而释放，用于垃圾回收。

C# 复制代码
string s1 = "A string is more ";
string s2 = "than the sum of its chars.";

// Concatenate s1 and s2. This actually creates a new
// string object and stores it in s1, releasing the
// reference to the original object.
s1 += s2;

System.Console.WriteLine(s1);
// Output: A string is more than the sum of its chars.

由于“修改”字符串实际上是创建新字符串，因此创建对字符串的引用时必须谨慎。如果创建了对字符串的引用，然后“修改”原始字符串，则该引用指向的仍是原始对象，而不是修改字符串时创建的新对象。下面的代码说明了这种行为：

C# 复制代码
string s1 = "Hello ";
string s2 = s1;
s1 += "World";

System.Console.WriteLine(s2);
//Output: Hello

有关如何创建基于修改（例如搜索和替换原始字符串的操作）的新字符串的更多信息，请参见如何：修改字符串内容（C# 编程指南）。

正则字符串和原义字符串

如果必须嵌入 C# 提供的转义符，则应使用正则字符串，如下面的示例所示：

C# 复制代码
string columns = "Column 1\tColumn 2\tColumn 3";
//Output: Column 1        Column 2        Column 3

string rows = "Row 1\r\nRow 2\r\nRow 3";
/* Output:
  Row 1
  Row 2
  Row 3
*/

string title = "\"The \u00C6olean Harp\", by Samuel Taylor Coleridge";
//Output: "The Æolean Harp", by Samuel Taylor Coleridge

如果字符串文本包含反斜杠字符（例如在文件路径中），为方便起见和提高可读性，应使用原义字符串。由于原义字符串保留换行符作为字符串文本的一部分，因此可用于初始化多行字符串。在原义字符串中嵌入引号时请使用双引号。下面的示例演示原义字符串的一些常见用途：

C# 复制代码
         string filePath = @"C:\Users\scoleridge\Documents\";
         //Output: C:\Users\scoleridge\Documents\

         string text = @"My pensive SARA ! thy soft cheek reclined
Thus on mine arm, most soothing sweet it is
To sit beside our Cot,...";
         /* Output:
         My pensive SARA ! thy soft cheek reclined
            Thus on mine arm, most soothing sweet it is
            To sit beside our Cot,...
         */

         string quote = @"Her name was ""Sara.""";
         //Output: Her name was "Sara."

字符串转义序列

转义序列 | 字符名称 | Unicode 编码
\' | 单引号 | 0x0027
\" | 双引号 | 0x0022
\\ | 反斜杠 | 0x005C
\0 | Null | 0x0000
\a | 警报 | 0x0007
\b | 回格 | 0x0008
\f | 换页 | 0x000C
\n | 换行 | 0x000A
\r | 回车 | 0x000D
\t | 水平制表符 | 0x0009
\U | 代理项对的 Unicode 转义序列。 | \Unnnnnnnn
\u | Unicode 转义序列 | \u0041 = "A"
\v | 垂直制表符 | 0x000B
\x | Unicode 转义序列类似于“\u”，只是长度可变。 | \x0041 = "A"
说明：

编译时，原义字符串转换为所有转义序列均保持不变的普通字符串。因而，如果在调试器监视窗口中查看原义字符串，则看到的将是编译器添加的转义字符，而不是源代码中的原义版本。例如，原义字符串 @”C:\files.txt” 在监视窗口中将显示为 “C:\\files.txt”。

格式字符串

格式字符串是内容可以在运行时动态确定的一种字符串。采用以下方式创建格式字符串：使用静态 Format 方法并在大括号中嵌入占位符，这些占位符将在运行时替换为其他值。下面的示例使用格式字符串输出循环中每个迭代的结果：

C# 复制代码
class FormatString
{
    static void Main()
    {
        // Get user input.
        System.Console.WriteLine("Enter a number");
        string input = System.Console.ReadLine();

        // Convert the input string to an int.
        int j;
        System.Int32.TryParse(input, out j);

        // Write a different string each iteration.
        string s;
        for (int i = 0; i < 10; i++)
        {
            // A simple format string with no alignment formatting.
            s = System.String.Format("{0} times {1} = {2}", i, j, (i * j));
            System.Console.WriteLine(s);
        }

        //Keep the console window open in debug mode.
        System.Console.ReadKey();
    }
}

WriteLine 方法的一个重载将格式字符串用作参数。因此，可以只嵌入格式字符串，而无需显式调用该方法。但若使用 WriteLine 方法在 Visual Studio“输出”窗口中显示调试输出，则必须显式调用 Format 方法，因为 WriteLine 只接受字符串，而不接受格式字符串。有关格式字符串的更多信息，请参见格式化类型。

子字符串

子字符串是包含在字符串中的任意字符序列。使用 Substring 方法可以基于原始字符串的一部分创建新字符串。可以使用 IndexOf 方法搜索子字符串的一个或多个匹配项。使用 Replace 方法可将指定子字符串的所有匹配项替换为一个新字符串。与 Substring 方法一样，Replace 实际上返回的也是新字符串，而不修改原始字符串。有关更多信息，请参见如何：使用字符串方法搜索字符串（C# 编程指南）和如何：修改字符串内容（C# 编程指南）。

C# 复制代码
string s3 = "Visual C# Express";
System.Console.WriteLine(s3.Substring(7, 2));
// Output: "C#"

System.Console.WriteLine(s3.Replace("C#", "Basic"));
// Output: "Visual Basic Express"

// Index values are zero-based
int index = s3.IndexOf("C");
// index = 7

访问各个字符

可以使用带索引值的数组表示法获取对各个字符的只读访问，如下面的示例所示：

C# 复制代码
string s5 = "Printing backwards";

for (int i = 0; i < s5.Length; i++)
{
    System.Console.Write(s5[s5.Length - i - 1]);
}
// Output: "sdrawkcab gnitnirP"

如果 String 方法不提供修改字符串中的各个字符所必须具有的功能，则您可以使用 StringBuilder 对象“就地”修改各个字符，然后使用 StringBuilder 方法创建一个新字符串来存储结果。在下面的示例中，假设您必须以特定方式修改原始字符串，然后存储结果以备将来使用：

C# 复制代码
string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";
System.Text.StringBuilder sb = new System.Text.StringBuilder(question);

for (int j = 0; j < sb.Length; j++)
{
    if (System.Char.IsLower(sb[j]) == true)
        sb[j] = System.Char.ToUpper(sb[j]);
    else if (System.Char.IsUpper(sb[j]) == true)
        sb[j] = System.Char.ToLower(sb[j]);
}
// Store the new string.
string corrected = sb.ToString();
System.Console.WriteLine(corrected);
// Output: How does Microsoft Word deal with the Caps Lock key?

Null 字符串和空字符串

空字符串是不包含字符的 System..::.String 对象的实例。在各种编程方案中经常会使用空字符串表示空白文本字段。可以对空字符串调用方法，因为它们是有效的 System..::.String 对象。空字符串可按如下方式初始化：

复制代码
string s = String.Empty;

相反，null 字符串并不引用 System..::.String 对象的实例，任何对 null 字符串调用方法的尝试都会生成 NullReferenceException。但是，可以在串联和比较操作中将 null 字符串与其他字符串一起使用。下面的示例阐释了引用 null 字符串导致引发异常的情形以及并不导致引发异常的情形：

C# 复制代码
static void Main()
{
    string str = "hello";
    string nullStr = null;
    string emptyStr = "a";

    string tempStr = str + nullStr; // tempStr = "hello"
    bool b = (emptyStr == nullStr);// b = false;
    string newStr = emptyStr + nullStr; // creates a new empty string
    int len = nullStr.Length; // throws NullReferenceException

}

使用 StringBuilder 快速创建字符串

.NET 中的字符串操作已高度优化，大多数情况下不会显著影响性能。但在某些应用场景中，例如在执行好几百甚至好几千次的紧凑循环中，字符串操作会影响性能。StringBuilder 类创建了一个字符串缓冲区，用于在程序执行大量字符串操作时提供更好的性能。StringBuilder 字符串还使您能够重新分配个别字符（内置字符串数据类型所不支持的字符）。例如，此代码在不创建新字符串的情况下更改了一个字符串的内容：

C# 复制代码
System.Text.StringBuilder sb = new System.Text.StringBuilder("Rat: the ideal pet");
sb[0] = 'C';
System.Console.WriteLine(sb.ToString());
System.Console.ReadLine();

//Outputs Cat: the ideal pet

在本示例中，StringBuilder 对象用于从一组数值类型中创建字符串：

C# 复制代码
class TestStringBuilder
{
    static void Main()
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();

        // Create a string composed of numbers 0 - 9
        for (int i = 0; i < 10; i++)
        {
            sb.Append(i.ToString());
        }
        System.Console.WriteLine(sb);  // displays 0123456789

        // Copy one character of the string (not possible with a System.String)
        sb[0] = sb[9];

        System.Console.WriteLine(sb);  // displays 9123456789
    }
}

字符串、扩展方法和 LINQ

由于 String 类型实现 IEnumerable<(Of <(T>)>)，因此可以对字符串使用 Enumerable 类中定义的扩展方法。对于 String 类型，为了避免视觉上的混乱，从 IntelliSense 中排除了这些方法，但这些方法仍然可用。此外，还可以对字符串使用 LINQ 查询表达式。有关更多信息，请参见 LINQ 和字符串。

请参见

任务

如何：使用正则表达式搜索字符串（C# 编程指南）
如何：使用字符串方法搜索字符串（C# 编程指南）
如何：拆分字符串（C# 编程指南）

概念

C# 编程指南

其他资源

分析字符串


如何：串联多个字符串（C# 编程指南）

[image]

C# 编程指南
如何：串联多个字符串（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

串联是将一个字符串追加到另一个字符串末尾的过程。使用 + 运算符串联字符串文本或字符串常量时，编译器会创建一个字符串。串联不在运行时发生。但字符串变量只能在运行时串联，对此，您应该了解各种方法的性能含义。

示例

下面的示例演示如何将一个长字符串拆分为几个较短的字符串，从而提高源代码的可读性。这些较短的字符串将在编译时串联成一个字符串。无论涉及到多少个字符串，都不会有运行时性能开销。

C# 复制代码
static void Main()
{
    // Concatenation of literals is performed at compile time, not run time.
    string text = @"Historically, the world of data and the world of objects " +
    "have not been well integrated. Programmers work in C# or Visual Basic " +
    "and also in SQL or XQuery. On the one side are concepts such as classes, " +
    "objects, fields, inheritance, and .NET Framework APIs. On the other side " +
    "are tables, columns, rows, nodes, and separate languages for dealing with " +
    "them. Data types often require translation between the two worlds; there are " +
    "different standard functions. Because the object world has no notion of query, a " +
    "query can only be represented as a string without compile-time type checking or " +
    "IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to " +
    "objects in memory is often tedious and error-prone.";

    Console.WriteLine(text);
}

若要串联字符串变量，可以使用 + 或 += 运算符，也可以使用 String..::.Concat、String..::.Format 或 StringBuilder..::.Append 方法。+ 运算符容易使用，且有利于提高代码的直观性，但它是连续工作的，即每次使用该运算符都会创建一个新字符串。因此，将多个运算符链接在一起某种程度上是比较低效的。例如，考虑下面的代码：

C# 复制代码
static void Main(string[] args)
{
    // To run this program, provide a command line string.
    // In Visual Studio, see Project > Properties > Debug.
    string userName = args[0];
    string date = DateTime.Today.ToShortDateString();

    // Use the + and += operators for one-time concatenations.
    string str = "Hello " + userName + ". Today is " + date + ".";
    System.Console.WriteLine(str);

    str += " How are you today?";
    System.Console.WriteLine(str);

    // Keep the console window open in debug mode.
    Console.WriteLine("Press any key to exit.");
    Console.ReadKey();
}

// Example output:
//  Hello Alexander. Today is 1/22/2008.
//  Hello Alexander. Today is 1/22/2008. How are you today?
//  Press any key to exit.
//
说明：

在字符串串联操作中，C# 编译器对 null 字符串和空字符串进行相同的处理，但它不转换原始 null 字符串的值。

如果您串联的字符串数量不那么巨大（例如，在循环中），那么这些代码的性能成本可能不会很高。上述情况同样适用于 String..::.Concat 和 String..::.Format 方法。

但如果性能的优劣很重要，则应该总是使用 StringBuilder 类来串联字符串。下面的代码使用 StringBuilder 类的 Append 方法来串联字符串，因此不会有 + 运算符的链接作用产生。

C# 复制代码
class StringBuilderTest
{
    static void Main()
    {
        string text = null;

        // Use StringBuilder for concatenation in tight loops.
        System.Text.StringBuilder sb = new System.Text.StringBuilder();
        for (int i = 0; i < 100; i++)
        {
            sb.AppendLine(i.ToString());
        }
        System.Console.WriteLine(sb.ToString());

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
// Output:
// 0
// 1
// 2
// 3
// 4
// ...
//

请参见

概念

C# 编程指南

参考

字符串（C# 编程指南）
String
StringBuilder

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


如何：修改字符串内容（C# 编程指南）

[image]

C# 编程指南
如何：修改字符串内容（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

由于字符串是不可变的，因此一个字符串对象一旦创建，值就不能再更改（在不使用不安全代码的情况下）。不过，修改字符串的值然后将结果存储到新的字符串对象中有很多种方法。System..::.String 类提供作用于输入字符串并返回新字符串对象的方法。在很多情况下，可以将这个新对象赋给保存原始字符串的变量。System.Text.RegularExpressions..::.Regex 类提供其他一些以类似方式工作的方法。System.Text..::.StringBuilder 类提供一个可“就地”修改的字符缓冲区。调用 StringBuilder..::.ToString 方法可新建包含此缓冲区的当前内容的字符串对象。

示例

下面的示例演示替换或移除指定字符串中的子字符串的各种方法。

C# 复制代码
class ReplaceSubstrings
{
    string searchFor;
    string replaceWith;

    static void Main(string[] args)
    {

        ReplaceSubstrings app = new ReplaceSubstrings();
        string s = "The mountains are behind the clouds today.";

        // Replace one substring with another with String.Replace.
        // Only exact matches are supported.
        s = s.Replace("mountains", "peaks");
        Console.WriteLine(s);
        // Output: The peaks are behind the clouds today.

        // Use Regex.Replace for more flexibility.
        // Replace "the" or "The" with "many" or "Many".
        // using System.Text.RegularExpressions
        app.searchFor = "the"; // A very simple regular expression.
        app.replaceWith = "many";
        s = Regex.Replace(s, app.searchFor, app.ReplaceMatchCase, RegexOptions.IgnoreCase);
        Console.WriteLine(s);
        // Output: Many peaks are behind many clouds today.

        // Replace all occurrences of one char with another.
        s = s.Replace(' ', '_');
        Console.WriteLine(s);
        // Output: Many_peaks_are_behind_many_clouds_today.

        // Remove a substring from the middle of the string.
        string temp = "many_";
        int i = s.IndexOf(temp);
        if (i >= 0)
        {
            s = s.Remove(i, temp.Length);
        }
        Console.WriteLine(s);
        // Output: Many_peaks_are_behind_clouds_today.

        // Remove trailing and leading whitespace.
        // See also the TrimStart and TrimEnd methods.
        string s2 = "    I'm wider than I need to be.      ";
        // Store the results in a new string variable.
        temp = s2.Trim();
        Console.WriteLine(temp);
        // Output: I'm wider than I need to be.

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }

    // Custom match method called by Regex.Replace
    // using System.Text.RegularExpressions
    string ReplaceMatchCase(Match m)
    {
        // Test whether the match is capitalized
        if (Char.IsUpper(m.Value[0]) == true)
        {
            // Capitalize the replacement string
            // using System.Text;
            StringBuilder sb = new StringBuilder(replaceWith);
            sb[0] = (Char.ToUpper(sb[0]));
            return sb.ToString();
        }
        else
        {
            return replaceWith;
        }
    }
}

若要使用数组表示法访问字符串中的各个字符，可以使用 StringBuilder 对象，该对象重载 [] 运算符以提供对其内部字符缓冲区的访问。也可以使用 ToCharArray 方法将该字符串转换为一个字符数组。下面的示例使用 ToCharArray 创建该数组。然后修改该数组中的某些元素。之后再调用采用一个字符数组作为输入参数的字符串构造函数来创建一个新字符串。

C# 复制代码
class ModifyStrings
{
    static void Main()
    {
        string str = "The quick brown fox jumped over the fence";
        System.Console.WriteLine(str);

        char[] chars = str.ToCharArray();
        int animalIndex = str.IndexOf("fox");
        if (animalIndex != -1)
        {
            chars[animalIndex++] = 'c';
            chars[animalIndex++] = 'a';
            chars[animalIndex] = 't';
        }

        string str2 = new string(chars);
        System.Console.WriteLine(str2);

        // Keep the console window open in debug mode
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
  The quick brown fox jumped over the fence
  The quick brown cat jumped over the fence
*/

下面的示例针对的是一种非常罕见的情况，即您可能希望使用不安全代码以类似于 C 样式字符数组的方式就地修改字符串。此示例演示如何使用 fixed 关键字“就地”访问各个字符。此外还演示对字符串进行不安全操作可能产生的一个副作用，此副作用是由于 C# 编译器在内部存储（扣留）字符串的方式而导致的。通常，除非绝对必要，否则不应该使用这种方法。

C# 复制代码
class UnsafeString
{
    unsafe static void Main(string[] args)
    {
        // Compiler will store (intern)
        // these strings in same location.
        string s1 = "Hello";
        string s2 = "Hello";

        // Change one string using unsafe code.
        fixed (char* p = s1)
        {
            p[0] = 'C';
        }

        //  Both strings have changed.
        Console.WriteLine(s1);
        Console.WriteLine(s2);

        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

}

请参见

概念

C# 编程指南

参考

字符串（C# 编程指南）

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 进行了大量更改并增加了其他一些代码示例，以提高准确性和完整性。 | 内容 Bug 修复


如何：比较字符串（C# 编程指南）

[image]

C# 编程指南
如何：比较字符串（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

比较字符串时，产生的结果会是一个字符串大于或小于另一个字符串，或者两个字符串相等。根据执行的是序号比较还是区分区域性比较，确定结果时所依据的规则会有所不同。对特定的任务使用正确类型的比较十分重要。

当需要对两个字符串的值进行比较和排序而不需要考虑语言惯例时，请使用基本的序号比较。基本的序号比较 (Ordinal) 是区分大小写的，这意味着两个字符串的字符必须完全匹配：“and”不等于“And”或“AND”。常用的变量有 OrdinalIgnoreCase，它将匹配“and”、“And”和“AND”；还有 StringComparison.OrdinalIgnoreCase，它常用于比较文件名、路径名和网络路径，以及其值不随用户计算机的区域设置的更改而变化的任何其他字符串。

区分区域性比较通常用于对终端用户输入的字符串进行比较和排序，因为这些字符串的字符和排序约定可能会根据用户计算机的区域设置的不同而有所不同。即使是包含相同字符的字符串，也可能会根据当前线程的区域性不同而不同。

说明：

在比较字符串时，您使用的方法应该显式指定了要执行的比较类型。这可增强代码的可维护性和可读性。应尽可能使用具有 StringComparison 枚举参数的 System..::.String 和 System..::.Array 类的方法重载，以便可以指定要执行的比较类型。比较字符串时，最好避免使用 == 和 != 运算符。还应该避免使用 String..::.CompareTo 实例方法，因为这些重载没有 StringComparison。

示例

下面的示例演示如何正确地比较其值将不随用户计算机的区域设置的改变而变化的字符串。此外，它还演示 C# 中的字符串限定功能。如果程序声明了两个或更多个相同的字符串变量，编译器会将它们存储在同一位置。通过调用 ReferenceEquals 方法，可以看到这两个字符串实际引用内存中的同一对象。使用 String..::.Copy 方法可避免此限定，如下面的示例所示。

C# 复制代码
// Internal strings that will never be localized.
string root = @"C:\users";
string root2 = @"C:\Users";

// Use the overload of the Equals method that specifies a StringComparison.
// Ordinal is the fastest way to compare two strings.
bool result = root.Equals(root2, StringComparison.Ordinal);

Console.WriteLine("Ordinal comparison: {0} and {1} are {2}", root, root2,
                    result ? "equal." : "not equal.");

// To ignore case means "user" equals "User". This is the same as using
// String.ToUpperInvariant on each string and then performing an ordinal comparison.
result = root.Equals(root2, StringComparison.OrdinalIgnoreCase);
Console.WriteLine("Ordinal ignore case: {0} and {1} are {2}", root, root2,
                     result ? "equal." : "not equal.");

// A static method is also available.
bool areEqual = String.Equals(root, root2, StringComparison.Ordinal);

// String interning. Are these really two distinct objects?
string a = "The computer ate my source code.";
string b = "The computer ate my source code.";

// ReferenceEquals returns true if both objects
// point to the same location in memory.
if (String.ReferenceEquals(a, b))
    Console.WriteLine("a and b are interned.");
else
    Console.WriteLine("a and b are not interned.");

// Use String.Copy method to avoid interning.
string c = String.Copy(a);

if (String.ReferenceEquals(a, c))
    Console.WriteLine("a and c are interned.");
else
    Console.WriteLine("a and c are not interned.");

下面的示例演示如何通过首选方式比较字符串，该首选方式使用具有 StringComparison 枚举的 System..::.String 方法。请注意，这里没有使用 String..::.CompareTo 实例方法，因为这些重载没有 StringComparison。

C# 复制代码
// "They dance in the street."
// Linguistically (in Windows), "ss" is equal to
// the German essetz: 'ß' character in both en-US and de-DE cultures.
string first = "Sie tanzen in die Straße.";
string second = "Sie tanzen in die Strasse.";

Console.WriteLine("First sentence is {0}", first);
Console.WriteLine("Second sentence is {0}", second);

// Store CultureInfo for the current culture. Note that the original culture
// can be set and retrieved on the current thread object.
System.Threading.Thread thread = System.Threading.Thread.CurrentThread;
System.Globalization.CultureInfo originalCulture = thread.CurrentCulture;

// Set the culture to en-US.
thread.CurrentCulture = new System.Globalization.CultureInfo("en-US");

// For culture-sensitive comparisons, use the String.Compare
// overload that takes a StringComparison value.
int i = String.Compare(first, second, StringComparison.CurrentCulture);
Console.WriteLine("Comparing in {0} returns {1}.", originalCulture.Name, i);

// Change the current culture to Deutch-Deutchland.
thread.CurrentCulture = new System.Globalization.CultureInfo("de-DE");
i = String.Compare(first, second, StringComparison.CurrentCulture);
Console.WriteLine("Comparing in {0} returns {1}.", thread.CurrentCulture.Name, i);

// For culture-sensitive string equality, use either StringCompare as above
// or the String.Equals overload that takes a StringComparison value.
thread.CurrentCulture = originalCulture;
bool b = String.Equals(first, second, StringComparison.CurrentCulture);
Console.WriteLine("The two strings {0} equal.", b == true ? "are" : "are not");

/*
 * Output:
    First sentence is Sie tanzen in die Straße.
    Second sentence is Sie tanzen in die Strasse.
    Comparing in current culture returns 0.
    The two strings are equal.
 */

下面的示例演示如何使用具有 System..::.StringComparer 参数的静态 Array 方法以区分区域性的方式对数组中的字符串进行排序和搜索。

C# 复制代码
class SortStringArrays
{
    static void Main()
    {

        string[] lines = new string[]
        {
            @"c:\public\textfile.txt",
            @"c:\public\textFile.TXT",
            @"c:\public\Text.txt",
            @"c:\public\testfile2.txt"
        };

        Console.WriteLine("Non-sorted order:");
        foreach (string s in lines)
        {
            Console.WriteLine("   {0}", s);
        }

        Console.WriteLine("\n\rSorted order:");

        // Specify Ordinal to demonstrate the different behavior.
        Array.Sort(lines, StringComparer.Ordinal);

        foreach (string s in lines)
        {
            Console.WriteLine("   {0}", s);
        }

        string searchString = @"c:\public\TEXTFILE.TXT";
        Console.WriteLine("Binary search for {0}", searchString);
        int result = Array.BinarySearch(lines, searchString, StringComparer.OrdinalIgnoreCase);
        ShowWhere<string>(lines, result);

        //Console.WriteLine("{0} {1}", result > 0 ? "Found" : "Did not find", searchString);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }

    // Displays where the string was found, or, if not found,
    // where it would have been located.
    private static void ShowWhere<T>(T[] array, int index)
    {
        if (index < 0)
        {
            // If the index is negative, it represents the bitwise
            // complement of the next larger element in the array.
            index = ~index;

            Console.Write("Not found. Sorts between: ");

            if (index == 0)
                Console.Write("beginning of array and ");
            else
                Console.Write("{0} and ", array[index - 1]);

            if (index == array.Length)
                Console.WriteLine("end of array.");
            else
                Console.WriteLine("{0}.", array[index]);
        }
        else
        {
            Console.WriteLine("Found at index {0}.", index);
        }
    }

}
/*
 * Output:
    Non-sorted order:
       c:\public\textfile.txt
       c:\public\textFile.TXT
       c:\public\Text.txt
       c:\public\testfile2.txt

    Sorted order:
       c:\public\Text.txt
       c:\public\testfile2.txt
       c:\public\textFile.TXT
       c:\public\textfile.txt
    Binary search for c:\public\TEXTFILE.TXT
    Found at index 2.
 */

当元素或键的类型为 string 时，System.Collections..::.Hashtable、System.Collections.Generic..::.Dictionary<(Of <(TKey, TValue>)>) 和 System.Collections.Generic..::.List<(Of <(T>)>) 等集合类的构造函数具有 System..::.StringComparer 参数。通常，应尽可能使用这些构造函数，并指定 Ordinal 或 OrdinalIgnoreCase。

请参见

概念

比较字符串

参考

字符串（C# 编程指南）
System.Globalization..::.CultureInfo
System..::.StringComparer

其他资源

对应用程序进行全球化和本地化

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


如何：拆分字符串（C# 编程指南）

[image]

C# 编程指南
如何：拆分字符串（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的代码示例演示如何使用 String..::.Split 方法分析字符串。作为输入，Split 采用一个字符数组指示哪些字符被用作分隔符。本示例中使用了空格、逗号、句点、冒号和制表符。一个含有这些分隔符的数组被传递给 Split，并使用结果字符串数组分别显示句子中的每个单词。

示例

C# 复制代码
class TestStringSplit
{
    static void Main()
    {
        char[] delimiterChars = { ' ', ',', '.', ':', '\t' };

        string text = "one\ttwo three:four,five six seven";
        System.Console.WriteLine("Original text: '{0}'", text);

        string[] words = text.Split(delimiterChars);
        System.Console.WriteLine("{0} words in text:", words.Length);

        foreach (string s in words)
        {
            System.Console.WriteLine(s);
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    Original text: 'one     two three:four,five six seven'
    7 words in text:
    one
    two
    three
    four
    five
    six
    seven
 */

请参见

概念

C# 编程指南

参考

字符串（C# 编程指南）

其他资源

.NET Framework 正则表达式
.NET 编程指南


如何：使用字符串方法搜索字符串（C# 编程指南）

[image]

C# 编程指南
如何：使用字符串方法搜索字符串（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

string 类型（它是 System..::.String 类的别名）为搜索字符串的内容提供了许多有用的方法。

示例

下面的示例使用 IndexOf、LastIndexOf、StartsWith 和 EndsWith 方法搜素字符串。

C# 复制代码
class StringSearch
{
    static void Main()
    {
        string str = "Extension methods have all the capabilities of regular static methods.";

        // Write the string and include the quotation marks.
        System.Console.WriteLine("\"{0}\"", str);

        // Simple comparisons are always case sensitive!
        bool test1 = str.StartsWith("extension");
        System.Console.WriteLine("Starts with \"extension\"? {0}", test1);

        // For user input and strings that will be displayed to the end user,
        // use the StringComparison parameter on methods that have it to specify how to match strings.
        bool test2 = str.StartsWith("extension", System.StringComparison.CurrentCultureIgnoreCase);
        System.Console.WriteLine("Starts with \"extension\"? {0} (ignoring case)", test2);

        bool test3 = str.EndsWith(".", System.StringComparison.CurrentCultureIgnoreCase);
        System.Console.WriteLine("Ends with '.'? {0}", test3);

        // This search returns the substring between two strings, so
        // the first index is moved to the character just after the first string.
        int first = str.IndexOf("methods") + "methods".Length;
        int last = str.LastIndexOf("methods");
        string str2 = str.Substring(first, last - first);
        System.Console.WriteLine("Substring between \"methods\" and \"methods\": '{0}'", str2);

        // Keep the console window open in debug mode
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/*
Output:
"Extension methods have all the capabilities of regular static methods."
Starts with "extension"? False
Starts with "extension"? True (ignoring case)
Ends with '.'? True
Substring between "methods" and "methods": ' have all the capabilities of regular static '
Press any key to exit.
*/

请参见

任务

如何：使用正则表达式搜索字符串（C# 编程指南）

概念

C# 编程指南
LINQ 和字符串

参考

字符串（C# 编程指南）


如何：使用正则表达式搜索字符串（C# 编程指南）

[image]

C# 编程指南
如何：使用正则表达式搜索字符串（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可以使用 System.Text.RegularExpressions..::.Regex 类搜索字符串。这些搜索有的非常简单，有的复杂到需要完全使用正则表达式。以下是使用 Regex 类搜索字符串的两个示例。有关更多信息，请参见 .NET Framework 正则表达式。

示例

以下代码是一个控制台应用程序，用于对数组中的字符串执行简单的不区分大小写的搜索。给定要搜索的字符串和包含搜索模式的字符串后，静态方法 Regex..::.IsMatch 将执行搜索。在本例中，使用第三个参数指示忽略大小写。有关更多信息，请参见 System.Text.RegularExpressions..::.RegexOptions。

C# 复制代码
class TestRegularExpressions
{
    static void Main()
    {
        string[] sentences =
        {
            "cow over the moon",
            "Betsy the Cow",
            "cowering in the corner",
            "no match here"
        };

        string sPattern = "cow";

        foreach (string s in sentences)
        {
            System.Console.Write("{0,24}", s);

            if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase))
            {
                System.Console.WriteLine("  (match for '{0}' found)", sPattern);
            }
            else
            {
                System.Console.WriteLine();
            }
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();

    }
}
/* Output:
           cow over the moon  (match for 'cow' found)
           Betsy the Cow  (match for 'cow' found)
           cowering in the corner  (match for 'cow' found)
           no match here
*/

以下代码是一个控制台应用程序，此程序使用正则表达式验证数组中每个字符串的格式。验证要求每个字符串具有电话号码的形式，即用短划线将数字分成三组，前两组各包含三个数字，第三组包含四个数字。这是使用正则表达式 ^\\d{3}-\\d{3}-\\d{4}$ 完成的。有关更多信息，请参见正则表达式语言元素。

C# 复制代码
class TestRegularExpressionValidation
{
    static void Main()
    {
        string[] numbers =
        {
            "123-456-7890",
            "444-234-22450",
            "690-203-6578",
            "146-893-232",
            "146-839-2322",
            "4007-295-1111",
            "407-295-1111",
            "407-2-5555",
        };

        string sPattern = "^\\d{3}-\\d{3}-\\d{4}$";

        foreach (string s in numbers)
        {
            System.Console.Write("{0,14}", s);

            if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern))
            {
                System.Console.WriteLine(" - valid");
            }
            else
            {
                System.Console.WriteLine(" - invalid");
            }
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
      123-456-7890 - valid
     444-234-22450 - invalid
      690-203-6578 - valid
       146-893-232 - invalid
      146-839-2322 - valid
     4007-295-1111 - invalid
      407-295-1111 - valid
        407-2-5555 - invalid
*/

请参见

概念

C# 编程指南

参考

字符串（C# 编程指南）


如何：确定字符串是否表示数值（C# 编程指南）

[image]

C# 语言参考
如何：确定字符串是否表示数值（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

若要确定字符串是否是指定数值类型的有效表示形式，请使用静态的 TryParse 方法，该方法由所有基元数值类型以及诸如 DateTime 和 IPAddress 这样的类型实现。下面的示例演示如何确定“108”是否为有效的 int 类型。

复制代码
  int i = 0;
  string s = "108";
  bool result = int.TryParse(s, out i); //i now = 108

如果字符串包含非数值字符或者所包含的数值对于指定的特定类型而言太大或太小，TryParse 都将返回 false 并将 out 参数设置为零。否则，它将返回 true，并且将 out 参数设置为字符串的数值。

说明：

有时，虽然字符串可能只包含数值字符，对于您使用的 TryParse 方法所属的类型却仍然会无效。例如，“256”对于 byte 类型不是有效值，但对于 int 类型有效。“98.6”对于 int 类型不是有效值，但对于 decimal 类型有效。

示例

下面的示例演示如何对 long、byte 和 decimal 值的字符串表示形式使用 TryParse。

C# 复制代码
string numString = "1287543"; //"1287543.0" will return false for a long
long number1 = 0;
bool canConvert = long.TryParse(numString, out number1);
if (canConvert == true)
  Console.WriteLine("number1 now = {0}", number1);
else
  Console.WriteLine("numString is not a valid long");

byte number2 = 0;
numString = "255"; // A value of 256 will return false
canConvert = byte.TryParse(numString, out number2);
if (canConvert == true)
  Console.WriteLine("number2 now = {0}", number2);
else
  Console.WriteLine("numString is not a valid byte");

decimal number3 = 0;
numString = "27.3"; //"27" is also a valid decimal
canConvert = decimal.TryParse(numString, out number3);
if (canConvert == true)
  Console.WriteLine("number3 now = {0}", number3);
else
  Console.WriteLine("number3 is not a valid decimal");

可靠编程

基元数值类型还实现了 Parse 静态方法，此方法会在字符串不是有效数字时引发异常。TryParse 通常更加有效，因为它在数字无效时只是返回 false。

安全性

请始终使用 TryParse 或 Parse 方法来验证用户在文本框和组合框等控件中输入的内容。

请参见

任务

如何：将字节数组转换为 int（C# 编程指南）
如何：将字符串转换为整型（C# 编程指南）
如何：在十六进制字符串与数值类型之间转换（C# 编程指南）

概念

分析数值字符串

其他资源

转换类型
格式化类型


如何：将字符串转换为 DateTime（C# 编程指南）

[image]

C# 编程指南
如何：将字符串转换为 DateTime（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

通常程序需要支持用户以字符串值的形式输入日期。若要将基于字符串的日期转换为 System..::.DateTime 对象，可以使用 Convert..::.ToDateTime(String) 方法或 DateTime..::.Parse 静态方法，如下面的示例所示。

示例

C# 复制代码
// Date strings are interpreted according to the current culture.
// If the culture is en-US, this is interpreted as "January 8, 2008",
// but if the user's computer is fr-FR, this is interpreted as "August 1, 2008"
string date = "01/08/2008";
DateTime dt = Convert.ToDateTime(date);
Console.WriteLine("Year: {0}, Month: {1}, Day: {2}", dt.Year, dt.Month, dt.Day);

// Specify exactly how to interpret the string.
IFormatProvider culture = new System.Globalization.CultureInfo("fr-FR", true);

// Alternate choice: If the string has been input by an end user, you might
// want to format it according to the current culture:
// IFormatProvider culture = System.Threading.Thread.CurrentThread.CurrentCulture;
DateTime dt2 = DateTime.Parse(date, culture, System.Globalization.DateTimeStyles.AssumeLocal);
Console.WriteLine("Year: {0}, Month: {1}, Day {2}", dt2.Year, dt2.Month, dt2.Day);

/* Output (assuming first culture is en-US and second is fr-FR):
    Year: 2008, Month: 1, Day: 8
    Year: 2008, Month: 8, Day 1
 */

请参见

参考

字符串（C# 编程指南）

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


如何：在旧式编码与 Unicode 之间转换（C# 编程指南）

[image]

C# 编程指南
如何：在旧式编码与 Unicode 之间转换（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

在 C# 中，内存中的所有字符串都是按 Unicode (UTF-16) 编码的。将数据从存储器移动到 string 对象中后，数据将自动转换为 UTF-16。如果数据仅包含从 0 到 127 的 ASCII 值，则此转换无需您执行任何额外的工作。但若源文本包含扩展的 ASCII 字节值（128 到 255），则默认情况下，将根据当前代码页解释扩展字符。若要指定应该根据其他某个代码页解释源文本，请使用 System.Text..::.Encoding 类，如下面的示例所示。

示例

下面的示例演示如何转换按 8 位 ASCII 编码的文本文件，此转换根据 Windows 代码页 737 解释源文本。

C# 复制代码
class ANSIToUnicode
{
    static void Main()
    {
        // Create a file that contains the Greek work ψυχή (psyche) when interpreted by using
        // code page 737 ((DOS) Greek). You can also create the file by using Character Map
        // to paste the characters into Microsoft Word and then "Save As" by using the DOS
        // (Greek) encoding. (Word will actually create a six-byte file by appending "\r\n" at the end.)
        System.IO.File.WriteAllBytes(@"greek.txt", new byte[] { 0xAF, 0xAC, 0xAE, 0x9E });

        // Specify the code page to correctly interpret byte values
        Encoding encoding = Encoding.GetEncoding(737); //(DOS) Greek code page
        byte[] codePageValues = System.IO.File.ReadAllBytes(@"greek.txt");

        // Same content is now encoded as UTF-16
        string unicodeValues = encoding.GetString(codePageValues);

        // Show that the text content is still intact in Unicode string
        // (Add a reference to System.Windows.Forms.dll)
        System.Windows.Forms.MessageBox.Show(unicodeValues);

        // Same content "ψυχή" is stored as UTF-8
        System.IO.File.WriteAllText(@"greek_unicode.txt", unicodeValues);

        // Conversion is complete. Show the bytes to prove the conversion.
        Console.WriteLine("8-bit encoding byte values:");
        foreach(byte b in codePageValues)
            Console.Write("{0:X}-", b);

        Console.WriteLine();
        Console.WriteLine("Unicode values:");
        string unicodeString = System.IO.File.ReadAllText("greek_unicode.txt");
        System.Globalization.TextElementEnumerator enumerator =
            System.Globalization.StringInfo.GetTextElementEnumerator(unicodeString);
        while(enumerator.MoveNext())
        {
           string s = enumerator.GetTextElement();
           int i = Char.ConvertToUtf32(s, 0);
           Console.Write("{0:X}-", i);
        }
        Console.WriteLine();

        // Keep the console window open in debug mode.
        Console.Write("Press any key to exit.");
        Console.ReadKey();
    }
    /*
     * Output:
        8-bit encoding byte values:
        AF-AC-AE-9E
        Unicode values:
        3C8-3C5-3C7-3B7
    */
}

请参见

参考

字符串（C# 编程指南）

修订记录

日期 | History | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


如何：将 RTF 转换为纯文本（C# 编程指南）

[image]

C# 编程指南
如何：将 RTF 转换为纯文本（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

RTF 格式是 Microsoft 于 20 世纪 80 年代后期开发的一种文件格式，旨在实现不同操作系统之间的文档交换。Microsoft Word 和 WordPad 都可以读写 RTF 文件。在 .NET Framework 中，可以使用 RichTextBox 控件创建支持 RTF 且支持用户以 WYSIWIG 方式将格式应用于文本的字处理器。

也可以使用 RichTextBox 以编程方式将 RTF 格式代码从文档中移除，从而将该文档转换为纯文本。执行这种类型的操作无需在 Windows 窗体中嵌入该控件。

在项目中使用 RichTextBox 控件

  1. 添加对 System.Windows.Forms.dll 的引用。

  2. 为 System.Windows.Forms 命名空间添加 using 指令（可选）。

示例

下面的示例提供一个要转换的示例 RTF 文件。该文件包含 RTF 格式，例如字体信息，还包含四个 Unicode 字符和四个扩展的 ASCII 字符。该文件打开，以 RTF 格式传递到 RichTextBox，作为文本检索，在 MessageBox 中显示并且输出到一个 UTF-8 格式的文件中。

C# 复制代码
    // Save the following RTF file to the same folder as your .exe file, and call it "test.rtf".
    /*
    {\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fswiss\fcharset0 Arial;}{\f1\fnil\fprq1\fcharset0 Courier New;}{\f2\fswiss\fprq2\fcharset0 Arial;}}
{\colortbl ;\red0\green128\blue0;\red0\green0\blue0;}
{\*\generator Msftedit 5.41.21.2508;}\viewkind4\uc1\pard\f0\fs20 This is the \i Greek \i0 word "psyche": \cf1\f1\u968?\u965?\u967?\u942?\cf2\f2 . It is encoded in Unicode.\par
Here are four extended \b ASCII \b0 characters (Windows code page 1252):  \'e2\'e4\u1233?\'e5\cf0\par
}
     */
    class ConvertFromRTF
    {
        static void Main()
        {

            string path = @"test2.rtf";

            //Create the RichTextBox. (Requires a reference to System.Windows.Forms.dll.)
            System.Windows.Forms.RichTextBox rtBox = new System.Windows.Forms.RichTextBox();

            // Get the contents of the RTF file. Note that when it is
            // stored in the string, it is encoded as UTF-16.
            string s = System.IO.File.ReadAllText(path);

            // Display the RTF text.
            System.Windows.Forms.MessageBox.Show(s);

            // Convert the RTF to plain text.
            rtBox.Rtf = s;
            string plainText = rtBox.Text;

            // Display plain text output in MessageBox because console
            // cannot display Greek letters.
            System.Windows.Forms.MessageBox.Show(plainText);

            // Output plain text to file, encoded as UTF-8.
            System.IO.File.WriteAllText(@"output.txt", plainText);
        }
    }

RTF 字符以八位编码。但此格式除了允许用户从指定代码页扩展 ASCII 字符外，还允许用户指定 Unicode 字符。由于 RichTextBox..::.Text 属性为 string 类型，因此字符按 Unicode UTF-16 进行编码。源 RTF 文档中的所有扩展的 ASCII 字符和 Unicode 字符都在文本输出中进行了正确的编码。

如果使用 File..::.WriteAllText 方法将文本写入磁盘，则文本将按 UTF-8（没有字节顺序标记）进行编码。

请参见

参考

字符串（C# 编程指南）
System.Windows.Forms..::.RichTextBox

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


语句、表达式和运算符（C# 编程指南）

[image]

C# 编程指南
语句、表达式和运算符（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

本节包含关于构成 C# 程序的基本元素的信息。构成应用程序的 C# 代码由包含 C# 关键字、表达式和运算符的语句组成。

有关更多信息，请参见：

  • 语句（C# 编程指南）

  • 表达式（C# 编程指南）

  • 运算符（C# 编程指南）

  • 匿名函数（C# 编程指南）

  • 可重载运算符（C# 编程指南）

  • 转换运算符（C# 编程指南）

    • 使用转换运算符（C# 编程指南）

    • 如何：在结构间实现用户定义的转换（C# 编程指南）

  • 如何：使用运算符重载创建复数类（C# 编程指南）

  • Equals() 和运算符 == 的重写准则（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.4 表达式

  • 1.5 语句

  • 1.6.7.5 运算符

  • 5.3.3 确定明确赋值的细则

  • 7 表达式

  • 7.2 运算符

  • 8 语句

请参见

概念

C# 编程指南

参考

强制转换和类型转换（C# 编程指南）


语句（C# 编程指南）

[image]

C# 编程指南
语句（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

程序所执行的操作以“语句”表达。常见操作包括声明变量、赋值、调用方法、循环访问集合，以及根据给定条件分支到一个或另一个代码块。语句在程序中的执行顺序称为“控制流”或“执行流”。根据程序对运行时所收到的输入的响应，在程序每次运行时控制流可能有所不同。

语句可以是以分号结尾的单行代码，或者是语句块中的一系列单行语句。语句块括在括号 {} 中，并且可以包含嵌套块。下面的代码演示两个单行语句示例和一个多行语句块：

C# 复制代码
    static void Main()
    {
        // Declaration statement.
        int counter;

        // Assignment statement.
        counter = 1;

        // Error! This is an expression, not an expression statement.
        // counter + 1;

        // Declaration statements with initializers are functionally
        // equivalent to a declaration statement followed by assignment statement:
        int[] radii = { 15, 32, 108, 74, 9 }; // Declare and initialize an array.
        const double pi = 3.14159; // Declare and initialize a constant.

        // foreach statement block that contains multiple statements.
        foreach (int radius in radii)
        {
            // Declaration statement with initializer.
            double circumference = pi * (2 * radius);

            // Expression statement (method invocation). A single-line
            // statement can span multiple text lines because line breaks
            // are treated as white space, which is ignored by the compiler.
            System.Console.WriteLine("Radius of circle #{0} is {1}. Circumference = {2:N2}",
                                    counter, radius, circumference);

            // Expression statement (postfix increment).
            counter++;

        } // End of foreach statement block
    } // End of Main method body.
} // End of SimpleStatements class.
/*
   Output:
    Radius of circle #1 = 15. Circumference = 94.25
    Radius of circle #2 = 32. Circumference = 201.06
    Radius of circle #3 = 108. Circumference = 678.58
    Radius of circle #4 = 74. Circumference = 464.96
    Radius of circle #5 = 9. Circumference = 56.55
*/

语句的类型

下表列出 C# 中的各种语句类型及其关联的关键字，并提供指向包含更多信息的主题的链接：

类别

C# 关键字/说明

声明语句

声明语句引入新的变量或常量。变量声明可以选择为变量赋值。在常量声明中必须赋值。

C# 复制代码
// Variable declaration statements.
double area;
double radius = 2;

// Constant declaration statement.
const double pi = 3.14159;

表达式语句

用于计算值的表达式语句必须在变量中存储该值。

C# 复制代码
// Expression statement (assignment).
area = 3.14 * (radius * radius);

// Error. Not a statement because no assignment:
//circ * 2;

// Expression statement (method invocation).
System.Console.WriteLine();

// Expression statement (new object creation).
System.Collections.Generic.List<string> strings =
    new System.Collections.Generic.List<string>();

选择语句

选择语句用于根据一个或多个指定条件分支到不同的代码段。有关更多信息，请参见下列主题：

if, else, switch, case

迭代语句

迭代语句用于遍历集合（如数组），或重复执行同一组语句直到满足指定的条件。有关更多信息，请参见下列主题：

do, for, foreach, in, while

跳转语句

跳转语句将控制转移给另一代码段。有关更多信息，请参见下列主题：

break, continue, default, goto, return，yield

异常处理语句

异常处理语句用于从运行时发生的异常情况正常恢复。有关更多信息，请参见下列主题：

throw, try-catch, try-finally, try-catch-finally

检查和未检查

检查和未检查语句用于指定当将结果存储在变量中、但该变量过小而不能容纳结果值时，是否允许数值运算导致溢出。有关更多信息，请参见检查和未检查。

fixed 语句

Fixed 语句禁止垃圾回收器重定位可移动的变量。有关更多信息，请参见 fixed。

lock 语句

lock 语句用于限制一次仅允许一个线程访问代码块。有关更多信息，请参见 lock。

标记语句

可以为语句指定一个标记，然后使用 goto 关键字跳转到该标记语句。（参见下一行中的示例。）

空语句

空语句只含一个分号。空语句不执行任何操作，可以在需要语句但不需要执行任何操作的地方使用。下面的示例演示空语句的两种用法：

C# 复制代码
void ProcessMessages()
{
    while (ProcessMessage())
        ; // Statement needed here.
}

void F()
{
    //...
    if (done) goto exit;
    //...
    exit:
    ; // Statement needed here.
}

嵌入语句

一些语句（例如 do、while、for 和 foreach）后面始终跟有一条嵌入语句。此嵌入语句可以是单个语句，也可以是语句块中括在括号 {} 内的多个语句。甚至可以在括号 {} 内包含单行嵌入语句，如下面的示例所示：

C# 复制代码
// Recommended style. Embedded statement in a block.
foreach (string s in System.IO.Directory.GetDirectories(
                        System.Environment.CurrentDirectory))
{
    System.Console.WriteLine(s);
}

// Not recommended.
foreach (string s in System.IO.Directory.GetDirectories(
                        System.Environment.CurrentDirectory))
    System.Console.WriteLine(s);

未括在括号 {} 内的嵌入语句不能作为声明语句或标记语句。下面的示例演示了这种情况：

C# 复制代码
if(b == true)
    //Error CS1023:
    int radius = 5;

将该嵌入语句放在语句块中以修复错误：

C# 复制代码
if(b == true)
{
    // OK:
    System.DateTime d = System.DateTime.Now;
    System.Console.WriteLine(d.ToLongDateString());
}

嵌套语句块

语句块可以嵌套，如以下代码所示：

C# 复制代码
foreach (string s in System.IO.Directory.GetDirectories(
    System.Environment.CurrentDirectory))
{
    if (s.StartsWith("CSharp"))
    {
        if (s.EndsWith("TempFolder"))
        {
            return s;
        }
    }

}
return "Not found.";

无法访问的语句

如果编译器认为在任何情况下控制流都无法到达特定语句，将生成警告 CS0162，如下面的示例所示：

C# 复制代码
// An over-simplified example of unreachable code.
const int val = 5;
if (val < 4)
{
    System.Console.WriteLine("I'll never write anything."); //CS0162
}

相关章节

  • 语句关键字（C# 参考）

  • 表达式（C# 编程指南）

  • 运算符（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.5 语句

  • 8 语句

请参见

概念

C# 编程指南

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增加了新的代码示例和表，并修订了文字。 | 信息补充。


表达式（C# 编程指南）

[image]

C# 编程指南
表达式（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

“表达式”是由一个或多个操作数以及零个或零个以上的运算符所组成的序列，可以通过计算得到一个值、对象、方法或命名空间等结果。表达式可以包含文本值、方法调用、运算符及其操作数，或简单名称。简单名称可以是变量、类型成员、方法参数、命名空间或类型的名称。

表达式可以使用运算符，而运算符又可以将其他表达式用作参数，或者使用方法调用，而方法调用的参数又可以是其他方法调用，因此表达式既可以非常简单，也可以非常复杂。下面是表达式的两个示例：

复制代码
((x < 10) && ( x > 5)) || ((x > 20) && (x < 25))
System.Convert.ToInt32("35")

表达式值

在大部分使用表达式的上下文中，例如在语句或方法参数中，表达式应计算为某个值。如果 x 和 y 是整数，表达式 x + y 将计算为一个数值。表达式 new MyClass() 计算为对 MyClass 对象的新实例的引用。表达式 myClass.ToString() 计算为一个字符串，因为字符串是该方法的返回类型。然而，虽然命名空间名称归类为表达式，但它不计算为值，因此永远不能作为任何表达式的最终结果。命名空间名称不能传递给方法参数，不能用在新表达式中，也不能赋值给变量。命名空间名称只能用作较大表达式的子表达式。同样如此的还有类型（与 System..::.Type 对象不同）、方法组名称（与特定方法不同）以及事件 add 和 remove 访问器。

每个值都有关联的类型。例如，如果 x 和 y 都是 int 类型的变量，则表达式 x + y 的值的类型也是 int。如果将该值赋给不同类型的变量，或者如果 x 和 y 是不同的类型，则应用类型转换规则。有关如何进行这种转换的更多信息，请参见强制转换和类型转换（C# 编程指南）。

溢出

如果值大于值类型的最大值，数值表达式可能导致溢出。有关更多信息，请参见Checked 和 Unchecked（C# 参考）和显式数值转换表（C# 参考）。

运算符的优先级和结合性

计算表达式的方式由结合性和运算符优先级控制。有关更多信息，请参见运算符（C# 编程指南）。

除赋值表达式和方法调用表达式之外，大部分表达式都必须嵌在语句中。有关更多信息，请参见语句（C# 编程指南）。

文本和简单名称

最简单的两种表达式类型是文本和简单名称。文本是没有名称的常数值。例如，在下面的代码示例中，5 和 "Hello World" 都是文本值：

C# 复制代码
// Expression statements.
int i = 5;
string s = "Hello World";

有关文本的更多信息，请参见 类型（C# 参考）。

在上面的示例中，i 和 s 都是用于标识局部变量的简单名称。在表达式中使用这些变量时，变量名称计算为当前在该变量的内存位置所存储的值。下面的示例演示了这种情况：

C# 复制代码
int num = 5;
System.Console.WriteLine(num); // Output: 5
num = 6;
System.Console.WriteLine(num); // Output: 6

调用表达式

在下面的代码示例中，对 DoWork 的调用是一个调用表达式。

复制代码
DoWork();

方法调用要求使用方法的名称（如前面的示例中那样作为名称，或作为其他表达式的结果），后跟括号和任意方法参数。有关更多信息，请参见方法（C# 编程指南）。委托调用使用委托的名称和括号内的方法参数。有关更多信息，请参见委托（C# 编程指南）。如果方法返回值，则方法调用和委托调用的计算结果为该方法的返回值。返回 void 的方法不能替代表达式中的值。

查询表达式

有关常规表达式的规则同样适用于查询表达式。有关更多信息，请参见 LINQ 查询表达式（C# 编程指南）。

Lambda 表达式

Lambda 表达式表示没有名称但可以具有输入参数和多个语句的“内联方法”。它们在 LINQ 中广泛用于向方法传递参数。Lambda 表达式被编译为委托或表达式目录树，具体取决于使用它们的上下文。有关更多信息，请参见 Lambda 表达式（C# 编程指南）。

表达式目录树

使用表达式目录树可以将表达式表示为数据结构。表达式目录树由 LINQ 提供程序广泛使用，用来将查询表达式转换为在其他某个上下文（如 SQL 数据库）中有意义的代码。有关更多信息，请参见表达式目录树。

备注

只要从表达式中识别到变量、对象属性或对象索引器访问，该项的值都会用作表达式的值。在 C# 中，只要表达式的最终计算结果是所需的类型，表达式就可以放置在任何需要值或对象的位置。

请参见

概念

C# 编程指南
LINQ 查询表达式（C# 编程指南）

参考

方法（C# 编程指南）
委托（C# 编程指南）
运算符（C# 编程指南）
类型（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增强了介绍部分的内容，并增加了“表达式值”、“溢出”和“运算符的优先级和结合性”几节。 | 信息补充。


运算符（C# 编程指南）

[image]

C# 编程指南
运算符（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

在 C# 中，“运算符”是一个术语或符号，它接受一个或多个表达式（即“操作数”）作为输入并返回值。接受一个操作数的运算符称为“一元”运算符，例如增量运算符 (++) 或 new。接受两个操作数的运算符称为“二元”运算符，例如算术运算符 +、-、*、/。条件运算符 ?: 接受三个操作数，是 C# 中唯一的三元运算符。

下面的 C# 语句包含一个一元运算符和一个操作数。增量运算符 ++ 修改操作数 y 的值：

C# 复制代码
y++;

下面的 C# 语句包含两个二元运算符，它们分别有两个操作数。赋值运算符 = 将一个整数 y 和一个表达式 2 + 3 作为操作数。表达式 2 + 3 本身包含加运算符，并使用整数值 2 和 3 作为操作数：

C# 复制代码
y = 2 + 3;

操作数可以是任何大小的有效表达式，可由任何数量的其他操作组成。在计算表达式时，编译器首先从左向右计算所有操作数。计算完所有操作数之后，按照称为“运算符优先级”的特定顺序计算运算符。下表根据运算符执行的操作类型将它们划分到不同的类别中。类别按优先级顺序列出。

主要运算符

表达式 | 说明
x.y | 成员访问
f(x) | 方法和委托调用
a[x] | 数组和索引器访问
x++ | 后递增
x-- | 后递减
new T(...) | 对象和委托创建
new T(...){...} | 具有初始值设定项的对象创建。请参见对象和集合初始值设定项（C# 编程指南）。
new {...} | 匿名对象初始值设定项。请参见匿名类型（C# 编程指南）。
new T[...] | 数组创建。请参见数组（C# 编程指南）。
typeof(T) | 获取 T 的 System.Type 对象
checked(x) | 在已检查的上下文中计算表达式
unchecked(x) | 在未检查的上下文中计算表达式
default (T) | 获取类型 T 的默认值
delegate {} | 匿名函数（匿名方法）

一元运算符

表达式 | 说明
+x | 标识
-x | 求反
!x | 逻辑求反
~x | 按位求反
++x | 前递增
--x | 前递减
(T)x | 将 x 显式转换为类型 T

乘法运算符

表达式 | 说明
* | 乘法
/ | 除法
% | 余数

加法运算符

表达式 | 说明
x + y | 相加、字符串串联、委托组合
x - y | 相减、委托移除

移位运算符

表达式 | 说明
x << y | 左移
x >> y | 右移

关系和类型运算符

表达式 | 说明
x < y | 小于
x > y | 大于
x <= y | 小于或等于
x >= y | 大于或等于
x is T | 如果 x 为 T，则返回 True；否则返回 False。
x as T | 返回类型为 T 的 x，如果 x 不是 T，则返回 null

相等运算符

表达式 | 说明
x == y | 等于
x != y | 不等于

赋值和匿名运算符

表达式 | 说明
= | 赋值
x op= y | 复合赋值，支持的运算符包括：+=、-=、*=、/=、%=、&=、|=、!=、<<=、>>=
(T x) => y | 匿名函数（lambda 表达式）

逻辑、条件和 null 运算符

类别 | 表达式 | 说明
逻辑“与” | x & y | 整型按位“与”，布尔型逻辑“与”
逻辑“异或” | x ^ y | 整型按位“异或”，布尔型逻辑“异或”
逻辑“或” | x | y | 整型按位“或”，布尔型逻辑“或”
条件“与” | x && y | 仅当 x 为 True 时计算 y
条件“或” | x || y | 仅当 x 为 False 时计算 y
Null 合并 | X ??y | 如果 x 为 Null 则计算结果为 y，否则计算结果为 x
条件 | x ?: y : z | 如果 x 为 True 则计算结果为 y，如果 x 为 False 则计算结果为 z

当表达式中出现两个具有相同优先级的运算符时，将根据结合性计算它们。左结合运算符按从左到右的顺序计算。例如，x * y / z 计算为 (x * y) / z。右结合运算符按从右到左的顺序计算。赋值运算符和三元运算符 (?:) 是右结合运算符。其他所有二元运算符都是左结合运算符。

可以将表达式用括号括起来强制在计算其他任何表达式之前计算该表达式。例如，2 + 3 * 2 正常情况下计算为 8。这是因为乘法运算符的优先级高于加法运算符。将该表达式写为 (2 + 3) * 2 的形式，结果将是 10，因为它指示 C# 编译器必须在计算乘法运算符 * 之前计算加法运算符 +。

对于自定义的类和结构，您可以更改运算符的行为。此过程称为“运算符重载”。有关更多信息，请参见可重载运算符（C# 编程指南）。

相关章节

有关更多信息，请参见运算符关键字（C# 参考）和 C# 运算符。

请参见

概念

C# 编程指南

参考

语句、表达式和运算符（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 删除了关于未定义输出的错误陈述。 | 信息补充。


匿名函数（C# 编程指南）

[image]

C# 编程指南
匿名函数（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

如其他地方所述，委托是一种包装方法调用的类型。就像类型一样，可以在方法之间传递委托实例，并且可以像方法一样调用委托实例。匿名函数是一个“内联”语句或表达式，可在需要委托类型的任何地方使用。可以使用匿名函数来初始化命名委托，或传递命名委托（而不是命名委托类型）作为方法参数。

共有两种匿名函数，以下主题中分别讨论了这些函数：

  • Lambda 表达式（C# 编程指南）.

  • 匿名方法（C# 编程指南）

    说明：

    Lambda 表达式可以绑定到表达式目录树，也可以绑定到委托。

C# 中委托的发展

在 C# 1.0 中，您通过使用在代码中其他位置定义的方法显式初始化委托来创建委托的实例。C# 2.0 引入了匿名方法的概念，作为一种编写可在委托调用中执行的未命名内联语句块的方式。C# 3.0 引入了 Lambda 表达式，这种表达式与匿名方法的概念类似，但更具表现力并且更简练。这两个功能统称为“匿名函数”。通常，针对 .NET Framework 版本 3.5 及更高版本的应用程序应使用 Lambda 表达式。

下面的示例演示了从 C# 1.0 到 C# 3.0 委托创建过程的发展：

C# 复制代码
class Test
{
    delegate void TestDelegate(string s);
    static void M(string s)
    {
        Console.WriteLine(s);
    }

    static void Main(string[] args)
    {
        // Original delegate syntax required
        // initialization with a named method.
        TestDelegate testdelA = new TestDelegate(M);

        // C# 2.0: A delegate can be initialized with
        // inline code, called an "anonymous method." This
        // method takes a string as an input parameter.
        TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };

        // C# 3.0. A delegate can be initialized with
        // a lambda expression. The lambda also takes a string
        // as an input parameter (x). The type of x is inferred by the compiler.
        TestDelegate testDelC = (x) => { Console.WriteLine(x); };

        // Invoke the delegates.
        testdelA("Hello. My name is M and I write lines.");
        testDelB("That's nothing. I'm anonymous and ");
        testDelC("I'm a famous author.");

        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Hello. My name is M and I write lines.
    That's nothing. I'm anonymous and
    I'm a famous author.
    Press any key to exit.
 */

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.29 匿名函数

请参见

概念

表达式目录树

参考

语句、表达式和运算符（C# 编程指南）
委托（C# 编程指南）


Lambda 表达式（C# 编程指南）

[image]

C# 编程指南
Lambda 表达式（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

“Lambda 表达式”是一个匿名函数，它可以包含表达式和语句，并且可用于创建委托或表达式目录树类型。

所有 Lambda 表达式都使用 Lambda 运算符 =>，该运算符读为“goes to”。该 Lambda 运算符的左边是输入参数（如果有），右边包含表达式或语句块。Lambda 表达式 x => x * x 读作“x goes to x times x”。可以将此表达式分配给委托类型，如下所示：

复制代码
delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

创建表达式目录树类型：

复制代码
using System.Linq.Expressions;
// ...
Expression<del> = x => x * x;

=> 运算符具有与赋值运算符 (=) 相同的优先级，并且是右结合运算符。

Lambda 用在基于方法的 LINQ 查询中，作为诸如 Where 和 Where 等标准查询运算符方法的参数。

使用基于方法的语法在 Enumerable 类中调用 Where 方法时（像在 LINQ to Objects 和 LINQ to XML 中那样），参数是委托类型 System..::.Func<(Of <(T, TResult>)>)。使用 Lambda 表达式创建委托最为方便。例如，当您在 System.Linq..::.Queryable 类中调用相同的方法时（像在 LINQ to SQL 中那样），则参数类型是 System.Linq.Expressions..::.Expression<Func>，其中 Func 是包含至多五个输入参数的任何 Func 委托。同样，Lambda 表达式只是一种用于构造表达式目录树的非常简练的方式。尽管事实上通过 Lambda 创建的对象的类型是不同的，但 Lambda 使得 Where 调用看起来类似。

在前面的示例中，请注意委托签名具有一个 int 类型的隐式类型输入参数，并返回 int。可以将 Lambda 表达式转换为该类型的委托，因为该表达式也具有一个输入参数 (x)，以及一个编译器可隐式转换为 int 类型的返回值。（以下几节中将对类型推理进行详细讨论。）使用输入参数 5 调用委托时，它将返回结果 25。

在 is 或 as 运算符的左侧不允许使用 Lambda。

适用于匿名方法的所有限制也适用于 Lambda 表达式。有关更多信息，请参见匿名方法（C# 编程指南）。

Lambda 表达式

表达式在右边的 Lambda 表达式称为“Lambda 表达式”。Lambda 表达式在构造表达式目录树时广泛使用。Lambda 表达式返回表达式的结果，并采用以下基本形式：

复制代码
(input parameters) => expression

只有在 Lambda 有一个输入参数时，括号才是可选的；否则括号是必需的。两个或更多输入参数由括在括号中的逗号分隔：

复制代码
(x, y) => x == y

有时，编译器难于或无法推断输入类型。如果出现这种情况，您可以按以下示例中所示方式显式指定类型：

复制代码
(int x, string s) => s.Length > x

使用空括号指定零个输入参数：

复制代码
() => SomeMethod()

在上一个示例中，请注意 Lambda 表达式的主体可以包含方法调用。但是，如果要创建将在另一个域（比如 SQL Server）中使用的表达式目录树，则不应在 Lambda 表达式中使用方法调用。方法在 .NET 公共语言运行时上下文的外部将没有意义。

Lambda 语句

Lambda 语句与 Lambda 表达式类似，只是语句括在大括号中：

复制代码
(input parameters) => {statement;}

Lambda 语句的主体可以包含任意数量的语句；但是，实际上通常不会多于两个或三个语句。

复制代码
delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

像匿名方法一样，Lambda 语句无法用于创建表达式目录树。

带有标准查询运算符的 Lambda

许多标准查询运算符都具有输入参数，其类型是泛型委托的 Func<(Of <(T, TResult>)>) 系列的其中之一。Func<(Of <(T, TResult>)>) 委托使用类型参数来定义输入参数的数量和类型，以及委托的返回类型。Func 委托对于封装应用于一组源数据中每个元素的用户定义表达式非常有用。例如，假设有以下委托类型：

复制代码
public delegate TResult Func<TArg0, TResult>(TArg0 arg0)

可以将委托实例化为 Func<int,bool> myFunc，其中 int 是输入参数，bool 是返回值。返回值始终在最后一个类型参数中指定。Func<int, string, bool> 定义包含两个输入参数（int 和 string）且返回类型为 bool 的委托。在调用下面的 Func 委托时，该委托将返回 true 或 false 以指示输入参数是否等于 5：

复制代码
    Func<int, bool> myFunc = x => x == 5;
    bool result = myFunc(4); // returns false of course

当参数类型为 Expression<Func> 时，您也可以提供 Lambda 表达式，例如在 System.Linq.Queryable 内定义的标准查询运算符中。如果指定 Expression<Func> 参数，Lambda 将编译为表达式目录树。

此处显示了一个标准查询运算符，Count 方法：

复制代码
    int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
    int oddNumbers = numbers.Count(n => n % 2 == 1);

编译器可以推断输入参数的类型，或者您也可以显式指定该类型。这个特别的 Lambda 表达式将计算整数 (n) 的数量，这些整数除以 2 时余数为 1。

以下方法将生成一个序列，其中包含数字数组中出现在“9”之前的所有元素，因为“9”是序列中不满足条件的第一个数字：

复制代码
    var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);

此示例演示如何通过将输入参数括在括号中来指定多个输入参数。该方法将返回数字数组中的所有元素，直至遇到一个值小于其位置的数字为止。不要将 Lambda 运算符 (=>) 与大于等于运算符 (=>) 混淆。

复制代码
    var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

Lambda 中的类型推理

在编写 Lambda 时，通常不必为输入参数指定类型，因为编译器可以基于 Lambda 主体、基础委托类型以及 C# 3.0 语言规范中描述的其他因素推断类型。对于大多数标准查询运算符，第一个输入是源序列中的元素的类型。因此，如果要查询 IEnumerable<Customer>，则输入变量将被推断为 Customer 对象，这意味着您可以访问其方法和属性：

复制代码
customers.Where(c => c.City == "London");

Lambda 的一般规则如下：

  • Lambda 包含的参数数量必须与委托类型包含的参数数量相同。

  • Lambda 中的每个输入参数必须都能够隐式转换为其对应的委托参数。

  • Lambda 的返回值（如果有）必须能够隐式转换为委托的返回类型。

请注意，Lambda 表达式本身没有类型，因为通用类型系统没有“Lambda 表达式”这一内部概念。但是，有时会不正式地论及 Lambda 表达式的“类型”。在这些情况下，类型是指委托类型或 Lambda 表达式所转换为的 Expression 类型。

Lambda 表达式中的变量范围

Lambda 可以引用“外部变量”，这些变量位于在其中定义 Lambda 的封闭方法或类型的范围内。将会存储通过这种方法捕获的变量以供在 Lambda 表达式中使用，即使变量将以其他方式超出范围或被作为垃圾回收。必须明确地分配外部变量，然后才能在 Lambda 表达式中使用该变量。下面的示例演示这些规则：

复制代码
delegate bool D();
    delegate bool D2(int i);

    class Test
    {
        D del;
        D2 del2;
        public void TestMethod(int input)
        {
            int j = 0;
            // Initialize the delegates with lambda expressions.
            // Note access to 2 outer variables.
            // del will be invoked within this method.
            del = () => { j = 10;  return j > input; };

            // del2 will be invoked after TestMethod goes out of scope.
            del2 = (x) => {return x == j; };

            // Demonstrate value of j:
            // Output: j = 0
            // The delegate has not been invoked yet.
            Console.WriteLine("j = {0}", j);

            // Invoke the delegate.
            bool boolResult = del();

            // Output: j = 10 b = True
            Console.WriteLine("j = {0}. b = {1}", j, boolResult);
        }

        static void Main()
        {
            Test test = new Test();
            test.TestMethod(5);

            // Prove that del2 still has a copy of
            // local variable j from TestMethod.
            bool result = test.del2(10);

            // Output: True
            Console.WriteLine(result);

            Console.ReadKey();
        }
    }

下列规则适用于 Lambda 表达式中的变量范围：

  • 捕获的变量将不会被作为垃圾回收，直至引用变量的委托超出范围为止。

  • 在外部方法中看不到 Lambda 表达式内引入的变量。

  • Lambda 表达式无法从封闭方法中直接捕获 ref 或 out 参数。

  • Lambda 表达式中的返回语句不会导致封闭方法返回。

  • Lambda 表达式不能包含其目标位于所包含匿名函数主体外部或内部的 goto 语句、break 语句或 continue 语句。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.29 匿名函数

请参见

概念

C# 编程指南
表达式目录树

参考

匿名方法（C# 编程指南）
is（C# 参考）

其他资源

语言集成查询 (LINQ)
Recursive lambda expressions（递归 lambda 表达式）


如何：在查询中使用 Lambda 表达式（C# 编程指南）

[image]

C# 编程指南
如何：在查询中使用 Lambda 表达式（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

您不会在查询语法中直接用到 Lambda 表达式，但会在方法调用中用到这些表达式，并且查询表达式可以包含方法调用。事实上，某些查询操作只能用方法语法表示。有关查询语法和方法语法之间的区别的更多信息，请参见查询语法与方法语法 (LINQ)。

示例

下面的示例演示如何通过使用 Enumerable..::.Where 标准查询运算符在基于方法的查询中使用 Lambda 表达式。请注意，此示例中的 Where 方法具有一个委托类型为 Func<(Of <(TResult>)>) 的输入参数，并且委托采用整数作为输入并返回布尔值。可以将 Lambda 表达式转换为该委托。假若这是使用 Queryable..::.Where 方法的 LINQ to SQL 查询，则参数类型将为 Expression<Func<int,bool>>，但 Lambda 表达式看起来将完全相同。有关表达式类型的更多信息，请参见 System.Linq.Expressions..::.Expression。

C# 复制代码
class SimpleLambda
{
    static void Main()
    {

        // Data source.
        int[] scores = { 90, 71, 82, 93, 75, 82 };

        // The call to Count forces iteration of the source
        int highScoreCount = scores.Where(n => n > 80).Count();

        Console.WriteLine("{0} scores are greater than 80", highScoreCount);

        // Outputs: 4 scores are greater than 80
    }
}

下面的示例演示如何在查询表达式的方法调用中使用 Lambda 表达式。Lambda 是必需的，因为无法使用查询语法来调用 Sum()()() 标准查询运算符。

查询首先按 GradeLevel 枚举中定义的方式，依据学生的成绩等级对学生进行分组。然后，对于每个组，查询将添加每名学生的总分。这需要两个 Sum 运算。内部的 Sum 计算每名学生的总分，外部的 Sum 保留该组中所有学生的运行合并总计。

C# 复制代码
private static void TotalsByGradeLevel()
{
    // This query retrieves the total scores for First Year students, Second Years, and so on.
    // The outer Sum method uses a lambda in order to specify which numbers to add together.
    var categories =
    from student in students
    group student by student.Year into studentGroup
    select new { GradeLevel = studentGroup.Key, TotalScore = studentGroup.Sum(s => s.ExamScores.Sum()) };

    // Execute the query.
    foreach (var cat in categories)
    {
        Console.WriteLine("Key = {0} Sum = {1}", cat.GradeLevel, cat.TotalScore);
    }
}
/*
     Outputs:
     Key = SecondYear Sum = 1014
     Key = ThirdYear Sum = 964
     Key = FirstYear Sum = 1058
     Key = FourthYear Sum = 974
*/

编译代码

若要运行此代码，请将方法复制并粘贴到如何：查询对象集合（C# 编程指南）中提供的 StudentClass 中，并从 Main 方法中调用它。

请参见

概念

表达式目录树

参考

Lambda 表达式（C# 编程指南）


如何：在 LINQ 外部使用 Lambda 表达式（C# 编程指南）

[image]

C# 编程指南
如何：在 LINQ 外部使用 Lambda 表达式（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

Lambda 表达式并不只限于在 LINQ 查询中使用。可以在需要委托值的任何地方（也就是在可以使用匿名方法的任何地方）使用这些表达式。下面的示例演示如何在 Windows 窗体事件处理程序中使用 Lambda 表达式。请注意，输入的类型（Object 和 MouseEventArgs）由编译器推理，因此不必在 Lambda 输入参数中显式给定。

示例

复制代码
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        // Use a lambda expression to define an event handler.
       this.Click += (s, e) => { MessageBox.Show(((MouseEventArgs)e).Location.ToString());};
    }
}

请参见

参考

Lambda 表达式（C# 编程指南）
匿名方法（C# 编程指南）

其他资源

语言集成查询 (LINQ)


匿名方法（C# 编程指南）

[image]

C# 编程指南
匿名方法（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在 2.0 之前的 C# 版本中，声明委托的唯一方法是使用命名方法。C# 2.0 引入了匿名方法，而在 C# 3.0 及更高版本中，Lambda 表达式取代了匿名方法，作为编写内联代码的首选方式。不过，本主题中有关匿名方法的信息同样也适用于 Lambda 表达式。有一种情况下，匿名方法提供了 Lambda 表达式中所没有的功能。匿名方法使您能够省略参数列表，这意味着可以将匿名方法转换为带有各种签名的委托。这对于 Lambda 表达式来说是不可能的。有关 lambda 表达式的更多特定信息，请参见 Lambda 表达式（C# 编程指南）。

要将代码块传递为委托参数，创建匿名方法则是唯一的方法。这里是两个示例：

C# 复制代码
// Create a handler for a click event
button1.Click += delegate(System.Object o, System.EventArgs e)
                   { System.Windows.Forms.MessageBox.Show("Click!"); };
C# 复制代码
// Create a delegate instance
delegate void Del(int x);

// Instantiate the delegate using an anonymous method
Del d = delegate(int k) { /* ... */ };

通过使用匿名方法，由于您不必创建单独的方法，因此减少了实例化委托所需的编码系统开销。

例如，如果创建方法所需的系统开销是不必要的，则指定代码块（而不是委托）可能非常有用。启动新线程即是一个很好的示例。无需为委托创建更多方法，线程类即可创建一个线程并且包含该线程执行的代码。

C# 复制代码
void StartThread()
{
    System.Threading.Thread t1 = new System.Threading.Thread
      (delegate()
            {
                System.Console.Write("Hello, ");
                System.Console.WriteLine("World!");
            });
    t1.Start();
}

备注

匿名方法的参数的范围是“匿名方法块”。

如果目标在块外部，那么，在匿名方法块内使用跳转语句（如 goto、break 或 continue）是错误的。如果目标在块内部，在匿名方法块外部使用跳转语句（如 goto、break 或 continue）也是错误的。

如果局部变量和参数的范围包含匿名方法声明，则该局部变量和参数称为该匿名方法的“外部”变量。例如，下面代码段中的 n 即是一个外部变量：

C# 复制代码
int n = 0;
Del d = delegate() { System.Console.WriteLine("Copy #:{0}", ++n); };

与局部变量不同，捕获变量的生命周期一直持续到引用该匿名方法的委托符合垃圾回收的条件为止。对 n 的引用是在创建该委托时捕获的。

匿名方法不能访问外部范围的 ref 或 out 参数。

在“匿名方法块”中不能访问任何不安全代码。

在 is 运算符的左侧不允许使用匿名方法。

示例

下面的示例演示实例化委托的两种方法：

  • 使委托与匿名方法关联。

  • 使委托与命名方法 (DoWork) 关联。

两种方法都会在调用委托时显示一条消息。

C# 复制代码
// Declare a delegate
delegate void Printer(string s);

class TestClass
{
    static void Main()
    {
        // Instatiate the delegate type using an anonymous method:
        Printer p = delegate(string j)
        {
            System.Console.WriteLine(j);
        };

        // Results from the anonymous delegate call:
        p("The delegate using the anonymous method is called.");

        // The delegate instantiation using a named method "DoWork":
        p = new Printer(TestClass.DoWork);

        // Results from the old style delegate call:
        p("The delegate using the named method is called.");
    }

    // The method associated with the named delegate:
    static void DoWork(string k)
    {
        System.Console.WriteLine(k);
    }
}
/* Output:
    The delegate using the anonymous method is called.
    The delegate using the named method is called.
*/

请参见

概念

C# 编程指南

参考

委托（C# 编程指南）
Lambda 表达式（C# 编程指南）
不安全代码和指针（C# 编程指南）
方法（C# 编程指南）
带有命名方法的委托与带有匿名方法的委托（C# 编程指南）

其他资源

C# 参考


可重载运算符（C# 编程指南）

[image]

C# 编程指南
可重载运算符（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 允许用户定义的类型通过使用 operator 关键字定义静态成员函数来重载运算符。但不是所有的运算符都可被重载，下表列出了不能被重载的运算符：

运算符 | 可重载性
+、-、!、~、++、--、true 和 false | 可以重载这些一元运算符。
+、-、*、/、%、&、|、^、<<、>> | 可以重载这些二进制运算符。
==、!=、<、>、<=、>= | 比较运算符可以重载（但请参见本表后面的说明）。
&&、|| | 条件逻辑运算符不能重载，但可使用能够重载的 & 和 | 进行计算。
[] | 不能重载数组索引运算符，但可定义索引器。
() | 不能重载转换运算符，但可定义新的转换运算符（请参见 explicit 和 implicit）。
+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>= | 赋值运算符不能重载，但 += 可使用 + 计算，等等。
=、.、?:、->、new、is、sizeof、typeof | 不能重载这些运算符。
说明：

比较运算符（如果重载）必须成对重载；也就是说，如果重载 ==，也必须重载 !=。反之亦然，< 和 > 以及 <= 和 >= 同样如此。

若要在自定义类中重载运算符，您需要在该类中创建具有正确签名的方法。该方法必须命名为“operator X”，其中 X 是正在重载的运算符的名称或符号。一元运算符具有一个参数，二元运算符具有两个参数。在每种情况下，参数的类型必须与声明该运算符的类或结构的类型相同，如下面的示例所示：

C# 复制代码
public static Complex operator +(Complex c1, Complex c2)

有关更多信息，请参见如何：使用运算符重载创建复数类（C# 编程指南）。

请参见

概念

C# 编程指南

参考

语句、表达式和运算符（C# 编程指南）
运算符（C# 编程指南）
C# 运算符

其他资源

Why are overloaded operators always static in C#?（为什么在 C# 中重载的运算符始终是静态的？）


转换运算符（C# 编程指南）

[image]

C# 编程指南
转换运算符（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 允许程序员在类或结构上声明转换，以便类或结构与其他类或结构或者基本类型进行相互转换。转换的定义方法类似于运算符，并根据它们所转换到的类型命名。要转换的类型参数或转换结果的类型必须是（不能两者同时都是）包含类型。

C# 复制代码
class SampleClass
{
    public static explicit operator SampleClass(int i)
    {
        SampleClass temp = new SampleClass();
        // code to convert from int to SampleClass...

        return temp;
    }
}

转换运算符概述

转换运算符具有以下特点：

  • 声明为 implicit 的转换在需要时自动进行。

  • 声明为 explicit 的转换需要调用强制转换。

  • 所有转换都必须声明为 static。

相关章节

更多信息：

  • 使用转换运算符（C# 编程指南）

  • 转换运算符设计指南

  • 如何：在结构间实现用户定义的转换（C# 编程指南）

  • explicit（C# 参考）

  • implicit（C# 参考）

  • static（C# 参考）

请参见

概念

C# 编程指南

其他资源

Chained user-defined explicit conversions in C#（C# 中关联的用户定义的显式转换）


使用转换运算符（C# 编程指南）

[image]

C# 编程指南
使用转换运算符（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

转换运算符可以是 explicit，也可以是 implicit。隐式转换运算符更容易使用，但是如果您希望运算符的用户能够意识到正在进行转换，则显式运算符很有用。此主题演示了这两种类型。

示例

这是显式转换运算符的一个示例。此运算符将类型 Byte 转换为称为 Digit 的值类型。由于不是所有字节都可以转换为数字，因此转换是显式的，这意味着必须使用强制转换，如 Main 方法所示。

C# 复制代码
struct Digit
{
    byte value;

    public Digit(byte value)  //constructor
    {
        if (value > 9)
        {
            throw new System.ArgumentException();
        }
        this.value = value;
    }

    public static explicit operator Digit(byte b)  // explicit byte to digit conversion operator
    {
        Digit d = new Digit(b);  // explicit conversion

        System.Console.WriteLine("Conversion occurred.");
        return d;
    }
}

class TestExplicitConversion
{
    static void Main()
    {
        try
        {
            byte b = 3;
            Digit d = (Digit)b;  // explicit conversion
        }
        catch (System.Exception e)
        {
            System.Console.WriteLine("{0} Exception caught.", e);
        }
    }
}
// Output: Conversion occurred.

此示例通过定义用来撤消前一个示例所执行的操作的转换运算符，来演示隐式转换运算符：它将名为 Digit 的值类转换为整数 Byte 类型。由于任何数字都可以转换为 Byte，因此没有必要一定让用户知道进行的转换。

C# 复制代码
struct Digit
{
    byte value;

    public Digit(byte value)  //constructor
    {
        if (value > 9)
        {
            throw new System.ArgumentException();
        }
        this.value = value;
    }

    public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
    {
        System.Console.WriteLine("conversion occurred");
        return d.value;  // implicit conversion
    }
}

class TestImplicitConversion
{
    static void Main()
    {
        Digit d = new Digit(3);
        byte b = d;  // implicit conversion -- no cast needed
    }
}
// Output: Conversion occurred.

请参见

概念

C# 编程指南

参考

转换运算符（C# 编程指南）

其他资源

C# 参考


如何：在结构间实现用户定义的转换（C# 编程指南）

[image]

C# 编程指南
如何：在结构间实现用户定义的转换（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例定义 RomanNumeral 和 BinaryNumeral 两个结构，并演示二者之间的转换。

示例

C# 复制代码
struct RomanNumeral
{
    private int value;

    public RomanNumeral(int value)  //constructor
    {
        this.value = value;
    }

    static public implicit operator RomanNumeral(int value)
    {
        return new RomanNumeral(value);
    }

    static public implicit operator RomanNumeral(BinaryNumeral binary)
    {
        return new RomanNumeral((int)binary);
    }

    static public explicit operator int(RomanNumeral roman)
    {
        return roman.value;
    }

    static public implicit operator string(RomanNumeral roman)
    {
        return ("Conversion not yet implemented");
    }
}

struct BinaryNumeral
{
    private int value;

    public BinaryNumeral(int value)  //constructor
    {
        this.value = value;
    }

    static public implicit operator BinaryNumeral(int value)
    {
        return new BinaryNumeral(value);
    }

    static public explicit operator int(BinaryNumeral binary)
    {
        return (binary.value);
    }

    static public implicit operator string(BinaryNumeral binary)
    {
        return ("Conversion not yet implemented");
    }
}

class TestConversions
{
    static void Main()
    {
        RomanNumeral roman;
        BinaryNumeral binary;

        roman = 10;

        // Perform a conversion from a RomanNumeral to a BinaryNumeral:
        binary = (BinaryNumeral)(int)roman;

        // Perform a conversion from a BinaryNumeral to a RomanNumeral:
        // No cast is required:
        roman = binary;

        System.Console.WriteLine((int)binary);
        System.Console.WriteLine(binary);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    10
    Conversion not yet implemented
*/

可靠编程

  • 在上面的示例中，语句：

    C# 复制代码
    binary = (BinaryNumeral)(int)roman;

    执行从 RomanNumeral 到 BinaryNumeral 的转换。由于没有从 RomanNumeral 到 BinaryNumeral 的直接转换，所以使用一个转换将 RomanNumeral 转换为 int，并使用另一个转换将 int 转换为 BinaryNumeral。

  • 另外，语句

    C# 复制代码
    roman = binary;

    执行从 BinaryNumeral 到 RomanNumeral 的转换。由于 RomanNumeral 定义了从 BinaryNumeral 的隐式转换，所以不需要转换。

请参见

概念

C# 编程指南

参考

转换运算符（C# 编程指南）

其他资源

C# 参考


如何：使用运算符重载创建复数类（C# 编程指南）

[image]

C# 编程指南
如何：使用运算符重载创建复数类（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例展示如何使用运算符重载创建定义复数加法的复数类 Complex。本程序使用 ToString 方法的重载显示数字的虚部和实部以及加法结果。

示例

C# 复制代码
public struct Complex
{
    public int real;
    public int imaginary;

    public Complex(int real, int imaginary)  //constructor
    {
        this.real = real;
        this.imaginary = imaginary;
    }

    // Declare which operator to overload (+),
    // the types that can be added (two Complex objects),
    // and the return type (Complex):
    public static Complex operator +(Complex c1, Complex c2)
    {
        return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
    }

    // Override the ToString() method to display a complex number in the traditional format:
    public override string ToString()
    {
        return (System.String.Format("{0} + {1}radius", real, imaginary));
    }
}

class TestComplex
{
    static void Main()
    {
        Complex num1 = new Complex(2, 3);
        Complex num2 = new Complex(3, 4);

        // Add two Complex objects through the overloaded plus operator:
        Complex sum = num1 + num2;

        // Print the numbers and the sum using the overriden ToString method:
        System.Console.WriteLine("First complex number:  {0}", num1);
        System.Console.WriteLine("Second complex number: {0}", num2);
        System.Console.WriteLine("The sum of the two numbers: {0}", sum);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    First complex number:  2 + 3i
    Second complex number: 3 + 4i
    The sum of the two numbers: 5 + 7i
*/

请参见

概念

C# 编程指南

参考

C# 运算符
operator（C# 参考）

其他资源

Why are overloaded operators always static in C#?（为什么在 C# 中重载的运算符始终是静态的？）


Equals() 和运算符 == 的重写准则（C# 编程指南）

[image]

C# 编程指南
Equals() 和运算符 == 的重写准则（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 中有两种不同的相等：引用相等和值相等。值相等是大家普遍理解的意义上的相等：它意味着两个对象包含相同的值。例如，两个值为 2 的整数具有值相等性。引用相等意味着要比较的不是两个对象，而是两个对象引用，且两者引用的是同一个对象。这可以通过简单的赋值来实现，如下面的示例所示：

C# 复制代码
System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

在上面的代码中，只存在一个对象，但存在对该对象的多个引用：a 和 b。由于它们引用的是同一个对象，因此具有引用相等性。如果两个对象具有引用相等性，则它们也具有值相等性，但是值相等性不能保证引用相等性。

若要检查引用相等性，应使用 ReferenceEquals。若要检查值相等性，请使用 Equals。

重写 Equals

由于 Equals 是一个虚方法，因此任何类都可以重写其实现。表示某个值（本质上可以是任何值类型）或一组值（如复数类）的任何类都应该重写 Equals。如果类型要实现 IComparable，则它应该重写 Equals。

Equals 的新实现应该遵循 Equals 的所有保证：

  • x.Equals(x) 返回 true。

  • x.Equals(y) 与 y.Equals(x) 返回相同的值。

  • 如果 (x.Equals(y) && y.Equals(z)) 返回 true，则 x.Equals(z) 返回 true。

  • 只要不修改 x 和 y 所引用的对象，x.Equals(y) 的后续调用就返回相同的值。

  • x.Equals (null) 返回 false（仅非空值类型。有关更多信息，请参见可空类型（C# 编程指南）。）

Equals 的新实现不应该引发异常。建议重写 Equals 的任何类同时也重写 Object..::.GetHashCode。除了实现 Equals（对象）外，还建议所有的类为自己的类型实现 Equals（类型）以增强性能。例如：

C# 复制代码
class TwoDPoint : System.Object
{
    public readonly int x, y;

    public TwoDPoint(int x, int y)  //constructor
    {
        this.x = x;
        this.y = y;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to Point return false.
        TwoDPoint p = obj as TwoDPoint;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public bool Equals(TwoDPoint p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (x == p.x) && (y == p.y);
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

可调用基类的 Equals 的任何派生类在完成其比较之前都应该这样做。在下面的示例中，Equals 调用基类 Equals，后者将检查空参数并将参数的类型与派生类的类型做比较。这样就把检查派生类中声明的新数据字段的任务留给了派生类中的 Equals 实现：

C# 复制代码
class ThreeDPoint : TwoDPoint
{
    public readonly int z;

    public ThreeDPoint(int x, int y, int z)
        : base(x, y)
    {
        this.z = z;
    }

    public override bool Equals(System.Object obj)
    {
        // If parameter cannot be cast to ThreeDPoint return false:
        ThreeDPoint p = obj as ThreeDPoint;
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return base.Equals(obj) && z == p.z;
    }

    public bool Equals(ThreeDPoint p)
    {
        // Return true if the fields match:
        return base.Equals((TwoDPoint)p) && z == p.z;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode() ^ z;
    }
}

重写运算符 ==

默认情况下，运算符 == 通过判断两个引用是否指示同一对象来测试引用是否相等。因此引用类型不需要实现运算符 == 就能获得此功能。当类型不可变（即实例中包含的数据不可更改）时，通过重载运算符 == 来比较值是否相等而不是比较引用是否相等可能会很有用，因为作为不可变的对象，只要其值相同，就可以将其视为相同。建议不要在非不可变类型中重写运算符 ==。

重载的运算符 == 实现不应引发异常。重载运算符 == 的任何类型还应重载运算符 !=。例如：

C# 复制代码
//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
    // If both are null, or both are same instance, return true.
    if (System.Object.ReferenceEquals(a, b))
    {
        return true;
    }

    // If one is null, but not both, return false.
    if (((object)a == null) || ((object)b == null))
    {
        return false;
    }

    // Return true if the fields match:
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
    return !(a == b);
}
说明：

运算符 == 的重载中的常见错误是使用 (a == b)、(a == null) 或 (b == null) 来检查引用相等性。这会调用重载的运算符 ==，从而导致无限循环。应使用 ReferenceEquals 或将类型强制转换为 Object 来避免无限循环。

请参见

概念

C# 编程指南

参考

语句、表达式和运算符（C# 编程指南）
运算符（C# 编程指南）
可重载运算符（C# 编程指南）
== 运算符（C# 参考）
() 运算符（C# 参考）


类和结构（C# 编程指南）

[image]

C# 编程指南
类和结构（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

类和结构是 .NET Framework 中的通用类型系统的两种基本构造。两者在本质上都属于数据结构，封装着一组整体作为一个逻辑单位的数据和行为。数据和行为是该类或结构的“成员”，它们包含各自的方法、属性和事件等（本主题后面列出了这些内容）。

类或结构的声明类似于蓝图，用于在运行时创建实例或对象。如果定义一个名为 Person 的类或结构，则 Person 为类型名称。如果声明并初始化 Person 类型的变量 p，则 p 称为 Person 的对象或实例。可以创建同一 Person 类型的多个实例，每个实例在其属性和字段中具有不同的值。

类是一种引用类型。创建类的对象时，对象赋值到的变量只保存对该内存的引用。将对象引用赋给新变量时，新变量引用的是原始对象。通过一个变量做出的更改将反映在另一个变量中，因为两者引用同一数据。

结构是一种值类型。创建结构时，结构赋值到的变量保存该结构的实际数据。将结构赋给新变量时，将复制该结构。因此，新变量和原始变量包含同一数据的两个不同的副本。对一个副本的更改不影响另一个副本。

类通常用于对较为复杂的行为建模，或对要在创建类对象后进行修改的数据建模。结构最适合一些小型数据结构，这些数据结构包含的数据以创建结构后不修改的数据为主。

有关更多信息，请参见类（C# 编程指南）、对象（C# 编程指南）和结构（C# 编程指南）。

示例

下面的示例在 ProgrammingGuide 命名空间的顶级使用三个成员定义了 MyCustomClass。在 Program 类的 Main 方法中创建了 MyCustomClass 的一个实例（对象），并使用点表示法访问该对象的方法和属性。

C# 复制代码
namespace ProgrammingGuide
{
    // Class definition.
    public class MyCustomClass
    {
        // Class members:
        // Property.
        public int Number { get; set; }

        // Method.
        public int Multiply(int num)
        {
            return num * Number;
        }

        // Instance Constructor.
        public MyCustomClass()
        {
            Number = 0;
        }
    }
    // Another class definition. This one contains
    // the Main method, the entry point for the program.
    class Program
    {
        static void Main(string[] args)
        {
            // Create an object of type MyCustomClass.
            MyCustomClass myClass = new MyCustomClass();

            // Set the value of a public property.
            myClass.Number = 27;

            // Call a public method.
            int result = myClass.Multiply(4);
        }
    }
}

封装

“封装”有时被称为面向对象的编程的第一个支柱或原则。根据封装的原则，类或结构可以指定其每个成员对于该类或结构外部的代码的可访问性。可将无意在类或程序集外部使用的方法和变量隐藏起来，以减小编码错误或遭恶意利用的可能性。

有关类的更多信息，请参见类（C# 编程指南）和对象（C# 编程指南）。有关如何设计类的更多信息，请参见类型设计准则。

成员

所有方法、字段、常量、属性和事件都必须在类型内部进行声明；这些称为类或结构的“成员”。与其他一些语言不同的是，C# 中没有全局变量或方法。即使是作为程序入口点的 Main 方法也必须在类或结构内部进行声明。下表列出了可在类或结构中声明的所有不同种类的成员。

  • 字段

  • 常量

  • 属性

  • 方法

  • 构造函数

  • 析构函数

  • 事件

  • 索引器

  • 运算符

  • 嵌套类型

可访问性

有些方法和属性要供类或结构外部的代码（称为“客户端代码”）调用或访问。另有一些方法和属性可能仅供类或结构在自身内部使用。应限制您的代码的可访问性，只允许应当访问它们的客户端代码进行访问，这一点十分重要。使用访问修饰符 public、protected、internal、protected internal 和 private 可以指定类型及其成员对于客户端代码的可访问性。默认可访问性为 private。有关更多信息，请参见访问修饰符（C# 编程指南）。

继承

类（而非结构）支持继承的概念。派生自另一个类（“基类”）的类将自动包含基类除构造函数和析构函数之外的所有公共、受保护和内部成员。有关更多信息，请参见继承（C# 编程指南）和多态性（C# 编程指南）。

可以将类声明为抽象类，表示该类的一个或多个方法不具有实现。抽象类虽然无法直接实例化，但可以用作其他类的基类，由其他类提供缺少的实现。还可以将类声明为密封类，以禁止其他类从该类继承。有关更多信息，请参见抽象类、密封类及类成员（C# 编程指南）。

接口

类和结构可以继承多个接口。从接口继承意味着该类型要实现该接口中定义的所有方法。有关更多信息，请参见接口（C# 编程指南）。

泛型类型

可以使用一个或多个类型参数来定义类和结构。客户端代码在创建类型的实例时提供类型。例如，System.Collections.Generic 命名空间中的 List<(Of <(T>)>) 类使用一个类型参数进行定义。客户端代码创建 List<string> 或 List<int> 的实例来指定列表中将包含的类型。有关更多信息，请参见泛型（C# 编程指南）。

静态类型

可以将类（不是结构）声明为静态。静态类只能包含静态成员，不能使用 new 关键字进行实例化。在程序加载时，静态类的一个副本将加载到内存中，可通过类名称访问该类的成员。类和结构都可以包含静态成员。有关更多信息，请参见静态类和静态类成员（C# 编程指南）。

嵌套类型

类或结构可以嵌套在其他类或结构中。有关更多信息，请参见嵌套类型。

分部类型

可以在一个代码文件中定义类、结构或方法的一部分，而在另一个代码文件中定义另一部分。有关更多信息，请参见分部类和方法。

对象初始值设定项

可以实例化和初始化类或结构对象以及对象的集合，无需显式调用其构造函数。有关更多信息，请参见对象和集合初始值设定项（C# 编程指南）。

匿名类型

在不方便或没必要创建命名类的情况下，例如当使用无需保留或传递给其他方法的数据结构填充列表时，可以使用匿名类型。有关更多信息，请参见匿名类型（C# 编程指南）。

扩展方法

通过创建一个单独的类型，然后将该类型的方法当作原始类型的方法来调用，可以在不必创建派生类的情况下对类进行“扩展”。有关更多信息，请参见扩展方法（C# 编程指南）。

隐式类型的局部变量

在类或结构方法中，可以使用隐式类型来指示编译器在编译时确定正确的类型。有关更多信息，请参见隐式类型的局部变量（C# 编程指南）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6 类和对象

  • 1.7 结构

  • 3.4.4 类成员

  • 4.2.1 类类型

  • 10 类

  • 11 结构

请参见

概念

C# 编程指南

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 在介绍文字中增加了一些信息和定义。 | 信息补充。
2008 年 7 月 | 在关于可访问性的段落中增加了一些信息。 | 信息补充。


类（C# 编程指南）

[image]

C# 编程指南
类（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

“类”是一种构造，通过使用该构造，您可以将其他类型的变量、方法和事件组合在一起，从而创建自己的自定义类型。类就像一个蓝图，它定义类型的数据和行为。如果类没有声明为静态类，客户端代码就可以创建赋给变量的“对象”或“实例”，从而使用该类。在对变量的所有引用都超出范围之前，该变量始终保持在内存中。所有引用都超出范围时，CLR 将标记该变量以供垃圾回收。如果类声明为静态类，则内存中只存在一个副本，并且客户端代码只能通过该类自身而不是“实例变量”访问该类。有关更多信息，请参见静态类和静态类成员（C# 编程指南）。

与结构不同，类支持“继承”，而继承是面向对象编程的基础特性。有关更多信息，请参见继承（C# 编程指南）。

声明类

类使用 class 关键字进行声明，如下面的示例所示：

C# 复制代码
public class Customer
{
    //Fields, properties, methods and events go here...
}

class 关键字前面是访问级别。由于在该例中使用 public，因此任何人都可以基于该类创建对象。类的名称位于 class 关键字的后面。定义的其余部分是类的主体，用于定义行为和数据。类的字段、属性、方法和事件统称为“类成员”。

创建对象

尽管有时类和对象可互换，但它们是不同的概念。类定义对象的类型，但它不是对象本身。对象是基于类的具体实体，有时称为类的实例。

通过使用 new 关键字（后跟对象将基于的类的名称）可以创建对象，如下所示：

C# 复制代码
Customer object1 = new Customer();

创建类的实例后，将向程序员传递回对该对象的引用。在前面的示例中，object1 是对基于 Customer 的对象的引用。此引用引用新对象，但不包含对象数据本身。实际上，可以在根本不创建对象的情况下创建对象引用：

C# 复制代码
Customer object2;

建议不要创建像这样的不引用对象的对象引用，因为在运行时通过这样的引用来访问对象的尝试将会失败。但是，可以创建这样的引用来引用对象，方法是创建新对象，或者将它分配给现有的对象，如下所示：

C# 复制代码
Customer object3 = new Customer();
Customer object4 = object3;

此代码创建了两个对象引用，它们引用同一个对象。因此，通过 object3 对对象所做的任何更改都将反映在随后使用的 object4 中。由于基于类的对象是按引用来引用的，因此类称为引用类型。

类继承

继承是通过使用“派生”来实现的，而派生意味着类是使用“基类”声明的，它的数据和行为从基类继承。通过在派生的类名后面追加冒号和基类名称，可以指定基类，如下所示：

C# 复制代码
public class Manager : Employee
{
    // Employee fields, properties, methods and events are inherited
    // New Manager fields, properties, methods and events go here...
}

当类声明基类时，它继承基类除构造函数以外的所有成员。

与 C++ 不同，C# 中的类只能直接从一个基类继承。但是，因为基类自身也可能继承自另一个类，所以类可以间接继承多个基类。而且，一个类可以直接实现一个以上的接口。有关更多信息，请参见接口（C# 编程指南）。

类可以声明为抽象类。抽象类包含具有签名定义但没有实现的抽象方法。抽象类不能进行实例化。只能通过实现抽象方法的派生类使用抽象类。相比之下，密封类不允许其他类从其派生。有关更多信息，请参见抽象类、密封类及类成员（C# 编程指南）。

类定义可在不同的源文件之间进行拆分。有关更多信息，请参见分部类和方法（C# 编程指南）。

说明

下面的示例中定义了一个公共类，其中包含一个字段、一个方法和一个称为构造函数的特殊方法。有关更多信息，请参见构造函数（C# 编程指南）。然后使用 new 关键字将该类实例化。

示例

C# 复制代码
public class Person
{
    // Field
    public string name;

    // Constructor
    public Person()
    {
        name = "unknown";
    }

    // Method
    public void SetName(string newName)
    {
        name = newName;
    }
}
class TestPerson
{
    static void Main()
    {
        Person person = new Person();
        Console.WriteLine(person.name);

        person.SetName("John Smith");
        Console.WriteLine(person.name);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    unknown
    John Smith
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6 类和对象

  • 10 类

请参见

概念

C# 编程指南

参考

成员（C# 编程指南）
方法（C# 编程指南）
构造函数（C# 编程指南）
析构函数（C# 编程指南）
对象（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 移除了关于基访问的有误导性的说法。 |


对象（C# 编程指南）

[image]

C# 编程指南
对象（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

类或结构定义的作用类似于蓝图，指定该类型可以进行哪些操作。从本质上说，对象是按照此蓝图分配和配置的内存块。程序可以创建同一个类的多个对象。对象也称为实例，可以存储在命名变量中，也可以存储在数组或集合中。使用这些变量来调用对象方法及访问对象公共属性的代码称为客户端代码。在 C# 等面向对象的语言中，典型的程序由动态交互的多个对象组成。

说明：

静态类型的行为与此处介绍的不同。有关更多信息，请参见静态类和静态类成员（C# 编程指南）。

结构实例与类实例

由于类是引用类型，因此类对象的变量引用该对象在托管堆上的地址。如果将同一类型的第二个对象分配给第一个对象，则两个变量都引用该地址的对象。这一点将在本主题后面部分进行更详细的讨论。

类的实例是使用 new 运算符创建的。在下面的示例中，Person 为类型，person1 和 person 2 为该类型的实例（即对象）。

C# 复制代码
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    //Other properties, methods, events...
}

class Program
{
    static void Main()
    {
        Person person1 = new Person("Leopold", 6);
        Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age);

        // Declare a new person, assign person1 to it.
        Person person2 = person1;

        //Change the name of person2, and person1 also changes.
        person2.Name = "Molly";
        person2.Age = 16;

        Console.WriteLine("person2 Name = {0} Age = {1}", person2.Name, person2.Age);
        Console.WriteLine("person1 Name = {0} Age = {1}", person2.Name, person2.Age);

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();

    }
}
/*
    Output:
    person1 Name = Leopold Age = 6
    person2 Name = Molly Age = 16
    person1 Name = Molly Age = 16
*/

由于结构是值类型，因此结构对象的变量具有整个对象的副本。结构的实例也可以使用 new 运算符来创建，但这不是必需的，如下面的示例所示：

C# 复制代码
public struct Person
{
    public string Name;
    public int Age;
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

public class Application
{
    static void Main()
    {
        // Create a struct instance and initialize by using "new".
        // Memory is allocated on thread stack.
        Person p1 = new Person("Alex", 9);
        Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);

        // Create a new struct object. Note that a struct can be initialized
        // without using "new".
        Person p2 = p1;

        // Assign values to p2 members.
        p2.Name = "Spencer";
        p2.Age = 7;
        Console.WriteLine("p2 Name = {0} Age = {1}", p2.Name, p2.Age);

        // p1 values remain unchanged because p2 is a copy.
        Console.WriteLine("p1 Name = {0} Age = {1}", p1.Name, p1.Age);

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/*
  Output:
    p1 Name = Alex Age = 9
    p2 Name = Spencer Age = 7
    p1 Name = Alex Age = 9
*/

p1 和 p2 的内存在线程堆栈上进行分配。该内存随声明它的类型或方法一起回收。这就是在赋值时复制结构的一个原因。相比之下，当对类实例对象的所有引用都超出范围时，为该类实例分配的内存将由公共语言运行时自动回收（垃圾回收）。无法像在 C++ 中那样明确地销毁类对象。有关 .NET Framework 中的垃圾回收的更多信息，请参见垃圾回收。

说明：

公共语言运行时中高度优化了托管堆上内存的分配和释放。在大多数情况下，在堆上分配类实例与在堆栈上分配结构实例在性能开销上没有显著的差别。

对象标识与相等的值

在比较两个对象是否相等时，首先必须明确您是想知道两个变量是否表示内存中的同一对象，还是想知道这两个对象的一个或多个字段的值是否相等。如果您要对值进行比较，则必须考虑这两个对象是值类型（结构）的实例，还是引用类型（类、委托、数组）的实例。

  • 若要确定两个类实例是否引用内存中的同一位置（意味着它们具有相同的标识），可使用静态 Equals 方法。（System..::.Object 是所有值类型和引用类型的隐式基类，其中包括用户定义的结构和类。）

  • 若要确定两个结构实例中的实例字段是否具有相同的值，可使用 ValueType..::.Equals 方法。由于所有结构都隐式继承自 System..::.ValueType，因此可以直接在对象上调用该方法，如下面的示例所示：

C# 复制代码
Person p1 = new Person("Wallace", 75);
Person p2;
p2.Name = "Wallace";
p2.Age = 75;

if (p2.Equals(p1))
    Console.WriteLine("p2 and p1 have the same values.");

// Output: p2 and p1 have the same values.

Equals 的 System..::.ValueType 实现使用反射，因为它必须能够确定任何结构中有哪些字段。在创建您自己的结构时，重写 Equals 方法可以提供针对您的类型的高效求等算法。

相关章节

更多信息：

  • 类（C# 编程指南）

  • 结构（C# 编程指南）

  • 构造函数（C# 编程指南）

  • 析构函数（C# 编程指南）

  • 事件（C# 编程指南）

请参见

概念

C# 编程指南
通用类型系统中的值类型
通用类型系统中的类

参考

object（C# 参考）
继承（C# 编程指南）
class（C# 参考）
struct（C# 参考）
new 运算符（C# 参考）


结构（C# 编程指南）

[image]

C# 编程指南
结构（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

结构是使用 struct 关键字定义的，例如：

C# 复制代码
public struct PostalAddress
{
    // Fields, properties, methods and events go here...
}

结构与类共享大多数相同的语法，但结构比类受到的限制更多：

  • 在结构声明中，除非字段被声明为 const 或 static，否则无法初始化。

  • 结构不能声明默认构造函数（没有参数的构造函数）或析构函数。

  • 结构不能从类或其他结构继承。

  • 结构在赋值时进行复制。将结构赋值给新变量时，将复制所有数据，并且对新副本所做的任何修改不会更改原始副本的数据。

  • 结构是值类型，而类是引用类型。

  • 与类不同，结构的实例化可以不使用 new 运算符。

  • 结构可以声明带参数的构造函数。

  • 一个结构不能从另一个结构或类继承，而且不能作为一个类的基。所有结构都直接继承自 System.ValueType，后者继承自 System.Object。

  • 结构可以实现接口。

  • 结构可用作可为 null 的类型，因而可向其赋 null 值。

相关章节

更多信息：

  • 使用结构（C# 编程指南）

  • 构造函数（C# 编程指南）

  • 可空类型（C# 编程指南）

  • 如何：了解向方法传递结构和向方法传递类引用之间的区别（C# 编程指南）

  • 如何：在结构间实现用户定义的转换（C# 编程指南）

请参见

概念

C# 编程指南
结构设计

参考

类和结构（C# 编程指南）
类（C# 编程指南）


使用结构（C# 编程指南）

[image]

C# 编程指南
使用结构（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

struct 类型适于表示 Point、Rectangle 和 Color 等轻量对象。尽管使用自动实现的属性将一个点表示为类同样方便，但在某些情况下使用结构更加有效。例如，如果声明一个 1000 个 Point 对象组成的数组，为了引用每个对象，则需分配更多内存；这种情况下，使用结构可以节约资源。因为 .NET Framework 包含一个名为 Point 的对象，所以本示例中的结构命名为“CoOrds”。

C# 复制代码
public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

为结构定义默认（无参数）构造函数是错误的。在结构体中初始化实例字段也是错误的。只能通过两种方式初始化结构成员：一是使用参数化构造函数，二是在声明结构后分别访问成员。对于任何私有成员或以其他方式设置为不可访问的成员，只能在构造函数中进行初始化。

如果使用 new 运算符创建结构对象，则会创建该结构对象，并调用适当的构造函数。与类不同，结构的实例化可以不使用 new 运算符。在此情况下不存在构造函数调用，因而可以提高分配效率。但是，在初始化所有字段之前，字段将保持未赋值状态且对象不可用。

当结构包含引用类型作为成员时，必须显式调用该成员的默认构造函数，否则该成员将保持未赋值状态且该结构不可用。（这将导致编译器错误 CS0171。）

对于结构，不像类那样存在继承。一个结构不能从另一个结构或类继承，而且不能作为一个类的基。但是，结构从基类 Object 继承。结构可实现接口，其方式同类完全一样。

与 C++ 不同，无法使用 struct 关键字声明类。在 C# 中，类与结构在语义上是不同的。结构是值类型，而类是引用类型。有关更多信息，请参见值类型。

除非需要引用类型语义，否则系统将较小的类作为结构处理效率会更高。

示例 1

说明

下面的示例演示使用默认构造函数和参数化构造函数的 struct 初始化。

代码

C# 复制代码
public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}
C# 复制代码
// Declare and initialize struct objects.
class TestCoOrds
{
    static void Main()
    {
        // Initialize:
        CoOrds coords1 = new CoOrds();
        CoOrds coords2 = new CoOrds(10, 10);

        // Display results:
        Console.Write("CoOrds 1: ");
        Console.WriteLine("x = {0}, y = {1}", coords1.x, coords1.y);

        Console.Write("CoOrds 2: ");
        Console.WriteLine("x = {0}, y = {1}", coords2.x, coords2.y);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    CoOrds 1: x = 0, y = 0
    CoOrds 2: x = 10, y = 10
*/

示例 2

说明

下面举例说明了结构特有的一种功能。它在不使用 new 运算符的情况下创建 CoOrds 对象。如果将 struct 换成 class，程序将不会编译。

代码

C# 复制代码
public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}
C# 复制代码
// Declare a struct object without "new."
class TestCoOrdsNoNew
{
    static void Main()
    {
        // Declare an object:
        CoOrds coords1;

        // Initialize:
        coords1.x = 10;
        coords1.y = 20;

        // Display results:
        Console.Write("CoOrds 1: ");
        Console.WriteLine("x = {0}, y = {1}", coords1.x, coords1.y);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: CoOrds 1: x = 10, y = 20

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
结构（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增加了关于 CS0171 的段落 | 内容 Bug 修复


继承（C# 编程指南）

[image]

C# 编程指南
继承（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

继承（加上封装和多态性）是面向对象的编程的三个主要特性（“支柱”）之一。继承用于创建可重用、扩展和修改在其他类中定义的行为的新类。其成员被继承的类称为“基类”，继承这些成员的类称为“派生类”。

说明：

结构不支持继承，但可以实现接口。有关更多信息，请参见接口（C# 编程指南）。

从概念上来说，派生类是基类的专用化。例如，如果您有一个基类 Animal，则可以有一个名为 Mammal 的派生类和一个名为 Reptile 的派生类。Mammal 是一个 Animal，Reptile 也是一个 Animal，但每个派生类均表示基类的不同专用化。

定义一个类从其他类派生时，派生类隐式获得基类的除构造函数和析构函数以外的所有成员。因此，派生类可以重用基类中的代码而无需重新实现这些代码。可以在派生类中添加更多成员。派生类以这种方式扩展基类的功能。

下图演示一个 WorkItem 类，该类表示某业务流程中的一个工作项。和所有的类一样，该类派生自 System..::.Object 并继承其所有方法。WorkItem 添加了自己的五个成员。其中包括一个构造函数，因为构造函数不能继承。ChangeRequest 继承自 WorkItem 并表示特定种类的工作项。ChangeRequest 在它从 WorkItem 和 Object 继承的成员中另外添加了两个成员。它必须添加自己的构造函数，还要添加一个成员以实现 ChangeRequest 与应用更改的原始 WorkItem 之间的关联。

类继承

下面的示例演示如何以 C# 表示上图所示的类关系。该示例还演示 WorkItem 如何重写虚方法 Object..::.ToString 以及 ChangeRequest 类如何继承该方法的 WorkItem 实现。

C# 复制代码
// WorkItem implicitly inherits from Object class
public class WorkItem
{
    private static int nextID;
    protected int ID { get; set; }
    protected TimeSpan jobLength { get; set; }
    protected string Title { get; set; }
    protected string Description { get; set; }
    // Default constructor
    public WorkItem()
    {
        ID = 0;
        Title = "Default title";
        Description = "Default description.";
        jobLength = new TimeSpan();
    }
    // Static constructor for static member.
    static WorkItem()
    {
        nextID = 0;
    }
    // Instance constructor.
    public WorkItem( string title, string desc, TimeSpan joblen)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = joblen;
    }
    protected int GetNextID()
    {
       return ++nextID;
    }
    public void Update(string title, TimeSpan joblen)
    {
        this.Title = title;
        this.jobLength = joblen;
    }

    // Virtual method override.
    public override string ToString()
    {
        return String.Format("{0} - {1}", this.ID, this.Title);
    }
}

// ChangeRequest derives from WorkItem and adds two of its own members.
public class ChangeRequest : WorkItem
{
    protected int originalItemID {get; set;}
    public ChangeRequest() { }
    public ChangeRequest(string title, string desc, TimeSpan jobLen, int originalID)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = jobLen;
        this.originalItemID = originalID;
    }
}

class Program
{
    static void Main()
    {
        WorkItem item = new WorkItem(
                        "Fix Bugs",
                        "Fix all bugs in my source code branch",
                        new TimeSpan(3, 4, 0, 0));

        ChangeRequest change = new ChangeRequest("Change design of base class",
                                                 "Add members to base class",
                                                 new TimeSpan(4, 0, 0),
                                                 1);

        Console.WriteLine(item.ToString());

        // ChangeRequest inherits WorkItem's override of ToString
        Console.WriteLine(change.ToString());

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    1 - Fix Bugs
    2 - Change design of base class
*/

抽象方法和虚方法

当基类将方法声明为 virtual 时，派生类可以用自己的实现重写该方法。如果基类将成员声明为 abstract，则在直接继承自该类的任何非抽象类中都必须重写该方法。如果派生类自身是抽象的，则它继承抽象成员而不实现它们。抽象成员和虚成员是多态性的基础，多态性是面向对象的编程的第二个主要特性。有关更多信息，请参见多态性（C# 编程指南）。

抽象基类

如果希望禁止通过 new 关键字直接进行实例化，可以将类声明为 abstract。如果这样做，则仅当从该类派生新类时才能使用该类。抽象类可以包含一个或多个自身声明为抽象的方法签名。这些签名指定参数和返回值，但没有实现（方法体）。抽象类不必包含抽象成员；但是，如果某个类确实包含抽象成员，则该类自身必须声明为抽象类。自身不是抽象类的派生类必须为抽象基类中的任何抽象方法提供实现。有关更多信息，请参见抽象类、密封类及类成员（C# 编程指南）和抽象类设计。

接口

“接口”是一种引用类型，有点像仅包含抽象成员的抽象基类。类在从接口派生时必须为该接口的所有成员提供实现。类虽然只能从一个直接基类派生，但可以实现多个接口。

接口用于为不一定具有“是”关系的类定义特定功能。例如，如果任何类或结构需要让客户端代码判断某类型的两个对象是否相等（无论该类型如何定义相等性），那么该类或结构就可以实现 IEquatable[`1] 接口。IEquatable<(Of <(T>)>) 不表示基类和派生类之间存在的同一种“是”关系（例如 Mammal 是 Animal）。有关更多信息，请参见接口（C# 编程指南）。

派生类对基类成员的访问

派生类可以访问基类的公共成员、受保护成员、内部成员和受保护内部成员。即使派生类继承基类的私有成员，仍不能访问这些成员。但是，所有这些私有成员在派生类中仍然存在，且执行与基类自身中相同的工作。例如，假定一个受保护基类方法访问私有字段。要使继承的基类方法正常工作，派生类中必须有该字段。

禁止进一步派生

类可以将自身或其成员声明为 sealed，从而禁止其他类从该类自身或其任何成员继承。有关更多信息，请参见抽象类、密封类及类成员（C# 编程指南）。

派生类隐藏基类成员

派生类可以通过以相同的名称和签名声明基类成员来隐藏这些成员。可以使用 new 修饰符显式指示成员不作为基类成员的重写。不是必须要使用 new，但如果不使用 new，将生成编译器警告。有关更多信息，请参见使用 Override 和 New 关键字进行版本控制（C# 编程指南）和了解何时使用 Override 和 New 关键字（C# 编程指南）。

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
class（C# 参考）
struct（C# 参考）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增加了内容、图示和新示例。 | 信息补充。


多态性（C# 编程指南）

[image]

C# 编程指南
多态性（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

多态性常被视为自封装和继承之后，面向对象的编程的第三个支柱。Polymorphism（多态性）是一个希腊词，指“多种形态”，多态性具有两个截然不同的方面：

  1. 在运行时，在方法参数和集合或数组等位置，派生类的对象可以作为基类的对象处理。发生此情况时，该对象的声明类型不再与运行时类型相同。

  2. 基类可以定义并实现虚方法，派生类可以重写这些方法，即派生类提供自己的定义和实现。在运行时，客户端代码调用该方法，CLR 查找对象的运行时类型，并调用虚方法的重写方法。因此，您可以在源代码中调用基类的方法，但执行该方法的派生类版本。

虚方法允许您以统一方式处理多组相关的对象。例如，假定您有一个绘图应用程序，允许用户在绘图图面上创建各种形状。您在编译时不知道用户将创建哪些特定类型的形状。但应用程序必须跟踪创建的所有类型的形状，并且必须更新这些形状以响应用户鼠标操作。您可以使用多态性通过两个基本步骤解决这一问题：

  1. 创建一个类层次结构，其中每个特定形状类均派生自一个公共基类。

  2. 使用虚方法通过对基类方法的单个调用来调用任何派生类上的相应方法。

首先，创建一个名为 Shape 的基类，并创建一些派生类，例如 Rectangle、Circle 和 Triangle。为 Shape 类提供一个名为 Draw 的虚方法，并在每个派生类中重写该方法以绘制该类表示的特定形状。创建一个 List<Shape> 对象，并向该对象添加 Circle、Triangle 和 Rectangle。若要更新绘图图面，请使用 foreach 循环对该列表进行循环访问，并对其中的每个 Shape 对象调用 Draw 方法。虽然列表中的每个对象都具有声明类型 Shape，但调用的将是运行时类型（该方法在每个派生类中的重写版本）。

C# 复制代码
public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Polymorphism at work #1: a Rectangle, Triangle and Circle
        // can all be used whereever a Shape is expected. No cast is
        // required because an implicit conversion exists from a derived
        // class to its base class.
        System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
        shapes.Add(new Rectangle());
        shapes.Add(new Triangle());
        shapes.Add(new Circle());

        // Polymorphism at work #2: the virtual method Draw is
        // invoked on each of the derived classes, not the base class.
        foreach (Shape s in shapes)
        {
            s.Draw();
        }

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

}

/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
 */

在 C# 中，每个类型都是多态的，因为包括用户定义类型在内的所有类型都继承自 Object。

多态性概述

虚拟成员

当派生类从基类继承时，它会获得基类的所有方法、字段、属性和事件。派生类的设计器可以选择是否

  • 重写基类中的虚拟成员。

  • 继承最接近的基类方法而不重写它

  • 定义隐藏基类实现的成员的新非虚实现

仅当基类成员声明为 virtual 或 abstract 时，派生类才能重写基类成员。派生成员必须使用 override 关键字显式指示该方法将参与虚调用。以下代码提供了一个示例：

C# 复制代码
public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}

字段不能是虚拟的，只有方法、属性、事件和索引器才可以是虚拟的。当派生类重写某个虚拟成员时，即使该派生类的实例被当作基类的实例访问，也会调用该成员。以下代码提供了一个示例：

C# 复制代码
DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Also calls the new method.

虚方法和属性允许派生类扩展基类，而无需使用方法的基类实现。有关更多信息，请参见使用 Override 和 New 关键字进行版本控制（C# 编程指南）。接口提供另一种方式来定义将实现留给派生类的方法或方法集。有关更多信息，请参见接口（C# 编程指南）和在类和接口之间选择。

使用新成员隐藏基类成员

如果希望派生成员具有与基类中的成员相同的名称，但又不希望派生成员参与虚调用，则可以使用 new 关键字。new 关键字放置在要替换的类成员的返回类型之前。以下代码提供了一个示例：

C# 复制代码
public class BaseClass
{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}

通过将派生类的实例强制转换为基类的实例，仍然可以从客户端代码访问隐藏的基类成员。例如：

C# 复制代码
DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

阻止派生类重写虚拟成员

无论在虚拟成员和最初声明虚拟成员的类之间已声明了多少个类，虚拟成员永远都是虚拟的。如果类 A 声明了一个虚拟成员，类 B 从 A 派生，类 C 从类 B 派生，则类 C 继承该虚拟成员，并且可以选择重写它，而不管类 B 是否为该成员声明了重写。以下代码提供了一个示例：

C# 复制代码
public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}

派生类可以通过将重写声明为 sealed 来停止虚拟继承。这需要在类成员声明中的 override 关键字前面放置 sealed 关键字。以下代码提供了一个示例：

C# 复制代码
public class C : B
{
    public sealed override void DoWork() { }
}

在上面的示例中，方法 DoWork 对从 C 派生的任何类都不再是虚方法，但它仍是 C 的实例的虚方法 -- 即使将这些实例强制转换为类型 B 或类型 A 也是如此。派生类可以通过使用 new 关键字替换密封的方法，如下面的示例所示：

C# 复制代码
public class D : C
{
    public new void DoWork() { }
}

在此情况下，如果在 D 中使用类型为 D 的变量调用 DoWork，被调用的将是新的 DoWork。如果使用类型为 C、B 或 A 的变量访问 D 的实例，对 DoWork 的调用将遵循虚拟继承的规则，即把这些调用传送到类 C 的 DoWork 实现。

从派生类访问基类虚拟成员

已替换或重写某个方法或属性的派生类仍然可以使用基关键字访问基类的该方法或属性。以下代码提供了一个示例：

C# 复制代码
public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //Perform Derived's work here
        //...
        // Call DoWork on base class
        base.DoWork();
    }
}

有关更多信息，请参见 base。

说明：

建议虚拟成员在它们自己的实现中使用 base 来调用该成员的基类实现。允许基类行为发生使得派生类能够集中精力实现特定于派生类的行为。未调用基类实现时，由派生类负责使它们的行为与基类的行为兼容。

本节内容

  • 使用 Override 和 New 关键字进行版本控制（C# 编程指南）

  • 了解何时使用 Override 和 New 关键字（C# 编程指南）

  • 如何：重写 ToString 方法（C# 编程指南）

请参见

概念

C# 编程指南
C# 编程指南

参考

继承（C# 编程指南）
抽象类、密封类及类成员（C# 编程指南）
方法（C# 编程指南）
事件（C# 编程指南）
属性（C# 编程指南）
索引器（C# 编程指南）
类型（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增加了内容和新的示例。 | 信息补充。


使用 Override 和 New 关键字进行版本控制（C# 编程指南）

[image]

C# 编程指南
使用 Override 和 New 关键字进行版本控制（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 语言经过专门设计，以便不同库中的基类与派生类之间的版本控制可以不断向前发展，同时保持向后兼容。这具有多方面的意义。例如，这意味着在基类中引入与派生类中的某个成员具有相同名称的新成员在 C# 中是完全支持的，不会导致意外行为。它还意味着类必须显式声明某方法是要重写一个继承方法，还是一个隐藏具有类似名称的继承方法的新方法。

在 C# 中，派生类可以包含与基类方法同名的方法。

  • 基类方法必须定义为 virtual。

  • 如果派生类中的方法前面没有 new 或 override 关键字，则编译器将发出警告，该方法将有如存在 new 关键字一样执行操作。

  • 如果派生类中的方法前面带有 new 关键字，则该方法被定义为独立于基类中的方法。

  • 如果派生类中的方法前面带有 override 关键字，则派生类的对象将调用该方法，而不是调用基类方法。

  • 可以从派生类中使用 base 关键字调用基类方法。

  • override、virtual 和 new 关键字还可以用于属性、索引器和事件中。

默认情况下，C# 方法为非虚方法。如果某个方法被声明为虚方法，则继承该方法的任何类都可以实现它自己的版本。若要使方法成为虚方法，必须在基类的方法声明中使用 virtual 修饰符。然后，派生类可以使用 override 关键字重写基虚方法，或使用 new 关键字隐藏基类中的虚方法。如果 override 关键字和 new 关键字均未指定，编译器将发出警告，并且派生类中的方法将隐藏基类中的方法。有关更多信息，请参见编译器警告（等级 2）CS0108。

为了在实践中演示上述情况，我们暂时假定公司 A 创建了一个名为 GraphicsClass 的类，您的程序将使用此类。GraphicsClass 如下所示：

C# 复制代码
class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
}

您的公司使用此类，并且您在添加新方法时将其用来派生自己的类：

C# 复制代码
class YourDerivedGraphicsClass : GraphicsClass
{
    public void DrawRectangle() { }
}

您的应用程序运行正常，直到公司 A 发布了 GraphicsClass 的新版本，类似于下面的代码：

C# 复制代码
class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
    public virtual void DrawRectangle() { }
}

现在，GraphicsClass 的新版本中包含一个名为 DrawRectangle 的方法。开始时，没有出现任何问题。新版本仍然与旧版本保持二进制兼容。已经部署的任何软件都将继续正常工作，即使新类已安装到这些软件所在的计算机系统上。在您的派生类中，对方法 DrawRectangle 的任何现有调用将继续引用您的版本。

但是，一旦您使用 GraphicsClass 的新版本重新编译应用程序，就会收到来自编译器的警告。有关更多信息，请参见编译器警告（等级 2）CS0108。

此警告提示您必须考虑希望 DrawRectangle 方法在应用程序中的工作方式。

如果您希望自己的方法重写新的基类方法，请使用 override 关键字：

C# 复制代码
class YourDerivedGraphicsClass : GraphicsClass
{
    public override void DrawRectangle() { }
}

override 关键字可确保派生自 YourDerivedGraphicsClass 的任何对象都将使用 DrawRectangle 的派生类版本。派生自 YourDerivedGraphicsClass 的对象仍可以使用基关键字访问 DrawRectangle 的基类版本：

C# 复制代码
base.DrawRectangle();

如果您不希望自己的方法重写新的基类方法，则需要注意以下事项。为了避免这两个方法之间发生混淆，可以重命名您的方法。这可能很耗费时间且容易出错，而且在某些情况下并不可行。但是，如果您的项目相对较小，则可以使用 Visual Studio 的重构选项来重命名方法。有关更多信息，请参见重构类和类型。

或者，也可以通过在派生类定义中使用关键字 new 来防止出现该警告：

C# 复制代码
class YourDerivedGraphicsClass : GraphicsClass
{
    public new void DrawRectangle() { }
}

使用 new 关键字可告诉编译器您的定义将隐藏基类中包含的定义。这是默认行为。

重写和方法选择

当在类中指定方法时，如果有多个方法与调用兼容（例如，存在两种同名的方法，并且其参数与传递的参数兼容），则 C# 编译器将选择最佳方法进行调用。下面的方法将是兼容的：

C# 复制代码
public class Derived : Base
{
    public override void DoWork(int param) { }
    public void DoWork(double param) { }
}

在 Derived 的一个实例中调用 DoWork 时，C# 编译器将首先尝试使该调用与最初在 Derived 上声明的 DoWork 版本兼容。重写方法不被视为是在类上进行声明的，而是在基类上声明的方法的新实现。仅当 C# 编译器无法将方法调用与 Derived 上的原始方法匹配时，它才尝试将该调用与具有相同名称和兼容参数的重写方法匹配。例如：

C# 复制代码
int val = 5;
Derived d = new Derived();
d.DoWork(val);  // Calls DoWork(double).

由于变量 val 可以隐式转换为 double 类型，因此 C# 编译器将调用 DoWork(double)，而不是 DoWork(int)。有两种方法可以避免此情况。首先，避免将新方法声明为与虚方法同名。其次，可以通过将 Derived 的实例强制转换为 Base 来使 C# 编译器搜索基类方法列表，从而使其调用虚方法。由于是虚方法，因此将调用 Derived 上的 DoWork(int) 的实现。例如：

C# 复制代码
((Base)d).DoWork(val);  // Calls DoWork(int) on Derived.

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
方法（C# 编程指南）
继承（C# 编程指南）


了解何时使用 Override 和 New 关键字（C# 编程指南）

[image]

C# 编程指南
了解何时使用 Override 和 New 关键字（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 允许派生类中的方法与基类中的方法具有相同的名称，只要您非常明确应如何处理新方法。下面的示例演示 new 和 override 关键字的使用。

首先声明三个类：一个名为 Car 的基类以及从该基类派生的两个类 ConvertibleCar 和 Minivan。基类包含一个可将有关汽车的描述发送到控制台的方法 (DescribeCar)。派生类方法也包含一个名为 DescribeCar 的方法，该方法显示派生类的独特属性。这些方法还调用基类的 DescribeCar 方法来演示从 Car 类继承属性的方式。

为了强调区别，ConvertibleCar 类使用 new 关键字来定义，而 Minivan 类使用 override 来定义。

C# 复制代码
// Define the base class
class Car
{
    public virtual void DescribeCar()
    {
        System.Console.WriteLine("Four wheels and an engine.");
    }
}

// Define the derived classes
class ConvertibleCar : Car
{
    public new virtual void DescribeCar()
    {
        base.DescribeCar();
        System.Console.WriteLine("A roof that opens up.");
    }
}

class Minivan : Car
{
    public override void DescribeCar()
    {
        base.DescribeCar();
        System.Console.WriteLine("Carries seven people.");
    }
}

现在可以编写一些代码来声明这些类的实例，并调用它们的方法以便对象能够描述其自身：

C# 复制代码
public static void TestCars1()
{
    Car car1 = new Car();
    car1.DescribeCar();
    System.Console.WriteLine("----------");

    ConvertibleCar car2 = new ConvertibleCar();
    car2.DescribeCar();
    System.Console.WriteLine("----------");

    Minivan car3 = new Minivan();
    car3.DescribeCar();
    System.Console.WriteLine("----------");
}

正如预期的那样，输出类似如下所示：

Four wheels and an engine.

----------

Four wheels and an engine.

A roof that opens up.

----------

Four wheels and an engine.

Carries seven people.

----------

但是，在该代码接下来的一节中，我们声明了一个从 Car 基类派生的对象的数组。此数组能够存储 Car、ConvertibleCar 和 Minivan 对象。该数组的声明类似如下所示：

C# 复制代码
public static void TestCars2()
{
    Car[] cars = new Car[3];
    cars[0] = new Car();
    cars[1] = new ConvertibleCar();
    cars[2] = new Minivan();
}

然后可以使用一个 foreach 循环来访问该数组中包含的每个 Car 对象，并调用 DescribeCar 方法，如下所示：

C# 复制代码
foreach (Car vehicle in cars)
{
    System.Console.WriteLine("Car object: " + vehicle.GetType());
    vehicle.DescribeCar();
    System.Console.WriteLine("----------");
}

此循环的输出如下所示：

Car object: YourApplication.Car

Four wheels and an engine.

----------

Car object: YourApplication.ConvertibleCar

Four wheels and an engine.

----------

Car object: YourApplication.Minivan

Four wheels and an engine.

Carries seven people.

----------

注意，ConvertibleCar 说明与您的预期不同。由于使用了 new 关键字来定义此方法，所调用的不是派生类方法，而是基类方法。Minivan 对象正确地调用重写方法，并产生预期的结果。

若要强制一个规则，要求从 Car 派生的所有类都必须实现 DescribeCar 方法，则应创建一个新的基类，将方法 DescribeCar 定义为 abstract。抽象方法不包含任何代码，仅包含方法签名。从此基类派生的任何类都必须提供 DescribeCar 的实现。有关更多信息，请参见抽象。

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
使用 Override 和 New 关键字进行版本控制（C# 编程指南）


如何：重写 ToString 方法（C# 编程指南）

[image]

C# 编程指南
如何：重写 ToString 方法（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 中的每个对象都继承 ToString 方法，此方法返回该对象的字符串表示形式。例如，所有 int 类型的变量都有一个 ToString 方法，此方法可让这些变量将其内容作为字符串返回：

C# 复制代码
int x = 42;
string strx = x.ToString();
System.Console.WriteLine(strx);

创建自定义类或结构时，应该重写 ToString 方法，以便向客户端代码提供类型信息。

安全说明：

当您决定通过此方法提供的信息的类型时，应考虑您的类或结构是否会被不受信任的代码使用。请务必确保您没有提供任何会被恶意代码利用的信息。

在类或结构中重写 OnString 方法

  1. 通过下面的修饰符和返回类型声明 ToString 方法：

    复制代码
    public override string ToString(){}

  2. 实现该方法，使其返回一个字符串。

    下面的示例不仅返回类的名称，还返回特定于该类的某个实例的数据。请注意，它还会在 age 变量上使用 ToString 方法，将 int 转换为可输出的字符串。

    复制代码
    class Person
{
  string name;
  int age;
  SampleObject(string name, int age)
  {
  this.name = name;
  this.age = age;
  }
  public override string ToString()
  {
     string s = age.ToString();
     return "Person: " + name + " " + s;
  }
}

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
new（C# 参考）
override（C# 参考）
virtual（C# 参考）


抽象类、密封类及类成员（C# 编程指南）

[image]

C# 编程指南
抽象类、密封类及类成员（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

使用 abstract 关键字可以创建必须在派生类中实现的不完整的类和类成员。

使用 sealed 关键字可以防止继承以前标记为 virtual 的类或某些类成员。

抽象类和类成员

通过在类定义前面放置关键字 abstract，可以将类声明为抽象类。例如：

C# 复制代码
public abstract class A
{
    // Class members here.
}

抽象类不能实例化。抽象类的用途是提供多个派生类可共享的基类的公共定义。例如，类库可以定义一个作为其多个函数的参数的抽象类，并要求程序员使用该库通过创建派生类来提供自己的类实现。

抽象类也可以定义抽象方法。方法是将关键字 abstract 添加到方法的返回类型的前面。例如：

C# 复制代码
public abstract class A
{
    public abstract void DoWork(int i);
}

抽象方法没有实现，所以方法定义后面是分号，而不是常规的方法块。抽象类的派生类必须实现所有抽象方法。当抽象类从基类继承虚方法时，抽象类可以使用抽象方法重写该虚方法。例如：

C# 复制代码
// compile with: /target:library
public class D
{
    public virtual void DoWork(int i)
    {
        // Original implementation.
    }
}

public abstract class E : D
{
    public abstract override void DoWork(int i);
}

public class F : E
{
    public override void DoWork(int i)
    {
        // New implementation.
    }
}

如果将 virtual 方法声明为 abstract，则该方法对于从抽象类继承的所有类而言仍然是虚方法。继承抽象方法的类无法访问该方法的原始实现。在前面的示例中，类 F 上的 DoWork 无法调用类 D 上的 DoWork。在此情况下，抽象类可以强制派生类为虚方法提供新的方法实现。

密封类和类成员

通过在类定义前面放置关键字 sealed，可以将类声明为密封类。例如：

C# 复制代码
public sealed class D
{
    // Class members here.
}

密封类不能用作基类。因此，它也不能是抽象类。密封类禁止派生。由于密封类从不用作基类，所以有些运行时优化可以使对密封类成员的调用略快。

在对基类的虚成员进行重写的派生类上的类成员、方法、字段、属性或事件可以将该成员声明为密封成员。在用于以后的派生类时，这将取消成员的虚效果。方法是在类成员声明中将 sealed 关键字置于 override 关键字的前面。例如：

C# 复制代码
public class D : C
{
    public sealed override void DoWork() { }
}

请参见

任务

如何：定义抽象属性（C# 编程指南）

概念

C# 编程指南

参考

类和结构（C# 编程指南）
继承（C# 编程指南）
方法（C# 编程指南）
字段（C# 编程指南）


如何：定义抽象属性（C# 编程指南）

[image]

C# 编程指南
如何：定义抽象属性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的示例演示如何定义抽象属性。抽象属性声明不提供属性访问器的实现，它只声明该类支持属性，而将访问器实现留给派生类。下面的示例演示如何实现从基类继承的抽象属性。

此示例由三个文件组成，其中每个文件都单独编译，产生的程序集由下一次编译引用：

  • abstractshape.cs：包含抽象 Area 属性的 Shape 类。

  • shapes.cs：Shape 类的子类。

  • shapetest.cs：测试程序，它显示某些 Shape 派生对象的面积。

若要编译该示例，请使用以下命令：

csc abstractshape.cs shapes.cs shapetest.cs

这样将生成可执行文件 shapetest.exe。

示例

该文件声明的 Shape 类包含 double 类型的 Area 属性。

C# 复制代码
// compile with: csc /target:library abstractshape.cs
public abstract class Shape
{
    private string name;

    public Shape(string s)
    {
        // calling the set accessor of the Id property.
        Id = s;
    }

    public string Id
    {
        get
        {
            return name;
        }

        set
        {
            name = value;
        }
    }

    // Area is a read-only property - only a get accessor is needed:
    public abstract double Area
    {
        get;
    }

    public override string ToString()
    {
        return Id + " Area = " + string.Format("{0:F2}", Area);
    }
}

  • 属性的修饰符就放置在属性声明中。例如：

    复制代码
    public abstract double Area

  • 声明抽象属性时（如本示例中的 Area），指明哪些属性访问器可用即可，不要实现它们。在此示例中，只有一个 get 访问器可用，因此该属性是只读的。

下面的代码演示 Shape 的三个子类，并演示它们如何重写 Area 属性来提供自己的实现。

C# 复制代码
// compile with: csc /target:library /reference:abstractshape.dll shapes.cs
public class Square : Shape
{
    private int side;

    public Square(int side, string id)
        : base(id)
    {
        this.side = side;
    }

    public override double Area
    {
        get
        {
            // Given the side, return the area of a square:
            return side * side;
        }
    }
}

public class Circle : Shape
{
    private int radius;

    public Circle(int radius, string id)
        : base(id)
    {
        this.radius = radius;
    }

    public override double Area
    {
        get
        {
            // Given the radius, return the area of a circle:
            return radius * radius * System.Math.PI;
        }
    }
}

public class Rectangle : Shape
{
    private int width;
    private int height;

    public Rectangle(int width, int height, string id)
        : base(id)
    {
        this.width = width;
        this.height = height;
    }

    public override double Area
    {
        get
        {
            // Given the width and height, return the area of a rectangle:
            return width * height;
        }
    }
}

下面的代码演示一个测试程序，它创建若干 Shape 派生对象，并输出它们的面积。

C# 复制代码
// compile with: csc /reference:abstractshape.dll;shapes.dll shapetest.cs
class TestClass
{
    static void Main()
    {
        Shape[] shapes =
        {
            new Square(5, "Square #1"),
            new Circle(3, "Circle #1"),
            new Rectangle( 4, 5, "Rectangle #1")
        };

        System.Console.WriteLine("Shapes Collection");
        foreach (Shape s in shapes)
        {
            System.Console.WriteLine(s);
        }
    }
}
/* Output:
    Shapes Collection
    Square #1 Area = 25.00
    Circle #1 Area = 28.27
    Rectangle #1 Area = 20.00
*/

请参见

任务

如何：创建和使用 C# DLL（C# 编程指南）

概念

C# 编程指南

参考

类和结构（C# 编程指南）
抽象类、密封类及类成员（C# 编程指南）
属性（C# 编程指南）


静态类和静态类成员（C# 编程指南）

[image]

C# 编程指南
静态类和静态类成员（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

静态类与非静态类基本相同，但存在一个区别：静态类不能实例化。也就是说，不能使用 new 关键字创建静态类类型的变量。因为没有实例变量，所以要使用类名本身访问静态类的成员。例如，如果名为 UtilityClass 的静态类有一个名为 MethodA 的公共方法，则按下面的示例所示调用该方法：

C# 复制代码
UtilityClass.MethodA();

和所有类类型一样，当加载引用静态类的程序时，.NET Framework 公共语言运行时 (CLR) 将加载该静态类的类型信息。程序不能指定加载静态类的确切时间。但是，可以保证在程序中首次引用该类前加载该类，并初始化该类的字段并调用其静态构造函数。静态构造函数仅调用一次，在程序驻留的应用程序域的生存期内，静态类一直保留在内存中。

对于只对输入参数进行运算而不获取或设置任何内部实例字段的方法集，静态类可以方便地用作这些方法集的容器。例如，在 .NET Framework 类库中，静态类 System..::.Math 包含的一些方法只执行数学运算，而无需存储或检索特定 Math 类实例特有的数据。

说明：

若要创建仅允许创建一个自身实例的非静态类，请参见 Implementing Singleton in C#（在 C# 中实现单一实例）。

下表介绍静态类的主要特性：

  • 仅包含静态成员。

  • 无法实例化。

  • 是密封的。

  • 不能包含实例构造函数。

因此，创建静态类与创建仅包含静态成员和私有构造函数的类基本相同。私有构造函数阻止类被实例化。使用静态类的优点在于，编译器能够执行检查以确保不致偶然地添加实例成员。编译器将保证不会创建此类的实利。

静态类是密封的，因此不可被继承。它们不能从除 Object 外的任何类中继承。静态类不能包含实例构造函数，但可以包含静态构造函数。如果非静态类包含需要进行重要的初始化的静态成员，也应定义静态构造函数。有关更多信息，请参见静态构造函数（C# 编程指南）。

示例

下面是一个静态类的示例，它包含两个在摄氏温度和华氏温度之间执行来回转换的方法：

C# 复制代码
public static class TemperatureConverter
{
    public static double CelsiusToFahrenheit(string temperatureCelsius)
    {
        // Convert argument to double for calculations.
        double celsius = Double.Parse(temperatureCelsius);

        // Convert Celsius to Fahrenheit.
        double fahrenheit = (celsius * 9 / 5) + 32;

        return fahrenheit;
    }

    public static double FahrenheitToCelsius(string temperatureFahrenheit)
    {
        // Convert argument to double for calculations.
        double fahrenheit = Double.Parse(temperatureFahrenheit);

        // Convert Fahrenheit to Celsius.
        double celsius = (fahrenheit - 32) * 5 / 9;

        return celsius;
    }
}

class TestTemperatureConverter
{
    static void Main()
    {
        Console.WriteLine("Please select the convertor direction");
        Console.WriteLine("1. From Celsius to Fahrenheit.");
        Console.WriteLine("2. From Fahrenheit to Celsius.");
        Console.Write(":");

        string selection = Console.ReadLine();
        double F, C = 0;

        switch (selection)
        {
            case "1":
                Console.Write("Please enter the Celsius temperature: ");
                F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine());
                Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
                break;

            case "2":
                Console.Write("Please enter the Fahrenheit temperature: ");
                C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine());
                Console.WriteLine("Temperature in Celsius: {0:F2}", C);
                break;

            default:
                Console.WriteLine("Please select a convertor.");
                break;
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Example Output:
    Please select the convertor direction
    1. From Celsius to Fahrenheit.
    2. From Fahrenheit to Celsius.
    :2
    Please enter the Fahrenheit temperature: 20
    Temperature in Celsius: -6.67
    Press any key to exit.
 */

静态成员

非静态类可以包含静态的方法、字段、属性或事件。即使没有创建类的实例，也可以调用该类中的静态成员。始终通过类名而不是实例名称访问静态成员。无论对一个类创建多少个实例，它的静态成员都只有一个副本。静态方法和属性不能访问其包含类型中的非静态字段和事件，并且不能访问任何对象的实例变量（除非在方法参数中显式传递）。

更常见的做法是声明具有一些静态成员的非静态类，而不是将整个类声明为静态类。静态字段有两个常见的用法：一是记录已实例化对象的个数，二是存储必须在所有实例之间共享的值。

静态方法可以被重载但不能被重写，因为它们属于类，不属于类的任何实例。

虽然字段不能声明为 static const，但 const 字段的行为在本质上是静态的。这样的字段属于类型，不属于类型的实例。因此，可以同对待静态字段一样使用 ClassName.MemberName 表示法来访问 const 字段。不需要对象实例。

C# 不支持静态局部变量（在方法范围内声明的变量）。

通过在成员的返回类型之前使用 static 关键字可以声明静态类成员，如下面的示例所示：

C# 复制代码
public class Automobile
{
    public static int NumberOfWheels = 4;
    public static int SizeOfGasTank
    {
        get
        {
            return 15;
        }
    }
    public static void Drive() { }
    public static event EventType RunOutOfGas;

    // Other non-static fields and properties...
}

静态成员在第一次被访问之前并且在调用静态构造函数（如有存在）之前进行初始化。若要访问静态类成员，应使用类名而不是变量名来指定该成员的位置，如下面的示例所示：

C# 复制代码
Automobile.Drive();
int i = Automobile.NumberOfWheels;

如果类包含静态字段，请提供在加载类时初始化这些字段的静态构造函数。

对静态方法的调用以 Microsoft 中间语言 (MSIL) 生成调用指令，而对实例方法的调用生成 callvirt 指令，该指令还检查 null 对象引用。但是，两者之间的性能差异在大多数时候并不明显。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.1.1.3 静态类

请参见

概念

C# 编程指南
静态类设计

参考

类（C# 编程指南）
class（C# 参考）
静态构造函数（C# 编程指南）
实例构造函数（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增加了内容和代码示例 | 信息补充。


成员（C# 编程指南）

[image]

C# 编程指南
成员（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

类和结构具有表示其数据和行为的成员。类的成员包括在类中声明的所有成员，以及在该类的继承层次结构中的所有类中声明的所有成员（构造函数和析构函数除外）。基类中的私有成员被继承，但不能从派生类访问。

下表列出类或结构中可包含的成员类型：

成员 | 说明
字段（C# 编程指南） | 字段是在类范围声明的变量。字段可以是内置数值类型或其他类的实例。例如，日历类可能具有一个包含当前日期的字段。
常量（C# 编程指南） | 常量是在编译时设置其值并且不能更改其值的字段或属性。
属性（C# 编程指南） | 属性是类中可以像类中的字段一样访问的方法。属性可以为类字段提供保护，以避免字段在对象不知道的情况下被更改。
方法（C# 编程指南） | 方法定义类可以执行的操作。方法可以接受提供输入数据的参数，并且可以通过参数返回输出数据。方法还可以不使用参数而直接返回值。
事件（C# 编程指南） | 事件向其他对象提供有关发生的事情（如单击按钮或成功完成某个方法）的通知。事件是使用委托定义和触发的。有关更多信息，请参见事件和委托。
运算符（C# 编程指南） | 重载运算符被视为类成员。在重载运算符时，在类中将该运算符定义为公共静态方法。预定义运算符（+、*、< 等）不考虑作为成员。有关更多信息，请参见可重载运算符（C# 编程指南）。
索引器（C# 编程指南） | 使用索引器可以用类似于数组的方式为对象建立索引。
构造函数（C# 编程指南） | 构造函数是在第一次创建对象时调用的方法。它们通常用于初始化对象的数据。
析构函数（C# 编程指南） | C# 中极少使用析构函数。析构函数是当对象即将从内存中移除时由运行时执行引擎调用的方法。它们通常用来确保任何必须释放的资源都得到适当的处理。
嵌套类型（C# 编程指南） | 嵌套类型是在其他类型中声明的类型，通常用于描述仅由包含它们的类型所使用的对象。

请参见

概念

C# 编程指南
事件和委托

参考

类（C# 编程指南）
方法（C# 编程指南）
构造函数（C# 编程指南）
析构函数（C# 编程指南）
属性（C# 编程指南）
字段（C# 编程指南）
索引器（C# 编程指南）
事件（C# 编程指南）
嵌套类型（C# 编程指南）
运算符（C# 编程指南）
可重载运算符（C# 编程指南）

其他资源

成员设计准则


访问修饰符（C# 编程指南）

[image]

C# 编程指南
访问修饰符（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

所有类型和类型成员都具有可访问性级别，用来控制是否可以在您程序集的其他代码中或其他程序集中使用它们。您在声明类型或成员时使用以下访问修饰符之一来指定其可访问性：

public

同一程序集中的任何其他代码或引用该程序集的其他程序集都可以访问该类型或成员。

private

只有同一类或结构中的代码可以访问该类型或成员。

protected

只有同一类或结构或者派生类中的代码可以访问该类型或成员。

internal

同一程序集中的任何代码都可以访问该类型或成员，但其他程序集中的代码不可以。

protected internal

同一程序集中的任何代码或其他程序集中的任何派生类都可以访问该类型或成员。

下面的示例演示如何为类型和成员指定访问修饰符：

C# 复制代码
public class Bicycle
{
    public void Pedal() { }
}

不是所有访问修饰符都可以在所有上下文中由所有类型或成员使用，在某些情况下类型成员的可访问性受到其包含类型的可访问性的限制。以下各节提供了有关可访问性的更多详细信息。

类和结构的可访问性

直接在命名空间中声明的类和结构（即，没有嵌套在其他类或结构中的类和结构）可以是公共类和结构，也可以是内部类和结构。如果不指定访问修饰符，则默认为 internal。嵌套的类和结构还可以声明为私有类和结构。不可以从包含类型访问私有嵌套类型。

派生类的可访问性不能高于其基类型。换句话说，不能有从内部类 A 派生的公共类 B。如果允许这种情况，将会使 A 成为公共类，因为 A 的所有受保护的成员或内部成员都可以从派生类访问。

可以使用 InternalsVisibleToAttribute 使其他某些程序集能够访问您的内部类型。有关更多信息，请参见友元程序集（C# 编程指南）。

类成员和结构成员的可访问性

可以使用五种访问类型中的任何一种来声明类成员（包括嵌套的类和结构）。结构成员无法声明为受保护成员，因为结构不支持继承。

成员的可访问性决不能高于其包含类型的可访问性。例如，在内部类型中声明的公共方法只具有内部可访问性。

如果类或结构的成员为属性、字段、方法、事件或委托，并且该成员是某个类型或具有参数或返回值类型，则该成员的可访问性不能超过该类型。例如，如果 C 不是公共类，则不能返回类 C 的公共方法 M。同样，如果 A 声明为私有，则类型 A 不能有受保护的属性。

用户定义的运算符必须始终声明为公共运算符。有关更多信息，请参见 operator（C# 参考）。

析构函数不能具有可访问性修饰符。

若要设置类成员或结构成员的访问级别，请向该成员声明添加适当的关键字。下面是一些示例：

C# 复制代码
// public class:
public class Tricycle
{
    // protected method:
    protected void Pedal() { }

    // private field:
    private int wheels = 3;

    // protected internal property:
    protected internal int Wheels
    {
        get { return wheels; }
    }
}
说明：

protected internal 可访问性的意思是受保护“或”内部，而不是受保护“和”内部。换句话说，可以从同一程序集内的任何类（包括派生类）中访问 protected internal 成员。若要限制为只有同一程序集内的派生类可以访问，请将类本身声明为内部，并将其成员声明为受保护。

其他类型

直接用命名空间声明时，可以将接口声明为公共接口或内部接口，并且与类和结构一样，接口默认具有内部可访问性。接口成员始终是公共成员，因为接口的用途是让其他类型能够访问某个类或结构。访问修饰符不能应用于接口成员。

枚举成员始终是公共的，不能应用任何访问修饰符。

默认情况下，委托默认具有内部访问级别。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.5 成员访问

  • 10.3.5 访问修饰符

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
接口（C# 编程指南）
private（C# 参考）
public（C# 参考）
internal（C# 参考）
protected（C# 参考）
class（C# 参考）
struct（C# 参考）
接口（C# 参考）


字段（C# 编程指南）

[image]

C# 编程指南
字段（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

“字段”是直接在类或结构中声明的任何类型的变量。字段是其包含类型的“成员”。

类或结构可以拥有实例字段或静态字段，或同时拥有两者。实例字段特定于类型的实例。如果您拥有类 T 和实例字段 F，可以创建类型 T 的两个对象，并修改每个对象中 F 的值，这不影响另一对象中的该值。相比之下，静态字段属于类本身，在该类的所有实例中共享。从实例 A 所做的更改将立刻呈现在实例 B 和 C 上（如果它们访问该字段）。

通常应仅为具有私有或受保护可访问性的变量使用字段。您的类向客户端代码公开的数据应通过方法、属性和索引器提供。通过使用这些构造间接访问内部字段，可以针对无效的输入值提供防护。存储由公共属性公开的数据的私有字段称为“后备存储”或“支持字段”。

字段通常存储这样的数据：该数据必须可供多个类方法访问，并且其存储期必须长于任何单个方法的生存期。例如，表示日历日期的类可能有三个整数字段：一个表示月份，一个表示日期，还有一个表示年份。不在单个方法范围外部使用的变量应在方法体自身范围内声明为局部变量。

在类块中通过指定字段的访问级别，然后指定字段的类型，再指定字段的名称来声明这些字段。例如：

C# 复制代码
public class CalendarEntry
{
    // private field
    private DateTime date;

    // public field (Generally not recommended)
    public string day;

    // Public property exposes date field safely.
    public DateTime Date
    {
        get
        {
            return date;
        }
        set
        {
            if(value.Year > 1980 || value.Year <= 2008)
            {
                date = value;
            }
            else
                throw new ArgumentOutOfRangeException();
        }

    }

    // Public method also exposes date field safely.
    public void SetDate(string dateString)
    {
        DateTime dt = Convert.ToDateTime(dateString);

        if (dt.Year > 1980 || dt.Year <= 2008)
        {
            date = dt;
        }
        else
            throw new ArgumentOutOfRangeException();
    }

    public TimeSpan GetTimeSpan(string dateString)
    {
        DateTime dt = Convert.ToDateTime(dateString);

        if (dt != null && dt.Ticks < date.Ticks)
        {
            return date - dt;
        }
        else
            throw new ArgumentOutOfRangeException();

    }
}

若要访问对象中的字段，请在对象名称后面添加一个句点，然后添加该字段的名称，比如 objectname.fieldname。例如：

C# 复制代码
CalendarEntry birthday = new CalendarEntry();
birthday.day = "Saturday";

声明字段时可以使用赋值运算符为字段指定一个初始值。例如，若要自动将 "Monday" 赋给 day 字段，需要声明 day，如下例所示：

C# 复制代码
public class CalendarDateWithInitialization
{
    public string day = "Monday";
    //...
}

字段的初始化紧靠调用对象实例的构造函数之前。如果构造函数为字段赋值，则该值将覆盖字段声明期间给出的任何值。有关更多信息，请参见使用构造函数（C# 编程指南）。

说明：

字段初始值设定项不能引用其他实例字段。

字段可标记为 public、private、protected、internal 或 protected internal。这些访问修饰符定义类的使用者访问字段的方式。有关更多信息，请参见访问修饰符（C# 编程指南）。

可以选择将字段声明为 static。这使得调用方在任何时候都能使用字段，即使类没有任何实例。有关更多信息，请参见静态类和静态类成员（C# 编程指南）。

可以将字段声明为 readonly。只读字段只能在初始化期间或在构造函数中赋值。static readonly 字段非常类似于常数，只不过 C# 编译器不能在编译时访问静态只读字段的值，而只能在运行时访问。有关更多信息，请参见常量（C# 编程指南）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.5 字段

  • 10.5 字段

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
使用构造函数（C# 编程指南）
继承（C# 编程指南）
访问修饰符（C# 编程指南）
抽象类、密封类及类成员（C# 编程指南）


常量（C# 编程指南）

[image]

C# 编程指南
常量（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

常量是在编译时已知并在程序的生存期内不发生更改的不可变值。常量使用 const 修饰符进行声明。只有 C# 内置类型（System..::.Object 除外）可以声明为 const。有关内置类型的列表，请参见内置类型表（C# 参考）。用户定义的类型（包括类、结构和数组）不能为 const。请使用 readonly 修饰符创建在运行时初始化一次且不可再更改的类、结构或数组。

C# 不支持 const 方法、属性或事件。

可以使用枚举类型为整数内置类型（例如 int、uint、long 等等）定义命名常量。有关更多信息，请参见 enum（C# 参考）。

常量必须在声明时初始化。例如：

C# 复制代码
class Calendar1
{
    public const int months = 12;
}

在此示例中，常量 months 始终为 12，不可更改，即使是该类自身也不能更改它。实际上，当编译器遇到 C# 源代码（例如 months）中的常量修饰符时，将直接把文本值替换到它生成的中间语言 (IL) 代码中。因为在运行时没有与常量关联的变量地址，所以 const 字段不能通过引用传递，并且不能在表达式中作为左值出现。

说明：

当引用在其他代码如 DLL 中定义的常量值时应十分谨慎。如果新版本的 DLL 为常量定义了新的值，程序仍将保留旧的文本值，直到针对新版本重新编译程序。

可以同时声明多个相同类型的常量，例如：

C# 复制代码
class Calendar2
{
    const int months = 12, weeks = 52, days = 365;
}

如果不会造成循环引用，用于初始化一个常量的表达式可以引用另一个常量。例如：

C# 复制代码
class Calendar3
{
    const int months = 12;
    const int weeks = 52;
    const int days = 365;

    const double daysPerWeek = (double) days / (double) weeks;
    const double daysPerMonth = (double) days / (double) months;
}

常量可标记为 public、private、protected、internal 或 protected internal。这些访问修饰符定义类的用户访问该常量的方式。有关更多信息，请参见访问修饰符（C# 编程指南）。

因为常量值对该类型的所有实例是相同的，所以常量被当作 static 字段一样访问。不使用 static 关键字声明常量。未包含在定义常量的类中的表达式必须使用类名、一个句点和常量名来访问该常量。例如：

C# 复制代码
int birthstones = Calendar.months;

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.4 常量

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
属性（C# 编程指南）
类型（C# 编程指南）
readonly（C# 参考）

其他资源

Immutability in C# Part One: Kinds of Immutability（C# 中的不变性第一部分：不变性的种类）


如何：在 C# 中定义常量

[image]

C# 编程指南
如何：在 C# 中定义常量
示例 另请参见 发送反馈意见

更新：2007 年 11 月

常量是在编译时设置其值并且永远不能更改其值的字段。使用常量可以为特殊值提供有意义的名称以代替数字文本（“幻数”）。

说明：

在 C# 中，不能使用 #define 预处理器指令定义常量，而这是 C 和 C++ 中通常采用的方式。

若要定义整数类型（int、byte 等）的常量值，请使用枚举类型。有关更多信息，请参见 enum（C# 参考）。

若要定义非整型常量，一种方法是将它们分组到单个名为 Constants 的静态类中。这要求对常量的所有引用都使用该类名作为前缀，如下面的示例所示。

示例

C# 复制代码
static class Constants
{
    public const double Pi = 3.14159;
    public const int SpeedOfLight = 300000; // km per sec.

}
class Program
{
    static void Main()
    {
        double radius = 5.3;
        double area = Constants.Pi * (radius * radius);
        int secsFromSun = 149476000 / Constants.SpeedOfLight; // in km
    }
}

使用类名限定符有助于确保您和使用常量的其他人了解到它是常量并且不能修改。

请参见

参考

类和结构（C# 编程指南）


属性（C# 编程指南）

[image]

C# 编程指南
属性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

属性是这样的成员：它们提供灵活的机制来读取、编写或计算私有字段的值。可以像使用公共数据成员一样使用属性，但实际上它们是称作“访问器”的特殊方法。这使得可以轻松访问数据，此外还有助于提高方法的安全性和灵活性。

在本示例中，TimePeriod 类存储一个时间段。在内部，类以秒为单位存储时间，但客户端使用名为 Hours 的属性能够以小时为单位指定时间。Hours 属性的访问器执行小时和秒之间的转换。

示例

C# 复制代码
class TimePeriod
{
    private double seconds;

    public double Hours
    {
        get { return seconds / 3600; }
        set { seconds = value * 3600; }
    }
}

class Program
{
    static void Main()
    {
        TimePeriod t = new TimePeriod();

        // Assigning the Hours property causes the 'set' accessor to be called.
        t.Hours = 24;

        // Evaluating the Hours property causes the 'get' accessor to be called.
        System.Console.WriteLine("Time in hours: " + t.Hours);
    }
}
// Output: Time in hours: 24

属性概述

  • 属性使类能够以一种公开的方法获取和设置值，同时隐藏实现或验证代码。

  • get 属性访问器用于返回属性值，而 set 访问器用于分配新值。这些访问器可以有不同的访问级别。有关更多信息，请参见非对称访问器可访问性（C# 编程指南）。

  • value 关键字用于定义由 set 索引器分配的值。

  • 不实现 set 方法的属性是只读的。

  • 对于不需要任何自定义访问器代码的简单属性，可考虑选择使用自动实现的属性。有关更多信息，请参见自动实现的属性（C# 编程指南）。

相关章节

  • 使用属性（C# 编程指南）

  • 接口属性（C# 编程指南）

  • 属性和索引器之间的比较（C# 编程指南）

  • 非对称访问器可访问性（C# 编程指南）

  • 自动实现的属性（C# 编程指南）

  • “属性”示例

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.2 属性

  • 10.2.9.1 为属性保留的成员名称

  • 10.7 属性

请参见

概念

C# 编程指南
属性设计

参考

使用属性（C# 编程指南）
索引器（C# 编程指南）


使用属性（C# 编程指南）

[image]

C# 编程指南
使用属性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

属性结合了字段和方法的多个方面。对于对象的用户，属性显示为字段，访问该属性需要相同的语法。对于类的实现者，属性是一个或两个代码块，表示一个 get 访问器和/或一个 set 访问器。当读取属性时，执行 get 访问器的代码块；当向属性分配一个新值时，执行 set 访问器的代码块。不具有 set 访问器的属性被视为只读属性。不具有 get 访问器的属性被视为只写属性。同时具有这两个访问器的属性是读写属性。

与字段不同，属性不作为变量来分类。因此，不能将属性作为 ref（C# 参考）参数或 out（C# 参考）参数传递。

属性具有多种用法：它们可在允许更改前验证数据；它们可透明地公开某个类上的数据，该类的数据实际上是从其他源（例如数据库）检索到的；当数据被更改时，它们可采取行动，例如引发事件或更改其他字段的值。

属性在类块中是按以下方式来声明的：指定字段的访问级别，接下来指定属性的类型和名称，然后跟上声明 get 访问器和/或 set 访问器的代码块。例如：

C# 复制代码
public class Date
{
    private int month = 7;  // Backing store

    public int Month
    {
        get
        {
            return month;
        }
        set
        {
            if ((value > 0) && (value < 13))
            {
                month = value;
            }
        }
    }
}

在此示例中，Month 是作为属性声明的，这样 set 访问器可确保 Month 值设置为 1 和 12 之间。Month 属性使用私有字段来跟踪实际值。属性的数据的真实位置经常称为属性的“后备存储”。属性使用作为后备存储的私有字段是很常见的。将字段标记为私有可确保该字段只能通过调用属性来更改。有关公共和私有访问限制的更多信息，请参见访问修饰符（C# 编程指南）。

自动实现的属性提供用于简单属性声明的简化语法。有关更多信息，请参见自动实现的属性（C# 编程指南）。

get 访问器

get 访问器体与方法体相似。它必须返回属性类型的值。执行 get 访问器相当于读取字段的值。例如，当正在从 get 访问器返回私有变量并且启用了优化时，对 get 访问器方法的调用由编译器进行内联，因此不存在方法调用的系统开销。然而，由于在编译时编译器不知道在运行时实际调用哪个方法，因此无法内联虚拟 get 访问器。以下是返回私有字段 name 的值的 get 访问器：

C# 复制代码
class Person
{
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
    }
}

当引用属性时，除非该属性为赋值目标，否则将调用 get 访问器以读取该属性的值。例如：

C# 复制代码
Person person = new Person();
//...

System.Console.Write(person.Name);  // the get accessor is invoked here

get 访问器必须以 return 或 throw 语句终止，并且控制权不能离开访问器体。

通过使用 get 访问器更改对象的状态不是一种好的编程风格。例如，以下访问器在每次访问 number 字段时都会产生更改对象状态的副作用。

C# 复制代码
private int number;
public int Number
{
    get
    {
        return number++;   // Don't do this
    }
}

get 访问器可用于返回字段值，或用于计算并返回字段值。例如：

C# 复制代码
class Employee
{
    private string name;
    public string Name
    {
        get
        {
            return name != null ? name : "NA";
        }
    }
}

在上一个代码段中，如果不对 Name 属性赋值，它将返回值 NA。

set 访问器

set 访问器类似于返回类型为 void 的方法。它使用称为 value 的隐式参数，此参数的类型是属性的类型。在下面的示例中，将 set 访问器添加到 Name 属性：

C# 复制代码
class Person
{
    private string name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}

当对属性赋值时，用提供新值的参数调用 set 访问器。例如：

C# 复制代码
Person person = new Person();
person.Name = "Joe";  // the set accessor is invoked here

System.Console.Write(person.Name);  // the get accessor is invoked here

在 set 访问器中，对局部变量声明使用隐式参数名称 value 是错误的。

备注

可将属性标记为 public、private、protected、internal 或 protected internal。这些访问修饰符定义类的用户如何才能访问属性。同一属性的 get 和 set 访问器可能具有不同的访问修饰符。例如，get 可能是 public 以允许来自类型外的只读访问；set 可能是 private 或 protected。有关更多信息，请参见访问修饰符（C# 编程指南）。

可以使用 static 关键字将属性声明为静态属性。这使得调用方随时可使用该属性，即使不存在类的实例。有关更多信息，请参见静态类和静态类成员（C# 编程指南）。

可以使用 virtual 关键字将属性标记为虚属性。这样，派生类就可以通过使用 override 关键字来重写事件行为。有关这些选项的更多信息，请参见继承（C# 编程指南）。

重写虚属性的属性还可以是 sealed 的，这表示它对派生类不再是虚拟的。最后，可以将属性声明为 abstract。这意味着类中没有任何实现，派生类必须编写自己的实现。有关这些选项的更多信息，请参见 抽象类、密封类及类成员（C# 编程指南）。

说明：

对 static 属性的访问器使用 virtual（C# 参考）、abstract（C# 参考）或 override（C# 参考）修饰符是错误的。

示例

此例说明了实例、静态和只读属性。它从键盘接受雇员的姓名，按 1 递增 NumberOfEmployees，并显示雇员的姓名和编号。

C# 复制代码
public class Employee
{
    public static int NumberOfEmployees;
    private static int counter;
    private string name;

    // A read-write instance property:
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    // A read-only static property:
    public static int Counter
    {
        get { return counter; }
    }

    // A Constructor:
    public Employee()
    {
        // Calculate the employee's number:
        counter = ++counter + NumberOfEmployees;
    }
}

class TestEmployee
{
    static void Main()
    {
        Employee.NumberOfEmployees = 107;
        Employee e1 = new Employee();
        e1.Name = "Claude Vige";

        System.Console.WriteLine("Employee number: {0}", Employee.Counter);
        System.Console.WriteLine("Employee name: {0}", e1.Name);
    }
}
/* Output:
    Employee number: 108
    Employee name: Claude Vige
*/

此示例说明如何访问基类中由派生类中具有同一名称的另一个属性所隐藏的属性。

C# 复制代码
public class Employee
{
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

public class Manager : Employee
{
    private string name;

    // Notice the use of the new modifier:
    public new string Name
    {
        get { return name; }
        set { name = value + ", Manager"; }
    }
}

class TestHiding
{
    static void Main()
    {
        Manager m1 = new Manager();

        // Derived class property.
        m1.Name = "John";

        // Base class property.
        ((Employee)m1).Name = "Mary";

        System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);
        System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
    }
}
/* Output:
    Name in the derived class is: John, Manager
    Name in the base class is: Mary
*/

以下是上一个示例中的要点：

  • 派生类中的属性 Name 隐藏基类中的属性 Name。在这种情况下，派生类的属性声明中使用 new 修饰符：

    C# 复制代码
    public new string Name

  • 转换 (Employee) 用于访问基类中的隐藏属性：

    C# 复制代码
    ((Employee)m1).Name = "Mary";

    有关隐藏成员的更多信息，请参见 new 修饰符（C# 参考）。

在此例中，Cube 和 Square 这两个类实现抽象类 Shape，并重写它的抽象 Area 属性。注意属性上 override 修饰符的使用。程序接受输入的边长并计算正方形和立方体的面积。它还接受输入的面积并计算正方形和立方体的相应边长。

C# 复制代码
abstract class Shape
{
    public abstract double Area
    {
        get;
        set;
    }
}

class Square : Shape
{
    public double side;

    public Square(double s)  //constructor
    {
        side = s;
    }

    public override double Area
    {
        get
        {
            return side * side;
        }
        set
        {
            side = System.Math.Sqrt(value);
        }
    }
}

class Cube : Shape
{
    public double side;

    public Cube(double s)
    {
        side = s;
    }

    public override double Area
    {
        get
        {
            return 6 * side * side;
        }
        set
        {
            side = System.Math.Sqrt(value / 6);
        }
    }
}

class TestShapes
{
    static void Main()
    {
        // Input the side:
        System.Console.Write("Enter the side: ");
        double side = double.Parse(System.Console.ReadLine());

        // Compute the areas:
        Square s = new Square(side);
        Cube c = new Cube(side);

        // Display the results:
        System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
        System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
        System.Console.WriteLine();

        // Input the area:
        System.Console.Write("Enter the area: ");
        double area = double.Parse(System.Console.ReadLine());

        // Compute the sides:
        s.Area = area;
        c.Area = area;

        // Display the results:
        System.Console.WriteLine("Side of the square = {0:F2}", s.side);
        System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
    }
}
/* Example Output:
    Enter the side: 4
    Area of the square = 16.00
    Area of the cube = 96.00

    Enter the area: 24
    Side of the square = 4.90
    Side of the cube = 2.00
*/

请参见

任务

“属性”示例

概念

C# 编程指南

参考

属性（C# 编程指南）
接口属性（C# 编程指南）
自动实现的属性（C# 编程指南）


接口属性（C# 编程指南）

[image]

C# 编程指南
接口属性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可以在接口（C# 参考）上声明属性。以下是接口索引器访问器的示例：

C# 复制代码
public interface ISampleInterface
{
    // Property declaration:
    string Name
    {
        get;
        set;
    }
}

接口属性的访问器不具有体。因此，访问器的用途是指示属性是否为读写、只读或只写。

示例

在此例中，接口 IEmployee 具有读写属性 Name 和只读属性 Counter。Employee 类实现 IEmployee 接口并使用这两种属性。程序读取新雇员的姓名和雇员的当前编号，并显示雇员姓名和计算所得的雇员编号。

可以使用属性的完全限定名，它引用声明成员的接口。例如：

C# 复制代码
string IEmployee.Name
{
    get { return "Employee Name"; }
    set { }
}

这称为显式接口实现（C# 编程指南）。例如，如果 Employee 类实现两个接口 ICitizen 和 IEmployee，并且两个接口都具有 Name 属性，则需要显式接口成员实现。即，如下属性声明：

C# 复制代码
string IEmployee.Name
{
    get { return "Employee Name"; }
    set { }
}

在 IEmployee 接口上实现 Name 属性，而下面的声明：

C# 复制代码
string ICitizen.Name
{
    get { return "Citizen Name"; }
    set { }
}

在 ICitizen 接口上实现 Name 属性。

C# 复制代码
interface IEmployee
{
    string Name
    {
        get;
        set;
    }

    int Counter
    {
        get;
    }
}

public class Employee : IEmployee
{
    public static int numberOfEmployees;

    private string name;
    public string Name  // read-write instance property
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    private int counter;
    public int Counter  // read-only instance property
    {
        get
        {
            return counter;
        }
    }

    public Employee()  // constructor
    {
        counter = ++counter + numberOfEmployees;
    }
}

class TestEmployee
{
    static void Main()
    {
        System.Console.Write("Enter number of employees: ");
        Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());

        Employee e1 = new Employee();
        System.Console.Write("Enter the name of the new employee: ");
        e1.Name = System.Console.ReadLine();

        System.Console.WriteLine("The employee information:");
        System.Console.WriteLine("Employee number: {0}", e1.Counter);
        System.Console.WriteLine("Employee name: {0}", e1.Name);
    }
}
复制代码
210
Hazem Abolrous

示例输出

Enter number of employees: 210

Enter the name of the new employee: Hazem Abolrous

The employee information:

Employee number: 211

Employee name: Hazem Abolrous

请参见

概念

C# 编程指南

参考

属性（C# 编程指南）
使用属性（C# 编程指南）
属性和索引器之间的比较（C# 编程指南）
索引器（C# 编程指南）
接口（C# 编程指南）


非对称访问器可访问性（C# 编程指南）

[image]

C# 编程指南
非对称访问器可访问性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

属性或索引器的 get 和 set 部分称为“访问器”。默认情况下，这些访问器具有相同的可见性或访问级别：其所属属性或索引器的可见性或访问级别。有关更多信息，请参见可访问性级别。不过，有时限制对其中某个访问器的访问会很有用。通常是在保持 get 访问器可公开访问的情况下，限制 set 访问器的可访问性。例如：

C# 复制代码
private string name = "Hello";

public string Name
{
    get
    {
        return name;
    }
    protected set
    {
        name = value;
    }
}

在此示例中，名为 Name 的属性定义了一个 get 访问器和一个 set 访问器。get 访问器接受该属性本身的可访问性级别（在此示例中为 public），而对于 set 访问器，则通过对该访问器本身应用 protected 访问修饰符来进行显式限制。

对访问器的访问修饰符的限制

对属性或索引器使用访问修饰符受以下条件的制约：

  • 不能对接口或显式接口成员实现使用访问器修饰符。

  • 仅当属性或索引器同时具有 set 和 get 访问器时，才能使用访问器修饰符。这种情况下，只允许对其中一个访问器使用修饰符。

  • 如果属性或索引器具有 override 修饰符，则访问器修饰符必须与重写的访问器的访问器（如果有的话）匹配。

  • 访问器的可访问性级别必须比属性或索引器本身的可访问性级别具有更严格的限制。

重写访问器的访问修饰符

在重写属性或索引器时，被重写的访问器对重写代码而言，必须是可访问的。此外，属性/索引器和访问器的可访问性级别都必须与相应的被重写属性/索引器和访问器匹配。例如：

C# 复制代码
public class Parent
{
    public virtual int TestProperty
    {
        // Notice the accessor accessibility level.
        protected set { }

        // No access modifier is used here.
        get { return 0; }
    }
}
public class Kid : Parent
{
    public override int TestProperty
    {
        // Use the same accessibility level as in the overridden accessor.
        protected set { }

        // Cannot use access modifier here.
        get { return 0; }
    }
}

实现接口

使用访问器实现接口时，访问器不能具有访问修饰符。但是，如果使用一个访问器（如 get）实现接口，则另一个访问器可以具有访问修饰符，如下面的示例所示：

C# 复制代码
public interface ISomeInterface
{
    int TestProperty
    {
        // No access modifier allowed here
        // because this is an interface.
        get;
    }
}

public class TestClass : ISomeInterface
{
    public int TestProperty
    {
        // Cannot use access modifier here because
        // this is an interface implementation.
        get { return 10; }

        // Interface property does not have set accessor,
        // so access modifier is allowed.
        protected set { }
    }
}

访问器可访问性域

如果对访问器使用访问某个修饰符，则访问器的可访问性域由该修饰符确定。

如果不对访问器使用访问修饰符，则访问器的可访问性域由属性或索引器的可访问性级别确定。

示例

下面的示例包含三个类：BaseClass、DerivedClass 和 MainClass。每个类的 BaseClass、Name 和 Id 都有两个属性。该示例演示在使用限制性访问修饰符（如 protected 或 private）时，如何通过 BaseClass 的 Id 属性隐藏 DerivedClass 的 Id 属性。因此，向该属性赋值时，将调用 BaseClass 类中的属性。将访问修饰符替换为 public 将使该属性可访问。

该示例还演示 DerivedClass 的 Name 属性的 set 访问器上的限制性访问修饰符（如 private 或 protected）如何防止对该访问器的访问，并在向它赋值时生成错误。

C# 复制代码
public class BaseClass
{
    private string name = "Name-BaseClass";
    private string id = "ID-BaseClass";

    public string Name
    {
        get { return name; }
        set { }
    }

    public string Id
    {
        get { return id; }
        set { }
    }
}

public class DerivedClass : BaseClass
{
    private string name = "Name-DerivedClass";
    private string id = "ID-DerivedClass";

    new public string Name
    {
        get
        {
            return name;
        }

        // Using "protected" would make the set accessor not accessible.
        set
        {
            name = value;
        }
    }

    // Using private on the following property hides it in the Main Class.
    // Any assignment to the property will use Id in BaseClass.
    new private string Id
    {
        get
        {
            return id;
        }
        set
        {
            id = value;
        }
    }
}

class MainClass
{
    static void Main()
    {
        BaseClass b1 = new BaseClass();
        DerivedClass d1 = new DerivedClass();

        b1.Name = "Mary";
        d1.Name = "John";

        b1.Id = "Mary123";
        d1.Id = "John123";  // The BaseClass.Id property is called.

        System.Console.WriteLine("Base: {0}, {1}", b1.Name, b1.Id);
        System.Console.WriteLine("Derived: {0}, {1}", d1.Name, d1.Id);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    Base: Name-BaseClass, ID-BaseClass
    Derived: John, ID-BaseClass
*/

注释

注意，如果将 new private string Id 替换为 new public string Id，则得到如下输出：

Name and ID in the base class: Name-BaseClass, ID-BaseClass

Name and ID in the derived class: John, John123

请参见

概念

C# 编程指南

参考

属性（C# 编程指南）
索引器（C# 编程指南）
访问修饰符（C# 编程指南）


如何：声明和使用读/写属性（C# 编程指南）

[image]

C# 编程指南
如何：声明和使用读/写属性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

属性可以提供公共数据成员的便利，而又不会带来不受保护、不受控制以及未经验证访问对象数据的风险。这是通过“访问器”来实现的：访问器是为基础数据成员赋值和检索其值的特殊方法。使用 set 访问器可以为数据成员赋值，使用 get 访问器可以检索数据成员的值。

此示例演示 Person 类，该类具有两个属性：Name (string) 和 Age (int)。这两个属性都提供 get 和 set 访问器，因此它们被视为读/写属性。

示例

C# 复制代码
class Person
{
    private string name = "N/A";
    private int age = 0;

    // Declare a Name property of type string:
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    // Declare an Age property of type int:
    public int Age
    {
        get
        {
            return age;
        }

        set
        {
            age = value;
        }
    }

    public override string ToString()
    {
        return "Name = " + Name + ", Age = " + Age;
    }
}

class TestPerson
{
    static void Main()
    {
        // Create a new Person object:
        Person person = new Person();

        // Print out the name and the age associated with the person:
        Console.WriteLine("Person details - {0}", person);

        // Set some values on the person object:
        person.Name = "Joe";
        person.Age = 99;
        Console.WriteLine("Person details - {0}", person);

        // Increment the Age property:
        person.Age += 1;
        Console.WriteLine("Person details - {0}", person);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Person details - Name = N/A, Age = 0
    Person details - Name = Joe, Age = 99
    Person details - Name = Joe, Age = 100
*/

可靠编程

在上面的示例中，Name 和 Age 属性是公共的，并且同时包含 get 和 set 访问器。这允许任何对象读写这些属性。不过，有时需要排除其中的一个访问器。例如，省略 set 访问器将使该属性成为只读的：

C# 复制代码
public string Name
{
    get
    {
        return name;
    }
}

此外，您还可以公开一个访问器，而使另一个访问器成为私有的或受保护的。有关更多信息，请参见非对称访问器可访问性。

声明了属性后，可像使用类的字段那样使用这些属性。这使得获取和设置属性值时都可以使用非常自然的语法，如以下语句中所示：

C# 复制代码
person.Name = "Joe";
person.Age = 99;

注意，属性 set 方法中可以使用一个特殊的 value 变量。该变量包含用户指定的值，例如：

C# 复制代码
name = value;

请注意用于使 Person 对象上的 Age 属性递增的简洁语法：

C# 复制代码
person.Age += 1;

如果将单独的 set 和 get 方法用于模型属性，则等效代码可能类似于：

复制代码
person.SetAge(person.GetAge() + 1);

本示例中重写了 ToString 方法：

C# 复制代码
public override string ToString()
{
    return "Name = " + Name + ", Age = " + Age;
}

注意，程序中未显式使用 ToString。默认情况下，它由 WriteLine 调用来调用。

请参见

概念

C# 编程指南

参考

属性（C# 编程指南）
类和结构（C# 编程指南）


自动实现的属性（C# 编程指南）

[image]

C# 编程指南
自动实现的属性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

当属性访问器中不需要其他逻辑时，自动实现的属性可使属性声明变得更加简洁。当您如下面的示例所示声明属性时，编译器将创建一个私有的匿名后备字段，该字段只能通过属性的 get 和 set 访问器进行访问。

示例

下面的示例演示了一个具有某些自动实现的属性的简单类：

C# 复制代码
class LightweightCustomer
{
    public double TotalPurchases { get; set; }
    public string Name { get; private set; } // read-only
    public int CustomerID { get; private set; } // read-only
}

自动实现的属性必须同时声明 get 和 set 访问器。若要创建 readonly 自动实现属性，请给予它 private set 访问器。

可以在自动实现的属性 (Property) 上使用属性 (Attribute)，但由于属性 (Attribute) 不可从源代码访问，显然不能用在支持字段上。如果您必须在属性 (Property) 的后备字段上使用属性 (Attribute)，则应该只创建常规属性 (Property)。

请参见

参考

属性（C# 编程指南）
修饰符（C# 参考）


如何：使用自动实现的属性实现轻量类（C# 编程指南）

[image]

C# 编程指南
如何：使用自动实现的属性实现轻量类（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例演示如何创建一个仅用于封装一组自动实现的属性的轻量类。当您必须使用引用类型语义时，请使用此种构造而不是结构。

示例

C# 复制代码
public class Contact
{
    public string Name { get; set; }
    public string Address { get; set; }
    public int ContactNumber { get; set; }
    public int ID { get; private set; } // readonly
}

编译器为每个自动实现的属性创建了后备字段。这些字段无法直接从源代码进行访问。

请参见

参考

属性（C# 编程指南）
struct（C# 参考）


方法（C# 编程指南）

[image]

C# 编程指南
方法（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

“方法”是包含一系列语句的代码块。程序通过“调用”方法并指定所需的任何方法参数来执行语句。在 C# 中，每个执行指令都是在方法的上下文中执行的。Main 方法是每个 C# 应用程序的入口点，在启动程序时由公共语言运行时 (CLR) 调用。

说明：

本主题讨论命名方法。有关匿名函数的更多信息，请参见匿名函数（C# 编程指南）。

方法签名

通过指定方法的访问级别（例如 public 或 private）、可选修饰符（例如 abstract 或 sealed）、返回值、名称和任何方法参数，可以在类或结构中声明方法。这些部分统称为方法的“签名”。

说明：

为进行方法重载，方法的返回类型不是方法签名的一部分。但是，在确定委托和委托所指向方法之间的兼容性时，返回类型是方法签名的一部分。

方法参数括在括号中，并用逗号隔开。空括号表示方法不需要参数。下面的类包含三个方法：

C# 复制代码
abstract class Motorcycle
{
    // Anyone can call this.
    public void StartEngine() {/* Method statements here */ }

    // Only derived classes can call this.
    protected void AddGas(int gallons) { /* Method statements here */ }

    // Derived classes can override the base class implementation.
    public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }

    // Derived classes must implement this.
    public abstract double GetTopSpeed();
}

方法访问

在对象上调用方法类似于访问字段。在对象名称之后，依次添加句点、方法名称和括号。参数在括号内列出，并用逗号隔开。因此，可以按以下示例中的方式调用 Motorcycle 类的方法：

C# 复制代码
class TestMotorcycle : Motorcycle
{

    public override double GetTopSpeed()
    {
        return 108.4;
    }

    static void Main()
    {

        TestMotorcycle moto = new TestMotorcycle();

        moto.StartEngine();
        moto.AddGas(15);
        moto.Drive(5, 20);
        double speed = moto.GetTopSpeed();
        Console.WriteLine("My top speed is {0}", speed);
    }
}

方法形参和实参

方法定义指定所需任何“形参”的名称和类型。调用代码在调用方法时，将为每个形参提供称为“实参”的具体值。实参必须与形参类型兼容，但调用代码中使用的实参名称（如果有）不必与方法中定义的形参名称相同。例如：

C# 复制代码
public void Caller()
{
    int numA = 4;
    // Call with an int variable.
    int productA = Square(numA);

    int numB = 32;
    // Call with another int variable.
    int productB = Square(numB);

    // Call with an integer literal.
    int productC = Square(12);

    // Call with an expression that evaulates to int.
    productC = Square(productA * 3);
}

int Square(int i)
{
    // Store input argument in a local variable.
    int input = i;
    return input * input;
}

通过引用传递与通过值传递

默认情况下，将值类型传递给方法时，传递的是副本而不是对象本身。因此，对参数所做的更改对于调用方法中的原始副本没有影响。可以使用 ref 关键字通过引用传递值类型。有关更多信息，请参见传递值类型参数（C# 编程指南）。有关内置值类型的列表，请参见值类型表（C# 参考）。

“引用类型”通过引用进行传递。将引用类型的对象传递给方法时，引用指向原始对象而不是副本。因此，通过此引用所进行的更改将反映在调用方法中。引用类型是通过使用 class 关键字创建的，如下面的示例中所示：

C# 复制代码
public class SampleRefType
{
    public int value;
}

现在，如果将基于此类型的对象传递给方法，则会通过引用传递该对象。例如：

C# 复制代码
public static void TestRefType()
{
    SampleRefType rt = new SampleRefType();
    rt.value = 44;
    ModifyObject(rt);
    Console.WriteLine(rt.value);
}
static void ModifyObject(SampleRefType obj)
{
    obj.value = 33;
}

此示例的效果本质上与前一示例相同。但是，由于使用的是引用类型，因此 ModifyObject 所做的更改反映在 TestRefType 方法中创建的对象中。因此，TestRefType 方法将显示值 33。

有关更多信息，请参见传递引用类型参数（C# 编程指南）和引用类型（C# 参考）。

返回值

方法可以向调用方返回值。如果返回类型（方法名称前列出的类型）不是 void，则方法可以使用 return 关键字来返回值。如果语句中 return 关键字的后面是与返回类型匹配的值，则该语句将该值返回给方法调用方。return 关键字还会停止方法的执行。如果返回类型为 void，则可使用没有值的 return 语句来停止方法的执行。如果没有 return 关键字，方法执行到代码块末尾时即会停止。具有非 void 返回类型的方法才能使用 return 关键字返回值。例如，下面的两个方法使用 return 关键字来返回整数：

C# 复制代码
class SimpleMath
{
    public int AddTwoNumbers(int number1, int number2)
    {
        return number1 + number2;
    }

    public int SquareANumber(int number)
    {
        return number * number;
    }
}

若要使用从方法返回的值，调用方法可以在本来使用同一类型的值就已足够的任何位置使用方法调用本身。还可以将返回值赋给变量：例如，下面的两个代码示例可实现相同的目的：

C# 复制代码
int result = obj.AddTwoNumbers(1, 2);
obj.SquareANumber(result);
C# 复制代码
obj.SquareANumber(obj.AddTwoNumbers(1, 2));

可以选择使用局部变量（本例中为 result）来存储值。这有助于提高代码的可读性，并且如果要为方法的整个范围存储参数的原始值，可能必须这样做。

有关更多信息，请参见 return（C# 参考）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.6 方法

  • 10.6 方法

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
访问修饰符（C# 编程指南）
静态类和静态类成员（C# 编程指南）
继承（C# 编程指南）
抽象类、密封类及类成员（C# 编程指南）
params（C# 参考）
return（C# 参考）
out（C# 参考）
ref（C# 参考）
传递参数（C# 编程指南）


传递参数（C# 编程指南）

[image]

C# 编程指南
传递参数（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 中，既可以通过值也可以通过引用传递参数。通过引用传递参数允许函数成员（方法、属性、索引器、运算符和构造函数）更改参数的值，并保持该更改。若要通过引用传递参数，请使用 ref 或 out 关键字。为简单起见，本主题的示例中只使用了 ref 关键字。有关 ref 和 ref 之间的区别的更多信息，请参见 ref（C# 参考）、out（C# 参考）和使用 ref 和 out 传递数组（C# 编程指南）。例如：

C# 复制代码
// Passing by value
static void Square(int x)
{
    // code...
}
C# 复制代码
// Passing by reference
static void Square(ref int x)
{
    // code...
}

本主题包括下列章节：

  • 传递值类型参数（C# 编程指南）

  • 传递引用类型参数（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.6.1 参数

  • 5.1.4 值参数

  • 5.1.5 引用参数

  • 5.1.6 输出参数

  • 10.6.1 方法参数

请参见

概念

C# 编程指南

参考

方法（C# 编程指南）


传递值类型参数（C# 编程指南）

[image]

C# 编程指南
传递值类型参数（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

值类型变量直接包含其数据，这与引用类型变量不同，后者包含对其数据的引用。因此，向方法传递值类型变量意味着向方法传递变量的一个副本。方法内发生的对参数的更改对该变量中存储的原始数据无任何影响。如果希望所调用的方法更改参数的值，必须使用 ref 或 out 关键字通过引用传递该参数。为了简单起见，下面的示例使用 ref。

示例

下面的示例演示通过值传递值类型参数。通过值将变量 n 传递给方法 SquareIt。方法内发生的任何更改对变量的原始值无任何影响。

C# 复制代码
class PassingValByVal
{
    static void SquareIt(int x)
    // The parameter x is passed by value.
    // Changes to x will not affect the original value of x.
    {
        x *= x;
        System.Console.WriteLine("The value inside the method: {0}", x);
    }
    static void Main()
    {
        int n = 5;
        System.Console.WriteLine("The value before calling the method: {0}", n);

        SquareIt(n);  // Passing the variable by value.
        System.Console.WriteLine("The value after calling the method: {0}", n);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 5
*/

变量 n 为值类型，包含其数据（值为 5）。当调用 SquareIt 时，n 的内容被复制到参数 x 中，在方法内将该参数求平方。但在 Main 中，n 的值在调用 SquareIt 方法前后是相同的。实际上，方法内发生的更改只影响局部变量 x。

下面的示例除使用 ref 关键字传递参数以外，其余与上一示例相同。参数的值在调用方法后发生更改。

C# 复制代码
class PassingValByRef
{
    static void SquareIt(ref int x)
    // The parameter x is passed by reference.
    // Changes to x will affect the original value of x.
    {
        x *= x;
        System.Console.WriteLine("The value inside the method: {0}", x);
    }
    static void Main()
    {
        int n = 5;
        System.Console.WriteLine("The value before calling the method: {0}", n);

        SquareIt(ref n);  // Passing the variable by reference.
        System.Console.WriteLine("The value after calling the method: {0}", n);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    The value before calling the method: 5
    The value inside the method: 25
    The value after calling the method: 25
*/

本示例中，传递的不是 n 的值，而是对 n 的引用。参数 x 不是 int 类型，它是对 int 的引用（本例中为对 n 的引用）。因此，当在方法内对 x 求平方时，实际被求平方的是 x 所引用的项：n。

更改所传递参数的值的常见示例是 Swap 方法，在该方法中传递 x 和 y 两个变量，然后使方法交换它们的内容。必须通过引用向 Swap 方法传递参数；否则，方法内所处理的将是参数的本地副本。以下是使用引用参数的 Swap 方法的示例：

C# 复制代码
static void SwapByRef(ref int x, ref int y)
{
    int temp = x;
    x = y;
    y = temp;
}

调用该方法时，请在调用中使用 ref 关键字，如下所示：

C# 复制代码
static void Main()
{
    int i = 2, j = 3;
    System.Console.WriteLine("i = {0}  j = {1}" , i, j);

    SwapByRef (ref i, ref j);

    System.Console.WriteLine("i = {0}  j = {1}" , i, j);

    // Keep the console window open in debug mode.
    System.Console.WriteLine("Press any key to exit.");
    System.Console.ReadKey();
}
/* Output:
    i = 2  j = 3
    i = 3  j = 2
*/

请参见

概念

C# 编程指南

参考

传递参数（C# 编程指南）
传递引用类型参数（C# 编程指南）


传递引用类型参数（C# 编程指南）

[image]

C# 编程指南
传递引用类型参数（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

引用类型的变量不直接包含其数据；它包含的是对其数据的引用。当通过值传递引用类型的参数时，有可能更改引用所指向的数据，如某类成员的值。但是无法更改引用本身的值；也就是说，不能使用相同的引用为新类分配内存并使之在块外保持。若要这样做，应使用 ref 或 out 关键字传递参数。为了简单起见，下面的示例使用 ref。

示例

下面的示例演示通过值向 Change 方法传递引用类型的参数 arr。由于该参数是对 arr 的引用，所以有可能更改数组元素的值。但是，试图将参数重新分配到不同的内存位置时，该操作仅在方法内有效，并不影响原始变量 arr。

C# 复制代码
class PassingRefByVal
{
    static void Change(int[] pArray)
    {
        pArray[0] = 888;  // This change affects the original element.
        pArray = new int[5] {-3, -1, -2, -3, -4};   // This change is local.
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main()
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr [0]);

        Change(arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr [0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: 888
*/

在上个示例中，数组 arr 为引用类型，在未使用 ref 参数的情况下传递给方法。在此情况下，将向方法传递指向 arr 的引用的一个副本。输出显示方法有可能更改数组元素的内容，在这种情况下，从 1 改为 888。但是，在 Change 方法内使用 new 运算符来分配新的内存部分，将使变量 pArray 引用新的数组。因此，这之后的任何更改都不会影响原始数组 arr（它是在 Main 内创建的）。实际上，本示例中创建了两个数组，一个在 Main 内，一个在 Change 方法内。

本示例除在方法头和调用中使用 ref 关键字以外，其余与上个示例相同。方法内发生的任何更改都会影响调用程序中的原始变量。

C# 复制代码
class PassingRefByRef
{
    static void Change(ref int[] pArray)
    {
        // Both of the following changes will affect the original variables:
        pArray[0] = 888;
        pArray = new int[5] {-3, -1, -2, -3, -4};
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main()
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

        Change(ref arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: -3
*/

方法内发生的所有更改都影响 Main 中的原始数组。实际上，使用 new 运算符对原始数组进行了重新分配。因此，调用 Change 方法后，对 arr 的任何引用都将指向 Change 方法中创建的五个元素的数组。

交换字符串是通过引用传递引用类型参数的很好的示例。本示例中，str1 和 str2 两个字符串在 Main 中初始化，并作为由 ref 关键字修改的参数传递给 SwapStrings 方法。这两个字符串在该方法内以及 Main 内均进行交换。

C# 复制代码
 class SwappingStrings
 {
     static void SwapStrings(ref string s1, ref string s2)
     // The string parameter is passed by reference.
     // Any changes on parameters will affect the original variables.
     {
         string temp = s1;
         s1 = s2;
         s2 = temp;
         System.Console.WriteLine("Inside the method: {0} {1}", s1, s2);
     }

     static void Main()
     {
         string str1 = "John";
         string str2 = "Smith";
         System.Console.WriteLine("Inside Main, before swapping: {0} {1}", str1, str2);

         SwapStrings(ref str1, ref str2);   // Passing strings by reference
         System.Console.WriteLine("Inside Main, after swapping: {0} {1}", str1, str2);
     }
 }
 /* Output:
     Inside Main, before swapping: John Smith
     Inside the method: Smith John
     Inside Main, after swapping: Smith John
*/

本示例中，需要通过引用传递参数以影响调用程序中的变量。如果同时从方法头和方法调用中移除 ref 关键字，则调用程序中不会发生任何更改。

有关字符串的更多信息，请参见字符串。

请参见

概念

C# 编程指南

参考

传递参数（C# 编程指南）
使用 ref 和 out 传递数组（C# 编程指南）
ref（C# 参考）
引用类型（C# 参考）


如何：了解向方法传递结构和向方法传递类引用之间的区别（C# 编程指南）

[image]

C# 编程指南
如何：了解向方法传递结构和向方法传递类引用之间的区别（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例演示在向方法传递结构时，传递的是该结构的副本，而在传递类实例时，传递的是一个引用。

本示例的输出表明：当向 ClassTaker 方法传递类实例时，只更改类字段的值。但是向 StructTaker 方法传递结构实例并不更改结构字段。这是因为向 StructTaker 方法传递的是结构的副本，而向 ClassTaker 方法传递的是对类的引用。

示例

C# 复制代码
class TheClass
{
    public string willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }

    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange);
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Class field = Changed
    Struct field = Not Changed
*/

请参见

概念

C# 编程指南

参考

类（C# 编程指南）
结构（C# 编程指南）
传递参数（C# 编程指南）


隐式类型的局部变量（C# 编程指南）

[image]

C# 编程指南
隐式类型的局部变量（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

可以赋予局部变量推断“类型”var 而不是显式类型。var 关键字指示编译器根据初始化语句右侧的表达式推断变量的类型。推断类型可以是内置类型、匿名类型、用户定义类型或 .NET Framework 类库中定义的类型。有关如何使用 var 初始化数组的更多信息，请参见隐式类型的数组（C# 编程指南）。

下面的示例演示了使用 var 声明局部变量的各种方式：

C# 复制代码
// i is compiled as an int
var i = 5;

// s is compiled as a string
var s = "Hello";

// a is compiled as int[]
var a = new[] { 0, 1, 2 };

// expr is compiled as IEnumerable<Customer>
// or perhaps IQueryable<Customer>
var expr =
    from c in customers
    where c.City == "London"
    select c;

// anon is compiled as an anonymous type
var anon = new { Name = "Terry", Age = 34 };

// list is compiled as List<int>
var list = new List<int>();

需要了解的一点是，var 关键字并不意味着“变体”，也不表示该变量是松散类型化变量或后期绑定变量。它只是表示由编译器确定和分配最适当的类型。

var 关键字可在下面的上下文中使用：

  • 在如上示例所示的局部变量（在方法范围中声明的变量）上。

  • 在 for 初始化语句中。

    复制代码
    for(var x = 1; x < 10; x++)

  • 在 foreach 初始化语句中。

    复制代码
    foreach(var item in list){...}

  • 在 using 语句中

    复制代码
    using (var file = new StreamReader("C:\\myfile.txt")) {...}

有关更多信息，请参见如何：在查询表达式中使用隐式类型的局部变量和数组（C# 编程指南）。

var 和匿名类型

在很多情况下，var 是可选的，它只是提供了语法上的便利。但是，在使用匿名类型初始化变量时，如果需要在以后访问对象的属性，则必须将该变量声明为 var。这在 LINQ 查询表达式中很常见。有关更多信息，请参见匿名类型（C# 编程指南）。

从源代码的角度来说，匿名类型没有名称。因此，如果已使用 var 初始化查询变量，则只有一种方法可以访问返回的对象序列中的属性，那就是使用 var 作为 foreach 语句中的迭代变量的类型。

C# 复制代码
class ImplicitlyTypedLocals2
{
    static void Main()
    {
        string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };

        // If a query produces a sequence of anonymous types,
        // then use var in the foreach statement to access the properties.
        var upperLowerWords =
             from w in words
             select new { Upper = w.ToUpper(), Lower = w.ToLower() };

        // Execute the query
        foreach (var ul in upperLowerWords)
        {
            Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower);
        }
    }
}
/* Outputs:
    Uppercase: APPLE, Lowercase: apple
    Uppercase: BLUEBERRY, Lowercase: blueberry
    Uppercase: CHERRY, Lowercase: cherry
 */

备注

下列限制适用于隐式类型的变量声明：

  • 只有在同一语句中声明和初始化局部变量时，才能使用 var；不能将该变量初始化为 null、方法组或匿名函数。

  • 不能将 var 用于类范围的域。

  • 由 var 声明的变量不能用在初始化表达式中。换句话说，表达式 int i = (i = 20) 是合法的；但表达式 var i = (i = 20) 则会产生编译时错误。

  • 不能在同一语句中初始化多个隐式类型的变量。

  • 如果范围中有一个名为 var 的类型，则 var 关键字将解析为该类型名称，而不作为隐式类型局部变量声明的一部分进行处理。

在查询表达式中，当难以确定查询变量的确切构造类型时，您会发现 var 也很有用。这种情况可能发生在分组和排序操作中。

当在键盘上键入变量的具体类型单调乏味时，或者当该类型显而易见或对提高代码可读性没有作用时，var 关键字也可能有用。var 以这种方式发挥作用的一个示例是嵌套的泛型类型，例如在分组操作中使用的那些类型。在下面的查询中，查询变量的类型是 IEnumerable<IGrouping<string, Student>>。只要您和其他必须维护您代码的人员了解到这一点，就可以毫无问题地使用隐式类型化，以达到方便和简洁的效果。

C# 复制代码
// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
 var studentQuery3 =
     from student in students
     group student by student.Last;

不过，使用 var 确实可能使其他开发人员更加难以理解您的代码。因此，C# 文档通常仅在需要时才使用 var。

请参见

任务

如何：在查询表达式中使用隐式类型的局部变量和数组（C# 编程指南）

概念

LINQ 查询表达式（C# 编程指南）

参考

隐式类型的数组（C# 编程指南）
匿名类型（C# 编程指南）
对象和集合初始值设定项（C# 编程指南）
var（C# 参考）
for（C# 参考）
foreach，in（C# 参考）
using 语句（C# 参考）

其他资源

C# 参考
语言集成查询 (LINQ)

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 纠正了有关初始化匿名类型时要求使用 var 的陈述内容。实际上，只有在以后希望访问这些对象的属性时才需要使用该关键字。 | 内容 Bug 修复


如何：在查询表达式中使用隐式类型的局部变量和数组（C# 编程指南）

[image]

C# 编程指南
如何：在查询表达式中使用隐式类型的局部变量和数组（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

必须使用隐式类型的局部变量来存储匿名类型。还可以在希望编译器确定局部变量（即在方法范围中声明的变量）类型的任何其他情况下使用它们。下面的示例演示如何在这两种方案中使用隐式类型的变量。

隐式类型的局部变量是使用 var 上下文关键字声明的。有关更多信息，请参见隐式类型的局部变量（C# 编程指南）和隐式类型的数组（C# 编程指南）。

示例

下面的示例演示如何在以下需要 var 关键字的常见方案中使用该关键字：创建并执行一个产生匿名类型序列的查询表达式。请注意，在此方案中，不仅查询变量必须使用 var 进行隐式类型化，而且 foreach 语句中的迭代变量也必须如此。

C# 复制代码
private static void QueryNames(char firstLetter)
{
    // Create the query. var is required because
    // the query produces a sequence of anonymous types.
    var studentQuery =
        from student in students
        where student.FirstName[0] == firstLetter
        select new { student.FirstName, student.LastName };

    // Execute the query.
    foreach (var student in studentQuery)
    {
        Console.WriteLine("First = {0}, Last = {1}", student.FirstName, student.LastName);
    }
}

本主题稍后部分的示例演示如何使用 var 关键字提供语法上的便利（即使它不是必需的）。为了演示此构造，只将查询变量进行隐式类型化。foreach 语句中的迭代变量被显式类型化，但也可以使用 var 声明它。请记住，var 本身不是类型，而是向编译器发出的一条用来推断和分配类型的指令。

C# 复制代码
var queryID =
    from student in students
    where student.ID > 111
    select student.LastName;

foreach (string str in queryID)
{
    Console.WriteLine(str);
}

请参见

概念

C# 编程指南
LINQ 查询表达式（C# 编程指南）

参考

扩展方法（C# 编程指南）
var（C# 参考）

其他资源

语言集成查询 (LINQ)


扩展方法（C# 编程指南）

[image]

C# 编程指南
扩展方法（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

扩展方法使您能够向现有类型“添加”方法，而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法，但可以像扩展类型上的实例方法一样进行调用。对于用 C# 和 Visual Basic 编写的客户端代码，调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。

最常见的扩展方法是 LINQ 标准查询运算符，这些运算符在现有 System.Collections..::.IEnumerable 和 System.Collections.Generic..::.IEnumerable<(Of <(T>)>) 类型中添加了查询功能。若要使用这些标准查询运算符，请先使用 using System.Linq 指令将它们纳入范围中。然后，任何实现了 IEnumerable<(Of <(T>)>) 的类型看起来都具有 GroupBy、OrderBy、Average 等实例方法。在 IEnumerable<(Of <(T>)>) 类型的实例（如 List<(Of <(T>)>) 或 Array）后键入“点”时，可以在 IntelliSense 语句结束中看到这些附加方法。

下面的示例演示如何对一个整数数组调用标准查询运算符 OrderBy 方法。括号里面的表达式是一个 lambda 表达式。很多标准查询运算符采用 lambda 表达式作为参数，但这不是扩展方法的必要条件。有关更多信息，请参见 Lambda 表达式（C# 编程指南）。

C# 复制代码
class ExtensionMethods2
{

    static void Main()
    {
        int[] ints = { 10, 45, 15, 39, 21, 26 };
        var result = ints.OrderBy(g => g);
        foreach (var i in result)
        {
            System.Console.Write(i + " ");
        }
    }
}
//Output: 10 15 21 26 39 45

扩展方法被定义为静态方法，但它们是通过实例方法语法进行调用的。它们的第一个参数指定该方法作用于哪个类型，并且该参数以 this 修饰符为前缀。仅当您使用 using 指令将命名空间显式导入到源代码中之后，扩展方法才位于范围中。

下面的示例演示为 System..::.String 类定义的一个扩展方法。请注意，它是在非嵌套、非泛型静态类内部定义的：

C# 复制代码
namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}

可使用以下 using 指令将 WordCount 扩展方法放入范围中：

复制代码
using ExtensionMethods;

而且，可以在应用程序中使用以下语法对该扩展方法进行调用：

复制代码
string s = "Hello Extension Methods";
int i = s.WordCount();

在代码中，可以使用实例方法语法调用该扩展方法。但是，编译器生成的中间语言 (IL) 会将代码转换为对静态方法的调用。因此，并未真正违反封装原则。实际上，扩展方法无法访问它们所扩展的类型中的私有变量。

有关更多信息，请参见如何：实现和调用自定义扩展方法（C# 编程指南）。

通常，您更多时候是调用扩展方法而不是实现您自己的扩展方法。由于扩展方法是使用实例方法语法调用的，因此不需要任何特殊知识即可从客户端代码中使用它们。若要为特定类型启用扩展方法，只需为在其中定义这些方法的命名空间添加 using 指令。例如，若要使用标准查询运算符，请将以下 using 指令添加到代码中：

复制代码
using System.Linq;

（您可能还必须添加对 System.Core.dll 的引用。）您将注意到，标准查询运算符现在作为可供大多数 IEnumerable<(Of <(T>)>) 类型使用的附加方法显示在 IntelliSense 中。

说明：

尽管标准查询运算符没有显示在 String 的 IntelliSense 中，但它们仍然可用。

在编译时绑定扩展方法

可以使用扩展方法来扩展类或接口，但不能重写扩展方法。与接口或类方法具有相同名称和签名的扩展方法永远不会被调用。编译时，扩展方法的优先级总是比类型本身中定义的实例方法低。换句话说，如果某个类型具有一个名为 Process(int i) 的方法，而您有一个具有相同签名的扩展方法，则编译器总是绑定到该实例方法。当编译器遇到方法调用时，它首先在该类型的实例方法中寻找匹配的方法。如果未找到任何匹配方法，编译器将搜索为该类型定义的任何扩展方法，并且绑定到它找到的第一个扩展方法。下面的示例演示编译器如何确定要绑定到哪个扩展方法或实例方法。

示例

下面的示例演示 C# 编译器在确定是将方法调用绑定到类型上的实例方法还是绑定到扩展方法时所遵循的规则。静态类 Extensions 包含为任何实现了 IMyInterface 的类型定义的扩展方法。类 A、B 和 C 都实现了该接口。

MethodB 方法永远不会被调用，因为它的名称和签名与这些类已经实现的方法完全匹配。

如果编译器找不到具有匹配签名的实例方法，它会绑定到匹配的扩展方法（如果存在这样的方法）。

C# 复制代码
namespace Extensions
{
  using System;
  using ExtensionMethodsDemo1;

     // Define extension methods for any type that implements IMyInterface.
     public static class Extension
     {
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        // This method is never called, because the three classes implement MethodB.
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;

    public interface IMyInterface
    {
        void MethodB();
    }

    class A : IMyInterface
    {
        public void MethodB(){Console.WriteLine("A.MethodB()");}
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj) { Console.WriteLine("C.MethodA(object obj)"); }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {
            A a = new A();
            B b = new B();
            C c = new C();
            TestMethodBinding(a,b,c);
        }

        static void TestMethodBinding(A a, B b, C c)
        {
            // A has no methods, so each call resolves to
            // the extension methods whose signatures match.
            a.MethodA(1);           // Extension.MethodA(object, int)
            a.MethodA("hello");     // Extension.MethodA(object, string)
            a.MethodB();            // A.MethodB()

            // B itself has a method with this signature.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()

            // B has no matching method, but Extension does.
            b.MethodA("hello");     // Extension.MethodA(object, string)

            // In each case C has a matching instance method.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");     // C.MethodA(object)
            c.MethodB();            // C.MethodB()
        }
    }
}
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */

通用准则

通常，建议您只在不得已的情况下才实现扩展方法，并谨慎地实现。只要有可能，必须扩展现有类型的客户端代码都应该通过创建从现有类型派生的新类型来达到这一目的。有关更多信息，请参见继承（C# 编程指南）。

在使用扩展方法来扩展您无法更改其源代码的类型时，您需要承受该类型实现中的更改会导致扩展方法失效的风险。

如果您确实为给定类型实现了扩展方法，请记住以下两点：

  • 如果扩展方法与该类型中定义的方法具有相同的签名，则扩展方法永远不会被调用。

  • 扩展方法被在命名空间级别放入范围中。例如，如果您在同一个名为 Extensions 的命名空间中具有多个包含扩展方法的静态类，则这些扩展方法将全部由 using Extensions; 指令放入范围中。

类库的实施者不应使用扩展方法来避免创建程序集的新版本。如果您要向库中添加重要的新功能，并且您拥有源代码，则应该遵循标准 .NET Framework 程序集版本控制准则。有关更多信息，请参见程序集版本控制。

请参见

概念

C# 编程指南
标准查询运算符概述

参考

Lambda 表达式（C# 编程指南）

其他资源

Conversion rules for Instance parameters and their impact（实例参数的转换规则及其影响）
Extension methods Interoperability between languages（扩展方法在不同语言之间的互操作性）
Extension methods and Curried Delegates（扩展方法和扩充委托）


如何：实现和调用自定义扩展方法（C# 编程指南）

[image]

C# 编程指南
如何：实现和调用自定义扩展方法（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本主题演示如何为 .NET Framework 类库中的任何类型或您想要扩展的任何其他 .NET 类型实现您自己的扩展方法。客户端代码可通过以下方式使用您的扩展方法：添加对包含这些扩展方法的 DLL 的引用，并且添加一条 using 指令以指定在其中定义这些扩展方法的命名空间。

定义和调用扩展方法

  1. 定义一个静态类以包含扩展方法。

    该类必须对客户端代码可见。有关可访问性规则的更多信息，请参见访问修饰符（C# 编程指南）。

  2. 将该扩展方法实现为静态方法，并使其至少具有与包含类相同的可见性。

  3. 该方法的第一个参数指定方法所操作的类型；该参数必须以 this 修饰符开头。

  4. 在调用代码中，添加一条 using 指令以指定包含扩展方法类的命名空间。

  5. 按照与调用类型上的实例方法一样的方式调用扩展方法。

    请注意，第一个参数不是由调用代码指定的，因为它表示正应用运算符的类型，并且编译器已经知道对象的类型。您只需通过 n 为这两个形参提供实参。

示例

下面的示例在 MyExtensions.StringExtension 类中实现了一个名为 WordCount 的扩展方法。该方法对 String 类进行操作，而该类被指定为第一个方法参数。MyExtensions 命名空间被导入到应用程序命名空间中，并且该方法是在 Main 方法内调用的。

C# 复制代码
using System.Linq;
using System.Text;
using System;

namespace CustomExtensions
{
    //Extension methods must be defined in a static class
    public static class StringExtension
    {
        // This is the extension method.
        // The first parameter takes the "this" modifier
        // and specifies the type for which the method is defined.
        public static int WordCount(this String str)
        {
            return str.Split(new char[] {' ', '.','?'}, StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}
namespace Extension_Methods_Simple
{
    //Import the extension method namespace.
    using CustomExtensions;
    class Program
    {
        static void Main(string[] args)
        {
            string s = "The quick brown fox jumped over the lazy dog.";
            //  Call the method as if it were an
            //  instance method on the type. Note that the first
            //  parameter is not specified by the calling code.
            int i = s.WordCount();
            System.Console.WriteLine("Word count of s is {0}", i);
        }
    }
}

编译代码

若要运行这段代码，请将其复制并粘贴到已经在 Visual Studio 中创建的 Visual C# 控制台应用程序项目中。默认情况下，此项目针对的是 .NET Framework 3.5 版，并且具有一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。如果项目不满足上面的一个或多个要求，则您可以手动添加它们。有关更多信息，请参见如何：创建 LINQ 项目。

安全性

扩展方法不会引起任何特定安全漏洞。它们永远不可用于模拟类型上的现有方法，因为在解决所有名称冲突时，都会偏向于类型本身所定义的实例或静态方法。扩展方法无法访问扩展类中的任何私有数据。

请参见

概念

C# 编程指南

参考

扩展方法（C# 编程指南）
静态类和静态类成员（C# 编程指南）
protected（C# 参考）
internal（C# 参考）
public（C# 参考）
this（C# 参考）
命名空间（C# 参考）

其他资源

语言集成查询 (LINQ)


如何：为枚举创建新方法（C# 编程指南）

[image]

C# 编程指南
如何：为枚举创建新方法（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可以使用扩展方法添加特定于某个特定枚举类型的功能。

示例

在下面的示例中，Grades 枚举表示学生可能在班里收到的字母等级分。该示例将一个名为 Passing 的扩展方法添加到 Grades 类型中，以便该类型的每个实例现在都“知道”它是否表示合格的等级分。

C# 复制代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace EnumExtension
{
    // Define an extension method in a non-nested static class.
    public static class Extensions
    {
        public static Grades minPassing = Grades.D;
        public static bool Passing(this Grades grade)
        {
            return grade >= minPassing;
        }
    }

    public enum Grades { F = 0, D=1, C=2, B=3, A=4 };
    class Program
    {
        static void Main(string[] args)
        {
            Grades g1 = Grades.D;
            Grades g2 = Grades.F;
            Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
            Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");

            Extensions.minPassing = Grades.C;
            Console.WriteLine("\r\nRaising the bar!\r\n");
            Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
            Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");
        }
    }
  }
}
/* Output:
    First is a passing grade.
    Second is not a passing grade.

    Raising the bar!

    First is not a passing grade.
    Second is not a passing grade.
 */

请注意，Extensions 类还包含一个动态更新的静态变量，并且扩展方法的返回值反映了该变量的当前值。这表明在幕后，将在定义扩展方法的静态类上直接调用这些方法。

编译代码

若要运行这段代码，请将其复制并粘贴到已经在 Visual Studio 中创建的 Visual C# 控制台应用程序项目中。默认情况下，此项目针对的是 .NET Framework 3.5 版，并且具有一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。如果项目不满足上面的一个或多个要求，则您可以手动添加它们。有关更多信息，请参见如何：创建 LINQ 项目。

请参见

概念

C# 编程指南

参考

扩展方法（C# 编程指南）


构造函数（C# 编程指南）

[image]

C# 编程指南
构造函数（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

任何时候，只要创建类或结构，就会调用它的构造函数。类或结构可能有多个接受不同参数的构造函数。构造函数使得程序员可设置默认值、限制实例化以及编写灵活且便于阅读的代码。

如果您没有为对象提供构造函数，则默认情况下 C# 将创建一个构造函数，该构造函数实例化对象，并将成员变量设置为默认值表（C# 参考）中列出的默认值。静态类和结构也可以有构造函数。

本节内容

使用构造函数（C# 编程指南）

实例构造函数（C# 编程指南）

私有构造函数（C# 编程指南）

静态构造函数（C# 编程指南）

如何：编写复制构造函数（C# 编程指南）

请参见

概念

C# 编程指南
构造函数设计

参考

类和结构（C# 编程指南）
析构函数（C# 编程指南）
static（C# 参考）


使用构造函数（C# 编程指南）

[image]

C# 编程指南
使用构造函数（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

构造函数是在创建给定类型的对象时执行的类方法。构造函数具有与类相同的名称，它通常初始化新对象的数据成员。

在下面的示例中，使用一个简单的构造函数定义了名为 Taxi 的类。然后使用 new 运算符来实例化该类。在为新对象分配内存之后，new 运算符立即调用 Taxi 构造函数。

C# 复制代码
public class Taxi
{
    public bool isInitialized;
    public Taxi()
    {
        isInitialized = true;
    }
}

class TestTaxi
{
    static void Main()
    {
        Taxi t = new Taxi();
        Console.WriteLine(t.isInitialized);
    }
}

不带参数的构造函数称为“默认构造函数”。无论何时，只要使用 new 运算符实例化对象，并且不为 new 提供任何参数，就会调用默认构造函数。有关更多信息，请参见实例构造函数（C# 编程指南）。

除非类是 static 的，否则 C# 编译器将为无构造函数的类提供一个公共的默认构造函数，以便该类可以实例化。有关更多信息，请参见静态类和静态类成员（C# 编程指南）。

通过将构造函数设置为私有构造函数，可以阻止类被实例化，如下所示：

C# 复制代码
class NLog
{
    // Private Constructor:
    private NLog() { }

    public static double e = Math.E;  //2.71828...
}

有关更多信息，请参见私有构造函数（C# 编程指南）。

结构类型的构造函数与类的构造函数类似，但是 structs 不能包含显式默认构造函数，因为编译器将自动提供一个构造函数。此构造函数会将 struct 中的每个字段初始化为默认值。有关更多信息，请参见默认值表（C# 参考）。然而，只有当 struct 用 new 实例化时，才会调用此默认构造函数。例如，下面的代码使用 Int32 的默认构造函数，因此您可以确信整数已初始化：

复制代码
int i = new int();
Console.WriteLine(i);

然而，下面的代码却导致了编译器错误 CS0165，因为它没有使用 new，而且尝试使用尚未初始化的对象：

复制代码
int i;
Console.WriteLine(i);

或者，基于 structs 的对象（包括所有内置数值类型）可以初始化或赋值后使用，如下面的示例所示：

复制代码
int a = 44;  // Initialize the value type...
int b;
b = 33;      // Or assign it before using it.
Console.WriteLine("{0}, {1}", a, b);

因此对值类型调用默认构造函数不是必需的。

类和 structs 都可以定义具有参数的构造函数。带参数的构造函数必须通过 new 语句或 base 语句来调用。类和 structs 还可以定义多个构造函数，并且二者均不需要定义默认构造函数。例如：

C# 复制代码
public class Employee
{
    public int salary;

    public Employee(int annualSalary)
    {
        salary = annualSalary;
    }

    public Employee(int weeklySalary, int numberOfWeeks)
    {
        salary = weeklySalary * numberOfWeeks;
    }
}

可以使用下列语句中的任一个语句来创建此类：

C# 复制代码
Employee e1 = new Employee(30000);
Employee e2 = new Employee(500, 52);

构造函数可以使用 base 关键字来调用基类的构造函数。例如：

C# 复制代码
public class Manager : Employee
{
    public Manager(int annualSalary)
        : base(annualSalary)
    {
        //Add further instructions here.
    }
}

在此示例中，基类的构造函数在执行构造函数块之前被调用。base 关键字可带参数使用，也可不带参数使用。构造函数的任何参数都可用作 base 的参数，或用作表达式的一部分。有关更多信息，请参见 base（C# 参考）。

在派生类中，如果不使用 base 关键字来显式调用基类构造函数，则将隐式调用默认构造函数（如果有的话）。这意味着下面的构造函数声明在效果上是相同的：

C# 复制代码
public Manager(int initialdata)
{
    //Add further instructions here.
}
C# 复制代码
public Manager(int initialdata)
    : base()
{
    //Add further instructions here.
}

如果基类没有提供默认构造函数，派生类必须使用 base 显式调用基构造函数。

构造函数可以使用 this 关键字调用同一对象中的另一构造函数。和 base 一样，this 可带参数使用也可不带参数使用，构造函数中的任何参数都可用作 this 的参数，或者用作表达式的一部分。例如，可以使用 this 重写前一示例中的第二个构造函数：

C# 复制代码
public Employee(int weeklySalary, int numberOfWeeks)
    : this(weeklySalary * numberOfWeeks)
{
}

上一示例中对 this 关键字的使用导致此构造函数被调用：

C# 复制代码
public Employee(int annualSalary)
{
    salary = annualSalary;
}

构造函数可以标记为 public、private、protected、internal 或 protected internal。这些访问修饰符定义类的用户构造该类的方式。有关更多信息，请参见访问修饰符。

使用 static 关键字可以将构造函数声明为静态构造函数。在访问任何静态字段之前，都将自动调用静态构造函数，它们通常用于初始化静态类成员。有关更多信息，请参见静态构造函数。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.1 构造函数

  • 10.11 实例构造函数（类）

  • 11.3.8 构造函数（结构）

  • 11.3.10 静态构造函数（结构）

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
构造函数（C# 编程指南）
析构函数（C# 编程指南）


实例构造函数（C# 编程指南）

[image]

C# 编程指南
实例构造函数（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

实例构造函数用于创建和初始化实例。创建新对象时将调用类构造函数，例如：

C# 复制代码
class CoOrds
{
    public int x, y;

    // constructor
    public CoOrds()
    {
        x = 0;
        y = 0;
    }
}
说明：

为了清楚起见，此类包含公共数据成员。建议不要使用这种编程方法，因为它使程序中任何位置的任何方法都可以不受限制、不经验证地访问对象的内部组件。数据成员通常应当为私有的，并且只应当通过类方法和属性来访问。

无论何时创建基于 CoOrds 类的对象，都会调用此构造函数。诸如此类不带参数的构造函数称为“默认构造函数”。然而，提供其他构造函数通常十分有用。例如，可以向 CoOrds 类添加构造函数，以便可以为数据成员指定初始值：

C# 复制代码
// A constructor with two arguments:
public CoOrds(int x, int y)
{
    this.x = x;
    this.y = y;
}

这样便可以用默认或特定的初始值创建 CoOrd 对象，如下所示：

C# 复制代码
CoOrds p1 = new CoOrds();
CoOrds p2 = new CoOrds(5, 3);

如果类没有默认构造函数，将自动生成一个构造函数，并且将用默认值来初始化对象字段，例如，int 被初始化为 0。有关默认值的更多信息，请参见 默认值表（C# 参考）。因此，由于 CoOrds 类的默认构造函数将所有数据成员都初始化为零，因此可以将它完全移除，而不会更改类的工作方式。本主题的稍后部分的示例 1 中提供了使用多个构造函数的完整示例，示例 2 中提供了自动生成的构造函数的示例。

也可以用实例构造函数来调用基类的实例构造函数。类构造函数可通过初始值设定项来调用基类的构造函数，如下所示：

C# 复制代码
class Circle : Shape
{
    public Circle(double radius)
        : base(radius, 0)
    {
    }
}

在此示例中，Circle 类将表示半径和高度的值传递给 Shape（Circle 从它派生而来）提供的构造函数。使用 Shape 和 Circle 的完整示例请见本主题中的示例 3。

示例 1

下面的示例说明包含两个类构造函数的类：一个类构造函数没有参数，另一个类构造函数带有两个参数。

C# 复制代码
class CoOrds
{
    public int x, y;

    // Default constructor:
    public CoOrds()
    {
        x = 0;
        y = 0;
    }

    // A constructor with two arguments:
    public CoOrds(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    // Override the ToString method:
    public override string ToString()
    {
        return (String.Format("({0},{1})", x, y));
    }
}

class MainClass
{
    static void Main()
    {
        CoOrds p1 = new CoOrds();
        CoOrds p2 = new CoOrds(5, 3);

        // Display the results using the overriden ToString method:
        Console.WriteLine("CoOrds #1 at {0}", p1);
        Console.WriteLine("CoOrds #2 at {0}", p2);
        Console.ReadKey();
    }
}
/* Output:
 CoOrds #1 at (0,0)
 CoOrds #2 at (5,3)
*/

示例 2

在此示例中，类 Person 没有任何构造函数；在这种情况下，将自动提供默认构造函数，同时将字段初始化为它们的默认值。

C# 复制代码
public class Person
{
    public int age;
    public string name;
}

class TestPerson
{
    static void Main()
    {
        Person person = new Person();

        Console.WriteLine("Name: {0}, Age: {1}", person.name, person.age);
        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output:  Name: , Age: 0

注意，age 的默认值为 0，name 的默认值为 null。有关默认值的更多信息，请参见 默认值表（C# 参考）。

示例 3

下面的示例说明使用基类初始值设定项。Circle 类是从通用类 Shape 派生的，Cylinder 类是从 Circle 类派生的。每个派生类的构造函数都使用其基类的初始值设定项。

C# 复制代码
abstract class Shape
{
    public const double pi = Math.PI;
    protected double x, y;

    public Shape(double x, double y)
    {
        this.x = x;
        this.y = y;
    }

    public abstract double Area();
}

class Circle : Shape
{
    public Circle(double radius)
        : base(radius, 0)
    {
    }
    public override double Area()
    {
        return pi * x * x;
    }
}

class Cylinder : Circle
{
    public Cylinder(double radius, double height)
        : base(radius)
    {
        y = height;
    }

    public override double Area()
    {
        return (2 * base.Area()) + (2 * pi * x * y);
    }
}

class TestShapes
{
    static void Main()
    {
        double radius = 2.5;
        double height = 3.0;

        Circle ring = new Circle(radius);
        Cylinder tube = new Cylinder(radius, height);

        Console.WriteLine("Area of the circle = {0:F2}", ring.Area());
        Console.WriteLine("Area of the cylinder = {0:F2}", tube.Area());

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Area of the circle = 19.63
    Area of the cylinder = 86.39
*/

有关调用基类构造函数的更多示例，请参见 virtual（C# 参考）、override（C# 参考）和 base（C# 参考）。

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
构造函数（C# 编程指南）
析构函数（C# 编程指南）
static（C# 参考）


私有构造函数（C# 编程指南）

[image]

C# 编程指南
私有构造函数（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

私有构造函数是一种特殊的实例构造函数。它通常用在只包含静态成员的类中。如果类具有一个或多个私有构造函数而没有公共构造函数，则其他类（除嵌套类外）无法创建该类的实例。例如：

C# 复制代码
class NLog
{
    // Private Constructor:
    private NLog() { }

    public static double e = Math.E;  //2.71828...
}

声明空构造函数可阻止自动生成默认构造函数。注意，如果您不对构造函数使用访问修饰符，则在默认情况下它仍为私有构造函数。但是，通常显式地使用 private 修饰符来清楚地表明该类不能被实例化。

当没有实例字段或实例方法（如 Math 类）时或者当调用方法以获得类的实例时，私有构造函数可用于阻止创建类的实例。如果类中的所有方法都是静态的，可考虑使整个类成为静态的。有关更多信息，请参见静态类和静态类成员（C# 编程指南）。

示例

下面是使用私有构造函数的类的示例。

C# 复制代码
public class Counter
{
    private Counter() { }
    public static int currentCount;
    public static int IncrementCount()
    {
        return ++currentCount;
    }
}

class TestCounter
{
    static void Main()
    {
        // If you uncomment the following statement, it will generate
        // an error because the constructor is inaccessible:
        // Counter aCounter = new Counter();   // Error

        Counter.currentCount = 100;
        Counter.IncrementCount();
        Console.WriteLine("New count: {0}", Counter.currentCount);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: New count: 101

注意，如果您取消注释该示例中的以下语句，它将生成一个错误，因为该构造函数受其保护级别的限制而不可访问：

C# 复制代码
// Counter aCounter = new Counter();   // Error

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.11.5 私有构造函数

  • 10.1.1.3 静态类

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
构造函数（C# 编程指南）
析构函数（C# 编程指南）
private（C# 参考）
public（C# 参考）


静态构造函数（C# 编程指南）

[image]

C# 编程指南
静态构造函数（C# 编程指南）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

静态构造函数用于初始化任何静态数据，或用于执行仅需执行一次的特定操作。在创建第一个实例或引用任何静态成员之前，将自动调用静态构造函数。

C# 复制代码
class SimpleClass
{
    // Static variable that must be initialized at run time.
    static readonly long baseline;

    // Static constructor is called at most one time, before any
    // instance constructor is invoked or member is accessed.
    static SimpleClass()
    {
        baseline = DateTime.Now.Ticks;
    }
}

静态构造函数具有以下特点：

  • 静态构造函数既没有访问修饰符，也没有参数。

  • 在创建第一个实例或引用任何静态成员之前，将自动调用静态构造函数来初始化类。

  • 无法直接调用静态构造函数。

  • 在程序中，用户无法控制何时执行静态构造函数。

  • 静态构造函数的典型用途是：当类使用日志文件时，将使用这种构造函数向日志文件中写入项。

  • 静态构造函数在为非托管代码创建包装类时也很有用，此时该构造函数可以调用 LoadLibrary 方法。

  • 如果静态构造函数引发异常，运行时将不会再次调用该构造函数，并且在程序运行所在的应用程序域的生存期内，类型将保持未初始化。

示例

在此示例中，类 Bus 有一个静态构造函数和一个静态成员 Drive()。当调用 Drive() 时，将调用静态构造函数来初始化类。

C# 复制代码
 public class Bus
 {
     // static variable used by all Bus instances
     // Represents the time the first bus of the day starts its route.
     protected static readonly DateTime globalStartTime;

     // Instance readonly variable
     protected int RouteNumber { get; set; }

     // Static constructor to initialize static variable.
     // It is invoked before the first instance constructor is called.
     static Bus()
     {
         globalStartTime = DateTime.Now;
         Console.WriteLine("Static ctor sets global start time to {0}", globalStartTime.ToLongTimeString());
     }

     // Instance constructor
     public Bus(int routeNum)
     {
         RouteNumber = routeNum;
         Console.WriteLine("{0} is created.", RouteNumber);
     }

     // Instance method.
     public void Drive()
     {
         TimeSpan elapsedTime = DateTime.Now - globalStartTime;

         // For demonstration purposes we treat milliseconds as minutes to
         // simulate actual bus times. Do not do this in your actual bus schedule program!
         Console.WriteLine("{0} is starting its route {1:N2} minutes after global start time {2}.",
                                 this.RouteNumber,
                                 elapsedTime.TotalMilliseconds,
                                 globalStartTime.ToShortTimeString());
     }
 }

 class TestBus
 {
     static void Main()
     {

         Bus bus = new Bus(71);
         bus.Drive();

         // Wait for next bus to warm up.
         System.Threading.Thread.Sleep(25);

         Bus bus2 = new Bus(72);
         bus2.Drive();

         // Keep the console window open in debug mode.
         System.Console.WriteLine("Press any key to exit.");
         System.Console.ReadKey();
     }
 }
 /* Output:
     Static ctor sets global start time to 10:04:08 AM
     71 is created.
     71 is starting its route 21.00 minutes after global start time 10:04 AM.
     72 is created.
     72 is starting its route 46.00 minutes after global start time 10:04 AM.
*/

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
构造函数（C# 编程指南）
析构函数（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增加了有关静态构造函数中的异常的项目符号内容。 | 内容 Bug 修复


如何：编写复制构造函数（C# 编程指南）

[image]

C# 编程指南
如何：编写复制构造函数（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

与有些语言不同，C# 不提供复制构造函数。如果您创建了新的对象并希望从现有对象复制值，您必须自行编写适当的方法。

示例

在本示例中，Person 类包含一个构造函数，该构造函数接受另一个 Person 类型的对象作为参数。然后此对象的字段中的内容将分配给新对象中的字段。

C# 复制代码
class Person
{
    private string name;
    private int age;

    // Copy constructor.
    public Person(Person previousPerson)
    {
        name = previousPerson.name;
        age = previousPerson.age;
    }

    // Instance constructor.
    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
    }

    // Get accessor.
    public string Details
    {
        get
        {
            return name + " is " + age.ToString();
        }
    }
}

class TestPerson
{
    static void Main()
    {
        // Create a new person object.
        Person person1 = new Person("George", 40);

        // Create another new object, copying person.
        Person person2 = new Person(person1);
        Console.WriteLine(person2.Details);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: George is 40

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
构造函数（C# 编程指南）
析构函数（C# 编程指南）
ICloneable


析构函数（C# 编程指南）

[image]

C# 编程指南
析构函数（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

析构函数用于析构类的实例。

备注

  • 不能在结构中定义析构函数。只能对类使用析构函数。

  • 一个类只能有一个析构函数。

  • 无法继承或重载析构函数。

  • 无法调用析构函数。它们是被自动调用的。

  • 析构函数既没有修饰符，也没有参数。

例如，下面是类 Car 的析构函数的声明：

C# 复制代码
class Car
{
    ~Car()  // destructor
    {
        // cleanup statements...
    }
}

该析构函数隐式地对对象的基类调用 Finalize。这样，前面的析构函数代码被隐式地转换为以下代码：

复制代码
protected override void Finalize()
{
    try
    {
        // Cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}

这意味着对继承链中的所有实例递归地调用 Finalize 方法（从派生程度最大的到派生程度最小的）。

说明：

不应使用空析构函数。如果类包含析构函数，Finalize 队列中则会创建一个项。调用析构函数时，将调用垃圾回收器来处理该队列。如果析构函数为空，只会导致不必要的性能损失。

程序员无法控制何时调用析构函数，因为这是由垃圾回收器决定的。垃圾回收器检查是否存在应用程序不再使用的对象。如果垃圾回收器认为某个对象符合析构，则调用析构函数（如果有）并回收用来存储此对象的内存。程序退出时也会调用析构函数。

可以通过调用 Collect 强制进行垃圾回收，但大多数情况下应避免这样做，因为这样会导致性能问题。

使用析构函数释放资源

通常，与运行时不进行垃圾回收的开发语言相比，C# 无需太多的内存管理。这是因为 .NET Framework 垃圾回收器会隐式地管理对象的内存分配和释放。但是，当应用程序封装窗口、文件和网络连接这类非托管资源时，应当使用析构函数释放这些资源。当对象符合析构时，垃圾回收器将运行对象的 Finalize 方法。

资源的显式释放

如果您的应用程序在使用昂贵的外部资源，我们还建议您提供一种在垃圾回收器释放对象前显式地释放资源的方式。可通过实现来自 IDisposable 接口的 Dispose 方法来完成这一点，该方法为对象执行必要的清理。这样可大大提高应用程序的性能。即使有这种对资源的显式控制，析构函数也是一种保护措施，可用来在对 Dispose 方法的调用失败时清理资源。

有关清理资源的更多详细信息，请参见下列主题：

  • 清理非托管资源

  • 实现 Dispose 方法

  • using 语句（C# 参考）

示例

下面的示例创建三个类，这三个类构成了一个继承链。类 First 是基类，Second 是从 First 派生的，而 Third 是从 Second 派生的。这三个类都有析构函数。在 Main() 中，创建了派生程度最大的类的实例。注意：程序运行时，这三个类的析构函数将自动被调用，并且是按照从派生程度最大的到派生程度最小的次序调用。

C# 复制代码
class First
{
    ~First()
    {
        System.Diagnostics.Trace.WriteLine("First's destructor is called.");
    }
}

class Second : First
{
    ~Second()
    {
        System.Diagnostics.Trace.WriteLine("Second's destructor is called.");
    }
}

class Third : Second
{
    ~Third()
    {
        System.Diagnostics.Trace.WriteLine("Third's destructor is called.");
    }
}

class TestDestructors
{
    static void Main()
    {
        Third t = new Third();
    }

}
/* Output (to VS Output Window):
    Third's destructor is called.
    Second's destructor is called.
    First's destructor is called.
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.6 析构函数

  • 10.3.9.4 为析构函数保留的成员名称

  • 10.13 析构函数（类）

  • 11.3.9 析构函数（结构）

请参见

概念

C# 编程指南

参考

构造函数（C# 编程指南）
IDisposable

其他资源

垃圾回收


对象和集合初始值设定项（C# 编程指南）

[image]

C# 语言参考
对象和集合初始值设定项（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

使用对象初始值设定项可以在创建对象时向对象的任何可访问的字段或属性分配值，而无需显式调用构造函数。下面的示例演示如何将对象初始值设定项用于命名类型。请注意，在 Test 类中使用了自动实现的属性。有关更多信息，请参见自动实现的属性（C# 编程指南）。

C# 复制代码
private class Cat
{
    // Auto-implemented properties
    public int Age { get; set; }
    public string Name { get; set; }
}

static void MethodA()
{
    // Object initializer
    Cat cat = new Cat { Age = 10, Name = "Sylvester" };
}

将对象初始值设定项用于匿名类型

尽管对象初始值设定项可以用在任何上下文中，但它们在 LINQ 查询表达式中尤其有用。查询表达式经常使用匿名类型，而这些类型只能使用对象初始值设定项进行初始化。在 select 子句中，查询表达式可以将原始序列的对象转换为可能具有不同的值和形式的对象。如果您只想存储某个序列中每个对象的部分信息，这会非常有用。在下面的示例中，假定某个产品对象 (p) 包含很多字段和方法，而您只想创建包含产品名称和单价的对象序列。

C# 复制代码
var productInfos =
    from p in products
    select new { p.ProductName, p.UnitPrice };

当执行此查询时，productInfos 变量将包含一个对象序列，而这些对象可以在 foreach 语句中访问，如下面的示例所示：

复制代码
foreach(var p in productInfos){...}

新的匿名类型中的每个对象都具有两个公共属性，这两个属性具有与原始对象中的属性或字段相同的名称。还可以在创建匿名类型时重命名字段；下面的示例将 UnitPrice 字段重命名为 Price。

复制代码
select new {p.ProductName, Price = p.UnitPrice};

将对象初始值设定项用于可为 null 的类型

将集合初始值设定项用于可为 null 的结构会导致编译时错误。

集合初始值设定项

使用集合初始值设定项可以在初始化一个实现了 IEnumerable 的集合类时指定一个或多个元素初始值设定项。元素初始值设定项可以是简单的值，也可以是表达式或对象初始值设定项。通过使用集合初始值设定项，您将无需在源代码中指定多个对该类的 Add 方法的调用；编译器会添加这些调用。

下面的示例演示了两个简单的集合初始值设定项：

复制代码
    List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };

下面的集合初始值设定项使用对象初始值设定项来初始化在前面的一个示例中定义的 Cat 类的对象。请注意，各个对象初始值设定项被分别括在大括号中，并且用逗号分隔。

C# 复制代码
List<Cat> cats = new List<Cat>
{
    new Cat(){ Name="Sylvester", Age=8 },
    new Cat(){ Name="Whiskers", Age=2},
    new Cat() { Name="Sasha", Age=14}
};

如果集合的 Add 方法允许，可以将 null 指定为集合初始值设定项中的一个元素。

C# 复制代码
List<Cat> moreCats = new List<Cat>
{
    new Cat(){ Name="Furrytail", Age=5 },
    new Cat(){ Name="Peaches", Age=4},
    null
};

请参见

概念

C# 编程指南
LINQ 查询表达式（C# 编程指南）

参考

匿名类型（C# 编程指南）


如何：在不调用构造函数的情况下初始化对象（C# 编程指南）

[image]

C# 语言参考
如何：在不调用构造函数的情况下初始化对象（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可以使用对象初始值设定项以声明性方式初始化类型对象，而无需调用类型的构造函数。

下面的示例演示如何将对象初始值设定项用于命名对象。匿名类型必须用对象初始值设定项加以声明。有关更多信息，请参见如何：在查询中返回元素属性的子集（C# 编程指南）。

示例

下面的示例演示如何使用对象初始值设定项初始化单个新的 StudentName 类型。

C# 复制代码
StudentName student = new StudentName
{
    FirstName = "Craig",
    LastName = "Playstead",
    ID = 116
};

下面的示例演示如何使用集合初始值设定项初始化一个 StudentName 类型集合。请注意，集合初始值设定项是一系列由逗号分隔的对象初始值设定项。

C# 复制代码
List<StudentName> students = new List<StudentName>()
{
  new StudentName {FirstName="Craig", LastName="Playstead", ID=116},
  new StudentName {FirstName="Shu", LastName="Ito", ID=112, },
  new StudentName {FirstName="Stefan", LastName="Rißling", ID=113, },
  new StudentName {FirstName="Rajesh", LastName="Rotti", ID=114, }
};

编译代码

若要运行这段代码，请将该类复制并粘贴到已经在 Visual Studio 中创建的 Visual C# 控制台应用程序项目中。默认情况下，此项目针对的是 .NET Framework 3.5 版，并且具有一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。如果项目不满足上面的一个或多个要求，则您可以手动添加它们。有关更多信息，请参见如何：创建 LINQ 项目。

请参见

概念

C# 编程指南

参考

对象和集合初始值设定项（C# 编程指南）


如何：使用集合初始值设定项初始化字典（C# 编程指南）

[image]

C# 编程指南
如何：使用集合初始值设定项初始化字典（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

Dictionary<(Of <(TKey, TValue>)>) 包含键/值对集合。它的 Add 方法采用两个参数，一个用于键，另一个用于值。若要初始化 Dictionary<(Of <(TKey, TValue>)>) 或其 Add 方法采用多个参数的任何集合，请将每组参数括在大括号中，如下面的示例所示。

示例

在下面的代码示例中，使用 Student 类型的实例初始化一个 Dictionary<(Of <(TKey, TValue>)>)。

C# 复制代码
Dictionary<int, StudentName> students = new Dictionary<int, StudentName>()
{
    { 111, new StudentName {FirstName="Sachin", LastName="Karnik", ID=211}},
    { 112, new StudentName {FirstName="Dina", LastName="Salimzianova", ID=317, }},
    { 113, new StudentName {FirstName="Andy", LastName="Ruth", ID=198, }}
};

请注意每个对象初始值设定项中的三对大括号。最内层的大括号括起了 Scores 列表的对象初始值设定项。下一组大括号括起了 Student 的初始值设定项，而最外层的大括号括起了将要添加到 students Dictionary<(Of <(TKey, TValue>)>) 中的键/值对的初始值设定项。最后，整个集合初始值设定项被括在一对大括号内。

编译代码

若要运行这段代码，请将该类复制并粘贴到已经在 Visual Studio 中创建的 Visual C# 控制台应用程序项目中。默认情况下，此项目针对的是 .NET Framework 3.5 版，并且具有一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。如果项目不满足上面的一个或多个要求，则您可以手动添加它们。有关更多信息，请参见如何：创建 LINQ 项目。

请参见

概念

C# 编程指南

参考

对象和集合初始值设定项（C# 编程指南）


嵌套类型（C# 编程指南）

[image]

C# 编程指南
嵌套类型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在类或结构内部定义的类型称为嵌套类型。例如：

C# 复制代码
class Container
{
    class Nested
    {
        Nested() { }
    }
}

不管外部类型是类还是结构，嵌套类型均默认为 private，但是可以设置为 public、protected internal、protected、internal 或 private。在上面的示例中，Nested 对外部类型是不可访问的，但可以设置为 public，如下所示：

C# 复制代码
class Container
{
    public class Nested
    {
        Nested() { }
    }
}

嵌套类型（或内部类型）可访问包含类型（或外部类型）。若要访问包含类型，请将其作为构造函数传递给嵌套类型。例如：

C# 复制代码
public class Container
{
    public class Nested
    {
        private Container parent;

        public Nested()
        {
        }
        public Nested(Container parent)
        {
            this.parent = parent;
        }
    }
}

嵌套类型可访问包含类型的私有成员和受保护的成员（包括所有继承的私有成员或受保护的成员）。

在前面的声明中，类 Nested 的完整名称为 Container.Nested。这是用来创建嵌套类的新实例的名称，如下所示：

C# 复制代码
Container.Nested nest = new Container.Nested();

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
访问修饰符（C# 编程指南）
构造函数（C# 编程指南）


分部类和方法（C# 编程指南）

[image]

C# 编程指南
分部类和方法（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

可以将类或结构、接口或方法的定义拆分到两个或多个源文件中。每个源文件包含类型或方法定义的一部分，编译应用程序时将把所有部分组合起来。

分部类

在以下几种情况下需要拆分类定义：

  • 处理大型项目时，使一个类分布于多个独立文件中可以让多位程序员同时对该类进行处理。

  • 使用自动生成的源时，无需重新创建源文件便可将代码添加到类中。Visual Studio 在创建 Windows 窗体、Web 服务包装代码等时都使用此方法。无需修改 Visual Studio 创建的文件，就可创建使用这些类的代码。

  • 若要拆分类定义，请使用 partial 关键字修饰符，如下所示：

C# 复制代码
public partial class Employee
{
    public void DoWork()
    {
    }
}

public partial class Employee
{
    public void GoToLunch()
    {
    }
}

partial 关键字指示可在命名空间中定义该类、结构或接口的其他部分。所有部分都必须使用 partial 关键字。在编译时，各个部分都必须可用来形成最终的类型。各个部分必须具有相同的可访问性，如 public、private 等。

如果将任意部分声明为抽象的，则整个类型都被视为抽象的。如果将任意部分声明为密封的，则整个类型都被视为密封的。如果任意部分声明基类型，则整个类型都将继承该类。

指定基类的所有部分必须一致，但忽略基类的部分仍继承该基类型。各个部分可以指定不同的基接口，最终类型将实现所有分部声明所列出的全部接口。在某一分部定义中声明的任何类、结构或接口成员可供所有其他部分使用。最终类型是所有部分在编译时的组合。

说明：

partial 修饰符不可用于委托或枚举声明中。

下面的示例演示嵌套类型可以是分部的，即使它们所嵌套于的类型本身并不是分部的也如此。

C# 复制代码
class Container
{
    partial class Nested
    {
        void Test() { }
    }
    partial class Nested
    {
        void Test2() { }
    }
}

编译时将对分部类型定义的属性进行合并。例如，请考虑下列声明：

C# 复制代码
[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

它们等效于以下声明：

C# 复制代码
[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

将从所有分部类型定义中对以下内容进行合并：

  • XML 注释

  • 接口

  • 泛型类型参数属性

  • 类属性

  • 成员

例如，请考虑下列声明：

C# 复制代码
partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }

它们等效于以下声明：

C# 复制代码
class Earth : Planet, IRotate, IRevolve { }

限制

处理分部类定义时需遵循下面的几个规则：

  • 要作为同一类型的各个部分的所有分部类型定义都必须使用 partial 进行修饰。例如，下面的类声明将生成错误：

    C# 复制代码
    public partial class A { }
//public class A { }  // Error, must also be marked partial

  • partial 修饰符只能出现在紧靠关键字 class、struct 或 interface 前面的位置。

  • 分部类型定义中允许使用嵌套的分部类型，如下面的示例中所示：

    C# 复制代码
    partial class ClassWithNestedClass
{
    partial class NestedClass { }
}

partial class ClassWithNestedClass
{
    partial class NestedClass { }
}

  • 要成为同一类型的各个部分的所有分部类型定义都必须在同一程序集和同一模块（.exe 或 .dll 文件）中进行定义。分部定义不能跨越多个模块。

  • 类名和泛型类型参数在所有的分部类型定义中都必须匹配。泛型类型可以是分部的。每个分部声明都必须以相同的顺序使用相同的参数名。

  • 下面的用于分部类型定义中的关键字是可选的，但是如果某关键字出现在一个分部类型定义中，则该关键字不能与在同一类型的其他分部定义中指定的关键字冲突：

    • public

    • private

    • protected

    • internal

    • abstract

    • sealed

    • 基类

    • new 修饰符（嵌套部分）

    • 泛型约束

      有关更多信息，请参见类型参数的约束（C# 编程指南）。

示例 1

说明

下面的示例在一个分部类定义中声明 CoOrds 类的字段和构造函数，在另一个分部类定义中声明成员 PrintCoOrds。

代码

C# 复制代码
public partial class CoOrds
{
    private int x;
    private int y;

    public CoOrds(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

public partial class CoOrds
{
    public void PrintCoOrds()
    {
        Console.WriteLine("CoOrds: {0},{1}", x, y);
    }

}

class TestCoOrds
{
    static void Main()
    {
        CoOrds myCoOrds = new CoOrds(10, 15);
        myCoOrds.PrintCoOrds();

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: CoOrds: 10,15

示例 2

说明

从下面的示例可以看出，您也可以开发分部结构和接口。

代码

C# 复制代码
partial interface ITest
{
    void Interface_Test();
}

partial interface ITest
{
    void Interface_Test2();
}

partial struct S1
{
    void Struct_Test() { }
}

partial struct S1
{
    void Struct_Test2() { }
}

分部方法

分部类或结构可以包含分部方法。类的一个部分包含方法的签名。可以在同一部分或另一个部分中定义可选实现。如果未提供该实现，则会在编译时移除方法以及对方法的所有调用。

分部方法使类的某个部分的实施者能够定义方法（类似于事件）。类的另一部分的实施者可以决定是否实现该方法。如果未实现该方法，编译器将移除方法签名以及对该方法的所有调用。因此，分部类中的任何代码都可以随意地使用分部方法，即使未提供实现也是如此。如果调用了未实现的方法，将不会导致编译时错误或运行时错误。

在自定义生成的代码时，分部方法特别有用。这些方法允许保留方法名称和签名，因此生成的代码可以调用方法，而开发人员可以决定是否实现方法。与分部类非常类似，分部方法使代码生成器创建的代码和开发人员创建的代码能够协同工作，而不会产生运行时开销。

分部方法声明由两个部分组成：定义和实现。它们可以位于分部类的不同部分中，也可以位于同一部分中。如果不存在实现声明，则编译器将优化定义声明和对方法的所有调用。

复制代码
// Definition in file1.cs
partial void onNameChanged();

// Implementation in file2.cs
partial void onNameChanged()
{
  // method body
}

  • 分部方法声明必须以上下文关键字 partial 开头，并且方法必须返回 void。

  • 分部方法可以有 ref 参数，但不能有 out 参数。

  • 分部方法为隐式 private 方法，因此不能为 virtual 方法。

  • 分部方法不能为 extern 方法，因为主体的存在确定了方法是在定义还是在实现。

  • 分部方法可以有 static 和 unsafe 修饰符。

  • 分部方法可以为泛型的。约束将放在定义分部方法声明上，但也可以选择重复放在实现声明上。参数和类型参数名称在实现声明和定义声明中不必相同。

  • 不能将委托转换为分部方法。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.2 分部类型

请参见

概念

C# 编程指南

参考

类（C# 编程指南）
结构（C# 编程指南）
接口（C# 编程指南）
分部（类型）（C# 参考）


匿名类型（C# 编程指南）

[image]

C# 编程指南
匿名类型（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

匿名类型提供了一种方便的方法，可用来将一组只读属性封装到单个对象中，而无需首先显式定义一个类型。类型名由编译器生成，并且不能在源代码级使用。这些属性的类型由编译器推断。下面的示例演示一个用两个分别名为 Amount 和 Message 的属性初始化的匿名类型。

复制代码
var v = new { Amount = 108, Message = "Hello" };

匿名类型通常用在查询表达式的 select 子句中，以便返回源序列中每个对象的属性子集。有关查询的更多信息，请参见 LINQ 查询表达式（C# 编程指南）。

匿名类型是使用 new 运算符和对象初始值设定项创建的。有关对象初始值设定项的更多信息，请参见对象和集合初始值设定项（C# 编程指南）。

匿名类型是由一个或多个公共只读属性组成的类类型。不允许包含其他种类的类成员（如方法或事件）。匿名类型不能强制转换为除 object 以外的任何接口或类型。

最常见的方案是用其他类型的一些属性初始化匿名类型。在下面的示例中，假定一个名为 Product 的类包含 Color 和 Price 属性以及其他几个您不感兴趣的属性。Products 是一个 Product 对象集合。匿名类型声明以 new 关键字开始。它初始化了一个只使用 Product 的两个属性的新类型。这将导致在查询中返回较少数量的数据。

如果您没有在匿名类型中指定成员名称，编译器会为匿名类型成员指定与用于初始化这些成员的属性相同的名称。必须为使用表达式初始化的属性提供名称。

C# 复制代码
var productQuery =
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

在将匿名类型分配给变量时，必须使用 var 构造初始化该变量。这是因为只有编译器能够访问匿名类型的基础名称。有关 var 的更多信息，请参见隐式类型的局部变量（C# 编程指南）。

备注

匿名类型是直接从对象派生的引用类型。尽管应用程序无法访问匿名类型，但编译器仍会为其提供一个名称。从公共语言运行时的角度来看，匿名类型与任何其他引用类型没有什么不同，唯一区别在于匿名类型无法强制转换为除 object 以外的任何类型。

如果两个或更多个匿名类型以相同的顺序具有相同数量和种类的属性，则编译器会将这些匿名类型视为相同的类型，并且它们共享编译器生成的相同类型信息。

匿名类型具有方法范围。若要向方法边界外部传递一个匿名类型或一个包含匿名类型的集合，必须首先将匿名类型强制转换为对象。但是，这会使匿名类型的强类型化无效。如果必须存储查询结果或者必须将查询结果传递到方法边界外部，请考虑使用普通的命名结构或类而不是匿名类型。

匿名类型不能像属性一样包含不安全类型。

由于匿名类型上的 Equals 和 GetHashCode 方法是根据属性的 Equals 和 GetHashcode 定义的，因此仅当同一匿名类型的两个实例的所有属性都相等时，这两个实例才相等。

请参见

概念

C# 编程指南
LINQ 查询表达式（C# 编程指南）

参考

对象和集合初始值设定项（C# 编程指南）

其他资源

C# 中的 LINQ 入门

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 在介绍性文字和“备注”部分增加了关于强制转换限制的信息。 | 信息补充。


如何：在查询中返回元素属性的子集（C# 编程指南）

[image]

C# 编程指南
如何：在查询中返回元素属性的子集（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

当下列两个条件都满足时，可在查询表达式中使用匿名类型：

  • 您只想返回每个源元素的某些属性。

  • 您无需在执行查询的方法的范围之外存储查询结果。

如果您只想从每个源元素中返回一个属性或字段，则只需在 select 子句中使用点运算符。例如，若要只返回每个 student 的 ID，可以按如下方式编写 select 子句：

复制代码
select student.ID;

示例

下面的示例演示如何使用匿名类型只返回每个源元素的符合指定条件的属性子集。

C# 复制代码
private static void QueryByScore()
{
    // Create the query. var is required because
    // the query produces a sequence of anonymous types.
    var queryHighScores =
        from student in students
        where student.ExamScores[0] > 95
        select new { student.FirstName, student.LastName };

    // Execute the query.
    foreach (var obj in queryHighScores)
    {
        // The anonymous type's properties were not named. Therefore
        // they have the same names as the Student properties.
        Console.WriteLine(obj.FirstName + ", " + obj.LastName);
    }
}
/* Output:
Adams, Terry
Fakhouri, Fadi
Garcia, Cesar
Omelchenko, Svetlana
Zabokritski, Eugene
*/

请注意，如果未指定名称，则匿名类型将使用源元素的名称作为其属性名称。若要为匿名类型中的属性指定新名称，请按如下方式编写 select 语句：

复制代码
select new { First = student.FirstName, Last = student.LastName };

如果您在上一个示例中这样做，则 Console.WriteLine 语句也必须更改：

复制代码
Console.WriteLine(student.First + " " + student.Last);

编译代码

  • 若要运行这段代码，请将该类复制并粘贴到已经在 Visual Studio 中创建的 Visual C# 控制台应用程序项目中。默认情况下，此项目针对 .NET Framework 3.5 版，并且将具有一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。如果项目不满足上面的一个或多个要求，则您可以手动添加它们。有关更多信息，请参见如何：创建 LINQ 项目。

请参见

概念

C# 编程指南
LINQ 查询表达式（C# 编程指南）

参考

匿名类型（C# 编程指南）


接口（C# 编程指南）

[image]

C# 编程指南
接口（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

接口使用 interface 关键字进行定义，如下面的示例所示：

C# 复制代码
interface IEquatable<T>
{
    bool Equals(T obj);
}

接口描述的是可属于任何类或结构的一组相关功能。接口可由方法、属性、事件、索引器或这四种成员类型的任意组合构成。接口不能包含字段。接口成员一定是公共的。

当类或结构继承接口时，意味着该类或结构为该接口定义的所有成员提供实现。接口本身不提供类或结构能够以继承基类功能的方式继承的任何功能。但是，如果基类实现接口，派生类将继承该实现。

类和结构可以按照类继承基类或结构的类似方式继承接口，但有两个例外：

  • 类或结构可继承多个接口。

  • 类或结构继承接口时，仅继承方法名称和签名，因为接口本身不包含实现。例如：

    C# 复制代码
    public class Car : IEquatable<Car>
{
    public string Make {get; set;}
    public string Model { get; set; }
    public string Year { get; set; }

    // Implementation of IEquatable<T> interface
    public bool Equals(Car car)
    {
        if (this.Make == car.Make &&
            this.Model == car.Model &&
            this.Year == car.Year)
        {
            return true;
        }
        else
            return false;
    }
}

若要实现接口成员，类中的对应成员必须是公共的、非静态的，并且与接口成员具有相同的名称和签名。类的属性和索引器可以为接口上定义的属性或索引器定义额外的访问器。例如，接口可以声明一个带有 get 访问器的属性，而实现该接口的类可以声明同时带有 get 和 set 访问器的同一属性。但是，如果属性或索引器使用显式实现，则访问器必须匹配。

接口和接口成员是抽象的；接口不提供默认实现。有关更多信息，请参见抽象类、密封类和类成员。

IEquatable<(Of <(T>)>) 接口向对象的用户宣布该对象可以确定其是否与同一类型的其他对象相等，而接口的用户不需要知道相关的实现方式。

接口可以继承其他接口。类可以通过其继承的基类或接口多次继承某个接口。在这种情况下，如果将该接口声明为新类的一部分，则该类只能实现该接口一次。如果没有将继承的接口声明为新类的一部分，其实现将由声明它的基类提供。基类可以使用虚拟成员实现接口成员；在这种情况下，继承接口的类可通过重写虚拟成员来更改接口行为。有关虚拟成员的更多信息，请参见多态性。

接口概述

接口具有下列属性：

  • 接口类似于抽象基类：继承接口的任何非抽象类型都必须实现接口的所有成员。

  • 不能直接实例化接口。

  • 接口可以包含事件、索引器、方法和属性。

  • 接口不包含方法的实现。

  • 类和结构可从多个接口继承。

  • 接口自身可从多个接口继承。

本节内容

显式接口实现（C# 编程指南）

解释如何创建特定于接口的类成员。

如何：显式实现接口成员（C# 编程指南）

提供一个有关如何显式实现接口成员的示例。

如何：使用继承显式实现接口成员（C# 编程指南）

提供一个有关如何通过继承显式实现接口成员的示例。

相关章节

接口属性（C# 编程指南）

解释如何声明接口上的属性。

接口中的索引器（C# 编程指南）

解释如何声明接口上的索引器。

如何：实现接口事件（C# 编程指南）

演示接口如何声明事件。

类和结构（C# 编程指南）

描述 C# 如何使用对象、类和结构。

继承（C# 编程指南）

解释 C# 如何实现继承。

方法（C# 编程指南）

解释 C# 编程模型中命名方法的概念。

多态性（C# 编程指南）

描述如何在 C# 中将类用作一个以上的类型。

抽象类、密封类及类成员（C# 编程指南）

描述 abstract 和 sealed 关键字如何影响继承。

属性（C# 编程指南）

解释 C# 中属性的概念。

事件（C# 编程指南）

解释 C# 中事件的概念。

索引器（C# 编程指南）

解释 C# 中索引器的概念。

请参见

概念

C# 编程指南

参考

继承（C# 编程指南）


显式接口实现（C# 编程指南）

[image]

C# 编程指南
显式接口实现（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

如果类实现两个接口，并且这两个接口包含具有相同签名的成员，那么在类中实现该成员将导致两个接口都使用该成员作为它们的实现。例如：

C# 复制代码
interface IControl
{
    void Paint();
}
interface ISurface
{
    void Paint();
}
class SampleClass : IControl, ISurface
{
    // Both ISurface.Paint and IControl.Paint call this method.
    public void Paint()
    {
    }
}

然而，如果两个接口成员执行不同的函数，那么这可能会导致其中一个接口的实现不正确或两个接口的实现都不正确。可以显式地实现接口成员 -- 即创建一个仅通过该接口调用并且特定于该接口的类成员。这是使用接口名称和一个句点命名该类成员来实现的。例如：

C# 复制代码
public class SampleClass : IControl, ISurface
{
    void IControl.Paint()
    {
        System.Console.WriteLine("IControl.Paint");
    }
    void ISurface.Paint()
    {
        System.Console.WriteLine("ISurface.Paint");
    }
}

类成员 IControl.Paint 只能通过 IControl 接口使用，ISurface.Paint 只能通过 ISurface 使用。两个方法实现都是分离的，都不可以直接在类中使用。例如：

C# 复制代码
SampleClass obj = new SampleClass();
//obj.Paint();  // Compiler error.

IControl c = (IControl)obj;
c.Paint();  // Calls IControl.Paint on SampleClass.

ISurface s = (ISurface)obj;
s.Paint(); // Calls ISurface.Paint on SampleClass.

显式实现还用于解决两个接口分别声明具有相同名称的不同成员（如属性和方法）的情况：

C# 复制代码
interface ILeft
{
    int P { get;}
}
interface IRight
{
    int P();
}

为了同时实现两个接口，类必须对属性 P 和/或方法 P 使用显式实现以避免编译器错误。例如：

C# 复制代码
class Middle : ILeft, IRight
{
    public int P() { return 0; }
    int ILeft.P { get { return 0; } }
}

请参见

概念

C# 编程指南

参考

类和结构（C# 编程指南）
接口（C# 编程指南）
继承（C# 编程指南）


如何：显式实现接口成员（C# 编程指南）

[image]

C# 编程指南
如何：显式实现接口成员（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例声明一个 接口 IDimensions 和一个类 Box，该类显式实现接口成员 getLength 和 getWidth。通过接口实例 dimensions 访问这些成员。

示例

C# 复制代码
interface IDimensions
{
    float getLength();
    float getWidth();
}

class Box : IDimensions
{
    float lengthInches;
    float widthInches;

    Box(float length, float width)
    {
        lengthInches = length;
        widthInches = width;
    }
    // Explicit interface member implementation:
    float IDimensions.getLength()
    {
        return lengthInches;
    }
    // Explicit interface member implementation:
    float IDimensions.getWidth()
    {
        return widthInches;
    }

    static void Main()
    {
        // Declare a class instance box1:
        Box box1 = new Box(30.0f, 20.0f);

        // Declare an interface instance dimensions:
        IDimensions dimensions = (IDimensions)box1;

        // The following commented lines would produce compilation
        // errors because they try to access an explicitly implemented
        // interface member from a class instance:
        //System.Console.WriteLine("Length: {0}", box1.getlength());
        //System.Console.WriteLine("Width: {0}", box1.getwidth());

        // Print out the dimensions of the box by calling the methods
        // from an instance of the interface:
        System.Console.WriteLine("Length: {0}", dimensions.getLength());
        System.Console.WriteLine("Width: {0}", dimensions.getWidth());
    }
}
/* Output:
    Length: 30
    Width: 20
*/

可靠编程

  • 请注意 Main 方法中下列代码行被注释掉，因为它们将产生编译错误。显式实现的接口成员不能从类实例访问：

    C# 复制代码
    //System.Console.WriteLine("Length: {0}", box1.getlength());
//System.Console.WriteLine("Width: {0}", box1.getwidth());

  • 还请注意，Main 方法中的下列代码行成功输出框的尺寸，因为这些方法是从接口实例调用的：

    C# 复制代码
    System.Console.WriteLine("Length: {0}", dimensions.getLength());
System.Console.WriteLine("Width: {0}", dimensions.getWidth());

请参见

任务

如何：使用继承显式实现接口成员（C# 编程指南）

概念

C# 编程指南

参考

类和结构（C# 编程指南）
接口（C# 编程指南）


如何：使用继承显式实现接口成员（C# 编程指南）

[image]

C# 编程指南
如何：使用继承显式实现接口成员（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

显式接口实现还允许程序员实现具有相同成员名称的两个接口，并为每个接口成员各提供一个实现。本示例同时以公制单位和英制单位显示框的尺寸。Box 类实现 IEnglishDimensions 和 IMetricDimensions 两个接口，它们表示不同的度量系统。两个接口有相同的成员名称 Length 和 Width。

示例

C# 复制代码
// Declare the English units interface:
interface IEnglishDimensions
{
    float Length();
    float Width();
}

// Declare the metric units interface:
interface IMetricDimensions
{
    float Length();
    float Width();
}

// Declare the Box class that implements the two interfaces:
// IEnglishDimensions and IMetricDimensions:
class Box : IEnglishDimensions, IMetricDimensions
{
    float lengthInches;
    float widthInches;

    public Box(float length, float width)
    {
        lengthInches = length;
        widthInches = width;
    }

    // Explicitly implement the members of IEnglishDimensions:
    float IEnglishDimensions.Length()
    {
        return lengthInches;
    }

    float IEnglishDimensions.Width()
    {
        return widthInches;
    }

    // Explicitly implement the members of IMetricDimensions:
    float IMetricDimensions.Length()
    {
        return lengthInches * 2.54f;
    }

    float IMetricDimensions.Width()
    {
        return widthInches * 2.54f;
    }

    static void Main()
    {
        // Declare a class instance box1:
        Box box1 = new Box(30.0f, 20.0f);

        // Declare an instance of the English units interface:
        IEnglishDimensions eDimensions = (IEnglishDimensions)box1;

        // Declare an instance of the metric units interface:
        IMetricDimensions mDimensions = (IMetricDimensions)box1;

        // Print dimensions in English units:
        System.Console.WriteLine("Length(in): {0}", eDimensions.Length());
        System.Console.WriteLine("Width (in): {0}", eDimensions.Width());

        // Print dimensions in metric units:
        System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
        System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
    }
}
/* Output:
    Length(in): 30
    Width (in): 20
    Length(cm): 76.2
    Width (cm): 50.8
*/

可靠编程

如果希望默认度量采用英制单位，请正常实现 Length 和 Width 这两个方法，并从 IMetricDimensions 接口显式实现 Length 和 Width 方法：

C# 复制代码
// Normal implementation:
public float Length()
{
    return lengthInches;
}
public float Width()
{
    return widthInches;
}

// Explicit implementation:
float IMetricDimensions.Length()
{
    return lengthInches * 2.54f;
}
float IMetricDimensions.Width()
{
    return widthInches * 2.54f;
}

这种情况下，可以从类实例访问英制单位，而从接口实例访问公制单位：

C# 复制代码
public static void Test()
{
    Box box1 = new Box(30.0f, 20.0f);
    IMetricDimensions mDimensions = (IMetricDimensions)box1;

    System.Console.WriteLine("Length(in): {0}", box1.Length());
    System.Console.WriteLine("Width (in): {0}", box1.Width());
    System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
    System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}

请参见

任务

如何：显式实现接口成员（C# 编程指南）

概念

C# 编程指南

参考

类和结构（C# 编程指南）
接口（C# 编程指南）


索引器（C# 编程指南）

[image]

C# 编程指南
索引器（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

索引器允许类或结构的实例就像数组一样进行索引。索引器类似于属性，不同之处在于它们的访问器采用参数。

在下面的示例中，定义了一个泛型类，并为其提供了简单的 get 和 set 访问器方法（作为分配和检索值的方法）。Program 类为存储字符串创建了此类的一个实例。

C# 复制代码
class SampleCollection<T>
{
    private T[] arr = new T[100];
    public T this[int i]
    {
        get
        {
            return arr[i];
        }
        set
        {
            arr[i] = value;
        }
    }
}

// This class shows how client code uses the indexer
class Program
{
    static void Main(string[] args)
    {
        SampleCollection<string> stringCollection = new SampleCollection<string>();
        stringCollection[0] = "Hello, World";
        System.Console.WriteLine(stringCollection[0]);
    }
}

索引器概述

  • 使用索引器可以用类似于数组的方式为对象建立索引。

  • get 访问器返回值。set 访问器分配值。

  • this 关键字用于定义索引器。

  • value 关键字用于定义由 set 索引器分配的值。

  • 索引器不必根据整数值进行索引，由您决定如何定义特定的查找机制。

  • 索引器可被重载。

  • 索引器可以有多个形参，例如当访问二维数组时。

相关章节

  • 使用索引器（C# 编程指南）

  • 接口中的索引器（C# 编程指南）

  • 属性和索引器之间的比较（C# 编程指南）

  • 非对称访问器可访问性（C# 编程指南）

  • “索引器”示例

  • “索引属性”示例

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.3 索引器

  • 10.2.9.3 为索引器保留的成员名称

  • 10.9 索引器

  • 13.2.4 接口索引器

请参见

概念

C# 编程指南

参考

属性（C# 编程指南）


使用索引器（C# 编程指南）

[image]

C# 编程指南
使用索引器（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

索引器在语法上方便您创建客户端应用程序可将其作为数组访问的类、结构或接口。索引器经常是在主要用于封装内部集合或数组的类型中实现的。例如，假定具有一个名为 TempRecord 的类，此类表示在 24 小时内的 10 个不同时间记录的华氏度。此类包含一个表示温度的 float 类型的名为“temps”的数组和表示记录温度的日期的 DateTime。通过在此类中实现一个索引器，客户端可以通过 float temp = tr[4] 而不是 float temp = tr.temps[4] 语法访问 TempRecord 实例中的温度。索引器表示法不仅简化了客户端应用程序的语法，还使其他开发人员能够更加直观地理解类及其用途。

要声明类或结构上的索引器，请使用 this 关键字，如下例所示：

复制代码
public int this[int index]    // Indexer declaration
{
    // get and set accessors
}

备注

索引器类型及其参数类型必须至少如同索引器本身一样是可访问的。有关可访问级别的更多信息，请参见访问修饰符。

有关如何对接口使用索引器的更多信息，请参见接口索引器。

索引器的签名由其形参的数量和类型组成。它不包括索引器类型或形参名。如果在同一类中声明一个以上的索引器，则它们必须具有不同的签名。

索引器值不属于变量；因此，不能将索引器值作为 ref 或 out 参数进行传递。

要为索引器提供一个其他语言可以使用的名字，请使用声明中的 name 属性。例如：

复制代码
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this [int index]   // Indexer declaration
{
}

此索引器将具有名称 TheItem。不提供名称属性将生成 Item 默认名称。

示例 1

说明

下面的示例说明如何声明私有数组字段、temps 和索引器。使用索引器可直接访问实例 tempRecord[i]。另一种使用索引器的方法是将数组声明为 public 成员并直接访问它的成员 tempRecord.temps[i]。

请注意，当计算索引器的访问时（例如，在 Console.Write 语句中），将调用 get 访问器。因此，如果 get 访问器不存在，将发生编译时错误。

代码

C# 复制代码
class TempRecord
{
    // Array of temperature values
    private float[] temps = new float[10] { 56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
                                            61.3F, 65.9F, 62.1F, 59.2F, 57.5F };

    // To enable client code to validate input
    // when accessing your indexer.
    public int Length
    {
        get { return temps.Length; }
    }
    // Indexer declaration.
    // If index is out of range, the temps array will throw the exception.
    public float this[int index]
    {
        get
        {
            return temps[index];
        }

        set
        {
            temps[index] = value;
        }
    }
}

class MainClass
{
    static void Main()
    {
        TempRecord tempRecord = new TempRecord();
        // Use the indexer's set accessor
        tempRecord[3] = 58.3F;
        tempRecord[5] = 60.1F;

        // Use the indexer's get accessor
        for (int i = 0; i < 10; i++)
        {
           System.Console.WriteLine("Element #{0} = {1}", i, tempRecord[i]);
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();

    }
}

使用其他值进行索引

C# 并不将索引类型限制为整数。例如，对索引器使用字符串可能是有用的。通过搜索集合内的字符串并返回相应的值，可以实现此类索引器。由于访问器可被重载，字符串和整数版本可以共存。

示例 2

说明

在此例中，声明了存储星期几的类。声明了一个 get 访问器，它接受字符串（天名称），并返回相应的整数。例如，星期日将返回 0，星期一将返回 1，等等。

代码

C# 复制代码
// Using a string as an indexer value
class DayCollection
{
    string[] days = { "Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat" };

    // This method finds the day or returns -1
    private int GetDay(string testDay)
    {

        for(int j = 0; j < days.Length - 1; j++)
        {
            if (days[j] == testDay)
            {
                return j;
            }
        }

        throw new System.ArgumentOutOfRangeException(testDay, "testDay must be in the form \"Sun\", \"Mon\", etc");
    }

    // The get accessor returns an integer for a given string
    public int this[string day]
    {
        get
        {
            return (GetDay(day));
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        DayCollection week = new DayCollection();
        System.Console.WriteLine(week["Fri"]);

        // Raises ArgumentOutOfRangeException
        System.Console.WriteLine(week["Made-up Day"]);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
// Output: 5

可靠编程

提高索引器的安全性和可靠性有两种主要的方法：

  • 确保结合某一类型的错误处理策略，以处理万一客户端代码传入无效索引值的情况。在本主题前面的第一个示例中，TempRecord 类提供了 Length 属性，使客户端代码能够在将输入传递给索引器之前对其进行验证。也可以将错误处理代码放入索引器自身内部。确保为用户记录在索引器的访问器中引发的任何异常。有关更多信息，请参见异常设计准则。

  • 应当为 get 和 set 访问器的可访问性设置尽可能多的限制。这一点对 set 访问器尤为重要。有关更多信息，请参见 非对称访问器可访问性（C# 编程指南）。

请参见

任务

“索引器”示例

概念

C# 编程指南

参考

索引器（C# 编程指南）
属性（C# 编程指南）


接口中的索引器（C# 编程指南）

[image]

C# 编程指南
接口中的索引器（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

索引器可在 接口（C# 参考） 上声明。接口索引器的访问器与类索引器的访问器具有以下方面的不同：

  • 接口访问器不使用修饰符。

  • 接口访问器没有体。

因此，访问器的用途是指示索引器是读写、只读还是只写。

以下是接口索引器访问器的示例：

C# 复制代码
public interface ISomeInterface
{
    //...

    // Indexer declaration:
    string this[int index]
    {
        get;
        set;
    }
}

一个索引器的签名必须区别于在同一接口中声明的其他所有索引器的签名。

示例

下面的示例显示如何实现接口索引器。

C# 复制代码
// Indexer on an interface:
public interface ISomeInterface
{
    // Indexer declaration:
    int this[int index]
    {
        get;
        set;
    }
}

// Implementing the interface.
class IndexerClass : ISomeInterface
{
    private int[] arr = new int[100];
    public int this[int index]   // indexer declaration
    {
        get
        {
            // The arr object will throw IndexOutOfRange exception.
            return arr[index];
        }
        set
        {
            arr[index] = value;
        }
    }
}

class MainClass
{
    static void Main()
    {
        IndexerClass test = new IndexerClass();
        // Call the indexer to initialize the elements #2 and #5.
        test[2] = 4;
        test[5] = 32;
        for (int i = 0; i <= 10; i++)
        {
            System.Console.WriteLine("Element #{0} = {1}", i, test[i]);
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    Element #0 = 56.2
    Element #1 = 56.7
    Element #2 = 56.5
    Element #3 = 58.3
    Element #4 = 58.8
    Element #5 = 60.1
    Element #6 = 65.9
    Element #7 = 62.1
    Element #8 = 59.2
    Element #9 = 57.5
*/

在上例中，可以通过使用接口成员的完全限定名来使用显式接口成员实现。例如：

复制代码
public string ISomeInterface.this
{
}

但是，只有当类使用同一索引器签名实现一个以上的接口时，为避免多义性才需要使用完全限定名。例如，如果 Employee 类实现的是两个接口 ICitizen 和 IEmployee，并且这两个接口具有相同的索引器签名，则必须使用显式接口成员实现。即，以下索引器声明：

复制代码
public string IEmployee.this
{
}

在 IEmployee 接口上实现索引器，而以下声明：

复制代码
public string ICitizen.this
{
}

在 ICitizen 接口上实现索引器。

请参见

任务

“索引器”示例

概念

C# 编程指南

参考

索引器（C# 编程指南）
属性（C# 编程指南）
接口（C# 编程指南）


属性和索引器之间的比较（C# 编程指南）

[image]

C# 编程指南
属性和索引器之间的比较（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

索引器与属性类似。除下表中显示的差别外，为属性访问器定义的所有规则同样适用于索引器访问器。

属性 | 索引器
允许像调用公共数据成员一样调用方法。 | 允许对一个对象本身使用数组表示法来访问该对象内部集合中的元素。
可通过简单的名称进行访问。 | 可通过索引器进行访问。
可以为静态成员或实例成员。 | 必须为实例成员。
属性的 get 访问器没有参数。 | 索引器的 get 访问器具有与索引器相同的形参表。
属性的 set 访问器包含隐式 value 参数。 | 除了值参数外，索引器的 set 访问器还具有与索引器相同的形参表。
支持对自动实现的属性（C# 编程指南）使用短语法。 | 不支持短语法。

请参见

概念

C# 编程指南

参考

索引器（C# 编程指南）
属性（C# 编程指南）


枚举类型（C# 编程指南）

[image]

C# 编程指南
枚举类型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

枚举类型（也称为枚举）为定义一组可以赋给变量的命名整数常量提供了一种有效的方法。例如，假设您必须定义一个变量，该变量的值表示一周中的一天。该变量只能存储七个有意义的值。若要定义这些值，可以使用枚举类型。枚举类型是使用 enum 关键字声明的。

C# 复制代码
enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };
enum Months : byte { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };

默认情况下，枚举中每个元素的基础类型是 int。可以使用冒号指定另一种整数值类型，如前面的示例所示。有关可能的类型的完整列表，请参见 enum（C# 参考）。

以下是使用枚举而不使用数值类型的好处：

  • 明确为客户端代码指定哪些值是变量的有效值。

  • 在 Visual Studio 中，IntelliSense 列出定义的值。

如果不为枚举数列表中的元素指定值，则它们的值将以 1 为增量自动递增。在前面的示例中，Days.Sunday 的值为 0，Days.Monday 的值为 1，依此类推。创建新的 Days 对象时，如果不显式为其赋值，则它将具有默认值 Days.Sunday (0)。创建枚举时，应选择最合理的默认值并赋给它一个零值。这便使得只要在创建枚举时未为其显式赋值，则所创建的全部枚举都将具有该默认值。

如果变量 meetingDay 的类型为 Days，则只能将 Days 定义的某个值赋给它（无需显式强制转换）。如果会议日期更改，可以将 Days 中的新值赋给 meetingDay：

C# 复制代码
Days meetingDay = Days.Monday;
//...
meetingDay = Days.Friday;
说明：

可以将任意整数值赋给 meetingDay。例如，代码行 meetingDay = (Days) 42 不会产生错误。但也不应该这样做，因为默认约定的是枚举变量只容纳枚举定义的值之一。将任意值赋给枚举类型的变量很有可能会导致错误。

可以将任意值赋给枚举类型的枚举数列表中的元素，也可以使用计算值：

C# 复制代码
enum MachineState
{
    PowerOff = 0,
    Running = 5,
    Sleeping = 10,
    Hibernating = Sleeping + 5
}

枚举类型作为位标志

可以使用枚举类型定义位标志，从而使该枚举类型的实例可以存储枚举数列表中定义的值的任意组合。（当然，某些组合在您的程序代码中可能没有意义或不允许使用。）

创建位标志枚举的方法是应用 System..::.FlagsAttribute 属性并适当定义一些值，以便可以对这些值执行 AND、OR、NOT 和 XOR 按位运算。在位标志枚举中包含一个值为零（表示“未设置任何标志”）的命名常量。如果零值不表示“未设置任何标志”，则请不要为标志指定零值。

在下面的示例中，定义了 Days 枚举的另一个版本，即 Days2。Days2 具有 Flags 属性，且它的每个值都是 2 的若干次幂，指数依次递增。这样您就可以创建值为 Days2.Tuesday 和 Days2.Thursday 的 Days2 变量。

C# 复制代码
[Flags]
enum Days2
{
    None = 0x0,
    Sunday = 0x1,
    Monday = 0x2,
    Tuesday = 0x4,
    Wednesday = 0x8,
    Thursday = 0x10,
    Friday = 0x20,
    Saturday = 0x40
}
class MyClass
{
    Days2 meetingDays = Days2.Tuesday | Days2.Thursday;
}

若要在某个枚举上设置标志，请使用逻辑 OR 运算符，如下面的示例所示：

C# 复制代码
// Initialize with two flags using bitwise OR.
meetingDays = Days2.Tuesday | Days2.Thursday;

// Set an additional flag using bitwise OR.
meetingDays = meetingDays | Days2.Friday;

Console.WriteLine("Meeting days are {0}", meetingDays);
// Output: Meeting days are Tuesday, Thursday, Friday

// Remove a flag using bitwise XOR.
meetingDays = meetingDays ^ Days2.Tuesday;
Console.WriteLine("Meeting days are {0}", meetingDays);
// Output: Meeting days are Thursday, Friday

若要确定是否设置了特定标志，请使用逻辑 AND 运算，如下面的示例所示：

C# 复制代码
// Test value of flags using bitwise AND.
bool test = (meetingDays & Days2.Thursday) == Days2.Thursday;
Console.WriteLine("Thursday {0} a meeting day.", test == true ? "is" : "is not");
// Output: Thursday is a meeting day.

有关使用 System..::.FlagsAttribute 属性定义枚举类型时需要考虑的事项的更多信息，请参见 System..::.Enum。

使用 System.Enum 方法发现和操作枚举值

所有枚举都是 System..::.Enum 类型的实例。不能从 System..::.Enum 派生新类，但可以使用它的方法发现有关枚举实例中的值的信息以及操作这些值。

C# 复制代码
string s = Enum.GetName(typeof(Days), 4);
Console.WriteLine(s);

Console.WriteLine("The values of the Days Enum are:");
foreach (int i in Enum.GetValues(typeof(Days)))
    Console.WriteLine(i);

Console.WriteLine("The names of the Days Enum are:");
foreach (string str in Enum.GetNames(typeof(Days)))
    Console.WriteLine(str);

有关更多信息，请参见 System..::.Enum。

此外，还可以使用扩展方法为枚举创建新方法。有关更多信息，请参见 如何：为枚举创建新方法（C# 编程指南）。

请参见

概念

C# 编程指南

参考

enum（C# 参考）
System..::.Enum

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


委托（C# 编程指南）

[image]

C# 编程指南
委托（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

委托是一种定义方法签名的类型，可以与具有兼容签名的任何方法关联。您可以通过委托调用方法。委托用于将方法作为参数传递给其他方法。事件处理程序就是通过委托调用的方法。您可以创建一个自定义方法，当发生特定事件时某个类（例如 Windows 控件）就可以调用您的方法。下面的示例演示了一个委托声明：

C# 复制代码
public delegate int PerformCalculation(int x, int y);

与委托的签名（由返回类型和参数组成）匹配的任何可访问类或结构中的任何方法都可以分配给该委托。方法可以是静态方法，也可以是实例方法。这样就可以通过编程方式来更改方法调用，还可以向现有类中插入新代码。只要知道委托的签名，就可以分配您自己的方法。

说明：

在方法重载的上下文中，方法的签名不包括返回值。但在委托的上下文中，签名的确包括返回值。换句话说，方法和委托必须具有相同的返回值。

将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。例如，可以向排序算法传递对比较两个对象的方法的引用。分离比较代码使得可以采用更通用的方式编写算法。

委托概述

委托具有以下特点：

  • 委托类似于 C++ 函数指针，但它们是类型安全的。

  • 委托允许将方法作为参数进行传递。

  • 委托可用于定义回调方法。

  • 委托可以链接在一起；例如，可以对一个事件调用多个方法。

  • 方法不必与委托签名完全匹配。有关更多信息，请参见Covariance and Contravariance委托中的协变和逆变（C# 编程指南）。

  • C# 2.0 版引入了匿名方法的概念，此类方法允许将代码块作为参数传递，以代替单独定义的方法。C# 3.0 引入了 Lambda 表达式，利用它们可以更简练地编写内联代码块。匿名方法和 Lambda 表达式（在某些上下文中）都可编译为委托类型。这些功能统称为匿名函数。有关 Lambda 表达式的更多信息，请参见匿名函数（C# 编程指南）。

本节内容

  • 使用委托（C# 编程指南）

  • 何时使用委托而不使用接口（C# 编程指南）

  • 带有命名方法的委托与带有匿名方法的委托（C# 编程指南）

  • 匿名方法（C# 编程指南）

  • 委托中的协变和逆变（C# 编程指南）

  • 如何：合并委托（多路广播委托）（C# 编程指南）

  • 如何：声明、实例化和使用委托（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.11 委托

  • 4.2.6 委托类型

  • 7.5.5.3 委托调用

  • 15 委托

请参见

概念

C# 编程指南

参考

事件（C# 编程指南）
Delegate


使用委托（C# 编程指南）

[image]

C# 编程指南
使用委托（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

委托是一种安全地封装方法的类型，它与 C 和 C++ 中的函数指针类似。与 C 中的函数指针不同，委托是面向对象的、类型安全的和保险的。委托的类型由委托的名称定义。下面的示例声明了一个名为 Del 的委托，该委托可以封装一个采用字符串作为参数并返回 void 的方法。

C# 复制代码
public delegate void Del(string message);

构造委托对象时，通常提供委托将包装的方法的名称或使用匿名方法。实例化委托后，委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法，来自方法的返回值（如果有）由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。例如：

C# 复制代码
// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}
C# 复制代码
// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

委托类型派生自 .NET Framework 中的 Delegate 类。委托类型是密封的，不能从 Delegate 中派生委托类型，也不可能从中派生自定义类。由于实例化委托是一个对象，所以可以将其作为参数进行传递，也可以将其赋值给属性。这样，方法便可以将一个委托作为参数来接受，并且以后可以调用该委托。这称为异步回调，是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时，使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。有关更多信息，请参见何时使用委托而不使用接口。

回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。它允许调用方的代码成为排序算法的一部分。下面的示例方法使用 Del 类型作为参数：

C# 复制代码
public void MethodWithCallback(int param1, int param2, Del callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

然后可以将上面创建的委托传递给该方法：

C# 复制代码
MethodWithCallback(1, 2, handler);

在控制台中将收到下面的输出：

The number is: 3

在将委托用作抽象概念时，MethodWithCallback 不需要直接调用控制台 -- 设计它时无需考虑控制台。MethodWithCallback 的作用只是准备字符串并将该字符串传递给其他方法。此功能特别强大，因为委托的方法可以使用任意数量的参数。

将委托构造为包装实例方法时，该委托将同时引用实例和方法。除了它所包装的方法外，委托不了解实例类型，所以只要任意类型的对象中具有与委托签名相匹配的方法，委托就可以引用该对象。将委托构造为包装静态方法时，它只引用方法。考虑下列声明：

C# 复制代码
public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

加上前面显示的静态 DelegateMethod，现在我们有三个方法可由 Del 实例进行包装。

调用委托时，它可以调用多个方法。这称为多路广播。若要向委托的方法列表（调用列表）中添加额外的方法，只需使用加法运算符或加法赋值运算符（“+”或“+=”）添加两个委托。例如：

C# 复制代码
MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

此时，allMethodsDelegate 在其调用列表中包含三个方法 -- Method1、Method2 和 DelegateMethod。原来的三个委托 d1、d2 和 d3 保持不变。调用 allMethodsDelegate 时，将按顺序调用所有这三个方法。如果委托使用引用参数，则引用将依次传递给三个方法中的每个方法，由一个方法引起的更改对下一个方法是可见的。如果任一方法引发了异常，而在该方法内未捕获该异常，则该异常将传递给委托的调用方，并且不再对调用列表中后面的方法进行调用。如果委托具有返回值和/或输出参数，它将返回最后调用的方法的返回值和参数。若要从调用列表中移除方法，请使用减法运算符或减法赋值运算符（“-”或“-=”）。例如：

C# 复制代码
//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;

由于委托类型派生自 System.Delegate，所以可在委托上调用该类定义的方法和属性。例如，为了找出委托的调用列表中的方法数，您可以编写下面的代码：

C# 复制代码
int invocationCount = d1.GetInvocationList().GetLength(0);

在调用列表中具有多个方法的委托派生自 MulticastDelegate，这是 System.Delegate 的子类。由于两个类都支持 GetInvocationList，所以上面的代码在两种情况下都适用。

多路广播委托广泛用于事件处理中。事件源对象向已注册接收该事件的接收方对象发送事件通知。为了为事件注册，接收方创建了旨在处理事件的方法，然后为该方法创建委托并将该委托传递给事件源。事件发生时，源将调用委托。然后，委托调用接收方的事件处理方法并传送事件数据。给定事件的委托类型由事件源定义。有关更多信息，请参见事件（C# 编程指南）。

在编译时，对分配了两种不同类型的委托进行比较将产生编译错误。如果委托实例静态地属于类型 System.Delegate，则允许进行比较，但在运行时将返回 false。例如：

C# 复制代码
delegate void Delegate1();
delegate void Delegate2();

static void method(Delegate1 d, Delegate2 e, System.Delegate f)
{
    // Compile-time error.
    //Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f
    // is not the same as that of d.
    System.Console.WriteLine(d == f);
}

请参见

概念

C# 编程指南

参考

委托（C# 编程指南）
委托中的协变和逆变（C# 编程指南）
事件（C# 编程指南）


带有命名方法的委托与带有匿名方法的委托（C# 编程指南）

[image]

C# 编程指南
带有命名方法的委托与带有匿名方法的委托（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

委托可以与命名方法关联。使用命名方法对委托进行实例化时，该方法将作为参数传递，例如：

C# 复制代码
// Declare a delegate:
delegate void Del(int x);

// Define a named method:
void DoWork(int k) { /* ... */ }

// Instantiate the delegate using the method as a parameter:
Del d = obj.DoWork;

这被称为使用命名的方法。使用命名方法构造的委托可以封装静态方法或实例方法。在早期版本的 C# 中，命名方法是对委托进行实例化的唯一方式。但是，在不希望付出创建新方法的系统开销时，C# 使您可以对委托进行实例化，并立即指定委托在被调用时将处理的代码块。代码块可以包含 lambda 表达式或匿名方法。有关更多信息，请参见匿名函数（C# 编程指南）。

备注

作为委托参数传递的方法必须与委托声明具有相同的签名。

委托实例可以封装静态或实例方法。

尽管委托可以使用 out 参数，但建议您不要将其用于多路广播事件委托，因为您无法知道哪个委托将被调用。

示例 1

以下是声明及使用委托的一个简单示例。注意，委托 Del 和关联的方法 MultiplyNumbers 具有相同的签名

C# 复制代码
// Declare a delegate
delegate void Del(int i, double j);

class MathClass
{
    static void Main()
    {
        MathClass m = new MathClass();

        // Delegate instantiation using "MultiplyNumbers"
        Del d = m.MultiplyNumbers;

        // Invoke the delegate object.
        System.Console.WriteLine("Invoking the delegate using 'MultiplyNumbers':");
        for (int i = 1; i <= 5; i++)
        {
            d(i, 2);
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }

    // Declare the associated method.
    void MultiplyNumbers(int m, double n)
    {
        System.Console.Write(m * n + " ");
    }
}
/* Output:
    Invoking the delegate using 'MultiplyNumbers':
    2 4 6 8 10
*/

示例 2

在下面的示例中，一个委托被同时映射到静态方法和实例方法，并分别返回特定的信息。

C# 复制代码
// Declare a delegate
delegate void Del();

class SampleClass
{
    public void InstanceMethod()
    {
        System.Console.WriteLine("A message from the instance method.");
    }

    static public void StaticMethod()
    {
        System.Console.WriteLine("A message from the static method.");
    }
}

class TestSampleClass
{
    static void Main()
    {
        SampleClass sc = new SampleClass();

        // Map the delegate to the instance method:
        Del d = sc.InstanceMethod;
        d();

        // Map to the static method:
        d = SampleClass.StaticMethod;
        d();
    }
}
/* Output:
    A message from the instance method.
    A message from the static method.
*/

请参见

任务

如何：合并委托（多路广播委托）（C# 编程指南）

概念

C# 编程指南

参考

委托（C# 编程指南）
事件（C# 编程指南）


何时使用委托而不使用接口（C# 编程指南）

[image]

C# 编程指南
何时使用委托而不使用接口（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

委托和接口都允许类设计器分离类型声明和实现。任何类或结构都能继承和实现给定的接口。可以为任何类上的方法创建委托，前提是该方法符合委托的方法签名。接口引用或委托可由不了解实现该接口或委托方法的类的对象使用。既然存在这些相似性，那么类设计器何时应使用委托，何时又该使用接口呢？

在以下情况下，请使用委托：

  • 当使用事件设计模式时。

  • 当封装静态方法可取时。

  • 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。

  • 需要方便的组合。

  • 当类可能需要该方法的多个实现时。

在以下情况下，请使用接口：

  • 当存在一组可能被调用的相关方法时。

  • 当类只需要方法的单个实现时。

  • 当使用接口的类想要将该接口强制转换为其他接口或类类型时。

  • 当正在实现的方法链接到类的类型或标识时：例如比较方法。

使用单一方法接口而不使用委托的一个很好的示例是 IComparable 或泛型版本 IComparable<(Of <(T>)>)。IComparable 声明 CompareTo 方法，该方法返回一个整数，指定相同类型的两个对象之间的小于、等于或大于关系。IComparable 可用作排序算法的基础。虽然将委托比较方法用作排序算法的基础是有效的，但是并不理想。因为进行比较的能力属于类，而比较算法不会在运行时改变，所以单一方法接口是理想的。

请参见

概念

C# 编程指南

参考

事件（C# 编程指南）
方法（C# 编程指南）
接口（C# 编程指南）


委托中的协变和逆变（C# 编程指南）

[image]

C# 编程指南
委托中的协变和逆变（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

将方法签名与委托类型匹配时，协变和逆变可以提供一定程度的灵活性。协变允许方法具有的派生返回类型比委托中定义的更多。逆变允许方法具有的派生参数类型比委托类型中的更少。

示例 1（协变）

说明

本示例演示如何将委托与具有返回类型的方法一起使用，这些返回类型派生自委托签名中的返回类型。由 SecondHandler 返回的数据类型是 Dogs 类型，它是由委托中定义的 Mammals 类型派生的。

代码

C# 复制代码
class Mammals
{
}

class Dogs : Mammals
{
}

class Program
{
    // Define the delegate.
    public delegate Mammals HandlerMethod();

    public static Mammals FirstHandler()
    {
        return null;
    }

    public static Dogs SecondHandler()
    {
        return null;
    }

    static void Main()
    {
        HandlerMethod handler1 = FirstHandler;

        // Covariance allows this delegate.
        HandlerMethod handler2 = SecondHandler;
    }
}

示例 2（逆变）

说明

本示例演示如何将委托与具有某个类型的参数的方法一起使用，这些参数是委托签名参数类型的基类型。通过逆变，以前必须使用若干个不同处理程序的地方现在只要使用一个事件处理程序即可 。如，现在可以创建一个接收 EventArgs 输入参数的事件处理程序，然后，可以将该处理程序与发送 MouseEventArgs 类型（作为参数）的 Button.MouseClick 事件一起使用，也可以将该处理程序与发送 KeyEventArgs 参数的 TextBox.KeyDown 事件一起使用。

代码

C# 复制代码
System.DateTime lastActivity;
public Form1()
{
    InitializeComponent();

    lastActivity = new System.DateTime();
    this.textBox1.KeyDown += this.MultiHandler; //works with KeyEventArgs
    this.button1.MouseClick += this.MultiHandler; //works with MouseEventArgs

}

// Event hander for any event with an EventArgs or
// derived class in the second parameter
private void MultiHandler(object sender, System.EventArgs e)
{
    lastActivity = System.DateTime.Now;
}

请参见

概念

C# 编程指南

参考

事件（C# 编程指南）
委托（C# 编程指南）
泛型委托（C# 编程指南）


如何：合并委托（多路广播委托）（C# 编程指南）

[image]

C# 编程指南
如何：合并委托（多路广播委托）（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例演示如何组合多路广播委托。委托对象的一个用途在于，可以使用 + 运算符将它们分配给一个要成为多路广播委托的委托实例。组合的委托可调用组成它的那两个委托。只有相同类型的委托才可以组合。

- 运算符可用来从组合的委托移除组件委托。

示例

C# 复制代码
delegate void Del(string s);

class TestClass
{
    static void Hello(string s)
    {
        System.Console.WriteLine("  Hello, {0}!", s);
    }

    static void Goodbye(string s)
    {
        System.Console.WriteLine("  Goodbye, {0}!", s);
    }

    static void Main()
    {
        Del a, b, c, d;

        // Create the delegate object a that references
        // the method Hello:
        a = Hello;

        // Create the delegate object b that references
        // the method Goodbye:
        b = Goodbye;

        // The two delegates, a and b, are composed to form c:
        c = a + b;

        // Remove a from the composed delegate, leaving d,
        // which calls only the method Goodbye:
        d = c - a;

        System.Console.WriteLine("Invoking delegate a:");
        a("A");
        System.Console.WriteLine("Invoking delegate b:");
        b("B");
        System.Console.WriteLine("Invoking delegate c:");
        c("C");
        System.Console.WriteLine("Invoking delegate d:");
        d("D");
    }
}
/* Output:
Invoking delegate a:
  Hello, A!
Invoking delegate b:
  Goodbye, B!
Invoking delegate c:
  Hello, C!
  Goodbye, C!
Invoking delegate d:
  Goodbye, D!
*/

请参见

概念

C# 编程指南

参考

事件（C# 编程指南）
MulticastDelegate


如何：声明、实例化和使用委托（C# 编程指南）

[image]

C# 编程指南
如何：声明、实例化和使用委托（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 1.0 及更高版本中，可以按此处所示方式声明委托：

C# 复制代码
public delegate void Del<T>(T item);
public void Notify(int i) { }
C# 复制代码
Del<int> d1 = new Del<int>(Notify);

在 C# 2.0 及更高版本中，还可以使用以下简化的语法，通过匿名方法来声明和初始化委托：

C# 复制代码
Del<int> d2 = Notify;

在 C# 3.0 及更高版本中，还可以使用 Lambda 表达式来声明和实例化委托。有关更多信息，请参见 Lambda 表达式（C# 编程指南）。

下面的示例阐释声明、实例化和使用委托。BookDB 类封装一个书店数据库，它维护一个书籍数据库。它公开 ProcessPaperbackBooks 方法，该方法在数据库中查找所有平装书，并对每本平装书调用一个委托。使用的 delegate 类型名为 ProcessBookDelegate。Test 类使用该类打印平装书的书名和平均价格。

委托的使用促进了书店数据库和客户代码之间功能的良好分隔。客户代码不知道书籍的存储方式和书店代码查找平装书的方式。书店代码也不知道找到平装书后将对平装书执行什么处理。

示例

C# 复制代码
// A set of classes for handling a bookstore:
namespace Bookstore
{
    using System.Collections;

    // Describes a book in the book list:
    public struct Book
    {
        public string Title;        // Title of the book.
        public string Author;       // Author of the book.
        public decimal Price;       // Price of the book.
        public bool Paperback;      // Is it paperback?

        public Book(string title, string author, decimal price, bool paperBack)
        {
            Title = title;
            Author = author;
            Price = price;
            Paperback = paperBack;
        }
    }

    // Declare a delegate type for processing a book:
    public delegate void ProcessBookDelegate(Book book);

    // Maintains a book database.
    public class BookDB
    {
        // List of all books in the database:
        ArrayList list = new ArrayList();

        // Add a book to the database:
        public void AddBook(string title, string author, decimal price, bool paperBack)
        {
            list.Add(new Book(title, author, price, paperBack));
        }

        // Call a passed-in delegate on each paperback book to process it:
        public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
        {
            foreach (Book b in list)
            {
                if (b.Paperback)
                    // Calling the delegate:
                    processBook(b);
            }
        }
    }
}

// Using the Bookstore classes:
namespace BookTestClient
{
    using Bookstore;

    // Class to total and average prices of books:
    class PriceTotaller
    {
        int countBooks = 0;
        decimal priceBooks = 0.0m;

        internal void AddBookToTotal(Book book)
        {
            countBooks += 1;
            priceBooks += book.Price;
        }

        internal decimal AveragePrice()
        {
            return priceBooks / countBooks;
        }
    }

    // Class to test the book database:
    class TestBookDB
    {
        // Print the title of the book.
        static void PrintTitle(Book b)
        {
            System.Console.WriteLine("   {0}", b.Title);
        }

        // Execution starts here.
        static void Main()
        {
            BookDB bookDB = new BookDB();

            // Initialize the database with some books:
            AddBooks(bookDB);

            // Print all the titles of paperbacks:
            System.Console.WriteLine("Paperback Book Titles:");

            // Create a new delegate object associated with the static
            // method Test.PrintTitle:
            bookDB.ProcessPaperbackBooks(PrintTitle);

            // Get the average price of a paperback by using
            // a PriceTotaller object:
            PriceTotaller totaller = new PriceTotaller();

            // Create a new delegate object associated with the nonstatic
            // method AddBookToTotal on the object totaller:
            bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

            System.Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
                    totaller.AveragePrice());
        }

        // Initialize the book database with some test books:
        static void AddBooks(BookDB bookDB)
        {
            bookDB.AddBook("The C Programming Language", "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
            bookDB.AddBook("The Unicode Standard 2.0", "The Unicode Consortium", 39.95m, true);
            bookDB.AddBook("The MS-DOS Encyclopedia", "Ray Duncan", 129.95m, false);
            bookDB.AddBook("Dogbert's Clues for the Clueless", "Scott Adams", 12.00m, true);
        }
    }
}
/* Output:
Paperback Book Titles:
   The C Programming Language
   The Unicode Standard 2.0
   Dogbert's Clues for the Clueless
Average Paperback Book Price: $23.97
*/

可靠编程

  • 声明委托。

    下面的语句声明一个新的委托类型。

    C# 复制代码
    public delegate void ProcessBookDelegate(Book book);

    每个委托类型都描述参数的数目和类型，以及它可以封装的方法的返回值类型。每当需要一组新的参数类型或新的返回值类型时，都必须声明一个新的委托类型。

  • 实例化委托。

    声明了委托类型后，必须创建委托对象并使之与特定方法关联。在上一个示例中，您通过按下面示例中的方式将 PrintTitle 方法传递到 ProcessPaperbackBooks 方法来实现这一点：

    C# 复制代码
    bookDB.ProcessPaperbackBooks(PrintTitle);

    这将创建与静态方法 Test.PrintTitle 关联的新委托对象。类似地，对象 totaller 的非静态方法 AddBookToTotal 是按下面示例中的方式传递的：

    C# 复制代码
    bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

    在两个示例中，都向 ProcessPaperbackBooks 方法传递了一个新的委托对象。

    委托创建后，它的关联方法就不能更改；委托对象是不可变的。

  • 调用委托。

    创建委托对象后，通常将委托对象传递给将调用该委托的其他代码。通过委托对象的名称（后面跟着要传递给委托的参数，括在括号内）调用委托对象。下面是委托调用的示例：

    C# 复制代码
    processBook(b);

    与本例一样，可以通过使用 BeginInvoke 和 EndInvoke 方法同步或异步调用委托。

请参见

概念

C# 编程指南

参考

事件（C# 编程指南）
委托（C# 编程指南）


事件（C# 编程指南）

[image]

C# 编程指南
事件（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

类或对象可以通过事件向其他类或对象通知发生的相关事情。发送（或引发）事件的类称为“发行者”，接收（或处理）事件的类称为“订户”。

在典型的 C# Windows 窗体或 Web 应用程序中，可订阅由控件（如按钮和列表框）引发的事件。可使用 Visual C# 集成开发环境 (IDE) 来浏览控件发布的事件，选择要处理的事件。IDE 会自动添加空事件处理程序方法和订阅事件的代码。有关更多信息，请参见 如何：订阅和取消订阅事件（C# 编程指南）。

事件概述

事件具有以下特点：

  • 发行者确定何时引发事件，订户确定执行何种操作来响应该事件。

  • 一个事件可以有多个订户。一个订户可处理来自多个发行者的多个事件。

  • 没有订户的事件永远也不会引发。

  • 事件通常用于通知用户操作，例如，图形用户界面中的按钮单击或菜单选择操作。

  • 如果一个事件有多个订户，当引发该事件时，会同步调用多个事件处理程序。要异步调用事件，请参见使用异步方式调用同步方法。

  • 可以利用事件同步线程。

  • 在 .NET Framework 类库中，事件是基于 EventHandler 委托和 EventArgs 基类的。

相关章节

有关更多信息，请参见：

  • 如何：订阅和取消订阅事件（C# 编程指南）

  • 如何：发布符合 .NET Framework 准则的事件（C# 编程指南）

  • 如何：在派生类中引发基类事件（C# 编程指南）

  • 如何：实现接口事件（C# 编程指南）

  • 线程同步（C# 编程指南）

  • 如何：使用字典存储事件实例（C# 编程指南）

  • 如何：实现自定义事件访问器（C# 编程指南）

  • “事件”示例

  • 事件设计

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.4 事件

  • 10.2.9.2 事件的保留成员名称

  • 10.8 事件

  • 13.2.3 接口事件

请参见

概念

C# 编程指南

参考

委托（C# 编程指南）
EventHandler

其他资源

在 Windows 窗体中创建事件处理程序
使用基于事件的异步模式进行多线程编程


如何：订阅和取消订阅事件（C# 编程指南）

[image]

C# 编程指南
如何：订阅和取消订阅事件（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

如果您想编写引发事件时调用的自定义代码，则可以订阅由其他类发布的事件。例如，可以订阅某个按钮的 click 事件，以使应用程序在用户单击该按钮时执行一些有用的操作。

使用 Visual Studio IDE 订阅事件

  1. 如果看不到“属性”窗口，请在“设计”视图中，右键单击要为其创建事件处理程序的窗体或控件，然后选择“属性”。

  2. 在“属性”窗口的顶部，单击“事件”图标。

  3. 双击要创建的事件，例如 Load 事件。

    Visual C# 会创建一个空事件处理程序方法，并将其添加到您的代码中。或者，您也可以在“代码”视图中手动添加代码。例如，下面的代码行声明了一个在 Form 类引发 Load 事件时调用的事件处理程序方法。

    C# 复制代码
    private void Form1_Load(object sender, System.EventArgs e)
{
    // Add your form load event handling code here.
}

    订阅该事件所需的代码行也会在项目的 Form1.Designer.cs 文件的 InitializeComponent 方法中自动生成。该代码行类似于：

    复制代码
      this.Load += new System.EventHandler(this.Form1_Load);

以编程方式订阅事件

  1. 定义一个事件处理程序方法，其签名与该事件的委托签名匹配。例如，如果事件基于 EventHandler 委托类型，则下面的代码表示方法存根：

    复制代码
      void HandleCustomEvent(object sender, CustomEventArgs a)
  {
     // Do something useful here.
  }

  2. 使用加法赋值运算符 (+=) 来为事件附加事件处理程序。在下面的示例中，假设名为 publisher 的对象拥有一个名为 RaiseCustomEvent 的事件。请注意，订户类需要引用发行者类才能订阅其事件。

    复制代码
    publisher.RaiseCustomEvent += HandleCustomEvent;

    请注意，前面的语法是 C# 2.0 中的新语法。此语法完全等效于必须使用 new 关键字显式创建封装委托的 C# 1.0 语法：

    复制代码
    publisher.RaiseCustomEvent += new CustomEventHandler(HandleCustomEvent);

    还可以通过使用 lambda 表达式添加事件处理程序：

    复制代码
    public Form1()
{
    InitializeComponent();
    // Use a lambda expression to define an event handler.
    this.Click += (s,e) => { MessageBox.Show(
       ((MouseEventArgs)e).Location.ToString());};
}

    有关更多信息，请参见 如何：在 LINQ 外部使用 Lambda 表达式（C# 编程指南）。

使用匿名方法订阅事件

  • 如果以后不必取消订阅某个事件，则可以使用加法赋值运算符 (+=) 将匿名方法附加到此事件。在下面的示例中，假设名为 publisher 的对象拥有一个名为 RaiseCustomEvent 的事件，并且还定义了一个 CustomEventArgs 类以承载某些类型的专用事件信息。请注意，订户类需要引用 publisher 才能订阅其事件。

    复制代码
      publisher.RaiseCustomEvent += delegate(object o, CustomEventArgs e)
  {
    string s = o.ToString() + " " + e.ToString();
    Console.WriteLine(s);
  };

    请务必注意，如果使用匿名函数订阅事件，事件的取消订阅过程将比较麻烦。这种情况下若要取消订阅，必须返回到该事件的订阅代码，将该匿名方法存储在委托变量中，然后将此委托添加到该事件中。一般来说，如果必须在后面的代码中取消订阅某个事件，则建议您不要使用匿名函数订阅此事件。有关匿名函数的更多信息，请参见匿名函数（C# 编程指南）。

取消订阅

要防止在引发事件时调用事件处理程序，请取消订阅该事件。要防止资源泄露，应在释放订户对象之前取消订阅事件。在取消订阅事件之前，在发布对象中作为该事件的基础的多路广播委托会引用封装了订户的事件处理程序的委托。只要发布对象保持该引用，垃圾回收功能就不会删除订户对象。

取消订阅事件

  • 使用减法赋值运算符 (-=) 取消订阅事件：

    复制代码
      publisher.RaiseCustomEvent -= HandleCustomEvent;

    所有订户都取消订阅事件后，发行者类中的事件实例将设置为 null。

请参见

任务

如何：发布符合 .NET Framework 准则的事件（C# 编程指南）

参考

事件（C# 编程指南）
event（C# 参考）
-= 运算符（C# 参考）
+= 运算符（C# 参考）


如何：发布符合 .NET Framework 准则的事件（C# 编程指南）

[image]

C# 编程指南
如何：发布符合 .NET Framework 准则的事件（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的过程演示了如何将符合标准 .NET Framework 模式的事件添加到您自己的类和结构中。.NET Framework 类库中的所有事件均基于 EventHandler 委托，定义如下：

复制代码
public delegate void EventHandler(object sender, EventArgs e);
说明：

.NET Framework 2.0 引入了此委托的一个泛型版本，即 EventHandler<(Of <(TEventArgs>)>)。下面的示例显示如何使用这两种版本。

虽然您定义的类中的事件可基于任何有效委托类型（甚至是可返回值的委托），但是，通常建议您使用 EventHandler 让事件基于 .NET Framework 模式，如下面的示例所示。

采用 EventHandler 模式发布事件

  1. （如果不需要与事件一起发送自定义数据，请跳过此步骤，进入步骤 3a。）在发行者类和订户类均可看见的范围中声明类，并添加保存自定义事件数据所需的成员。在此示例中，会返回一个简单字符串。

    复制代码
    public class CustomEventArgs : EventArgs
{
    public CustomEventArgs(string s)
    {
        msg = s;
    }
    private string msg;
    public string Message
    {
        get { return msg; }
    }
}

  2. （如果您使用的是 EventHandler<(Of <(TEventArgs>)>) 的泛型版本，请跳过此步骤。）在发布类中声明一个委托。为它指定以 EventHandler 结尾的名称。第二个参数指定自定义 EventArgs 类型。

    复制代码
    public delegate void CustomEventHandler(object sender, CustomEventArgs a);

  3. 使用以下任一步骤，在发布类中声明事件。

    1. 如果没有自定义 EventArgs 类，事件类型就是非泛型 EventHandler 委托。它无需声明，因为它已在创建 C# 项目时包含的 System 命名空间中进行了声明：

      复制代码
      public event EventHandler RaiseCustomEvent;

    2. 如果使用的是 EventHandler 的非泛型版本，并且您有一个由 EventArgs 派生的自定义类，请在发布类中声明您的事件，并且将您的委托用作类型：

      复制代码
      class Publisher
{
    public event CustomEventHandler RaiseCustomEvent;
}

    3. 如果使用的是泛型版本，则不需要自定义委托。相反，应将事件类型指定为 EventHandler<CustomEventArgs>，在尖括号内放置您自己的类的名称。

      复制代码
      public event EventHandler<CustomEventArgs> RaiseCustomEvent;

示例

下面的示例通过将自定义 EventArgs 类和 EventHandler<(Of <(TEventArgs>)>) 用作事件类型来演示上述步骤。

C# 复制代码
namespace DotNetEvents
{
    using System;
    using System.Collections.Generic;

    // Define a class to hold custom event info
    public class CustomEventArgs : EventArgs
    {
        public CustomEventArgs(string s)
        {
            message = s;
        }
        private string message;

        public string Message
        {
            get { return message; }
            set { message = value; }
        }
    }

    // Class that publishes an event
    class Publisher
    {

        // Declare the event using EventHandler<T>
        public event EventHandler<CustomEventArgs> RaiseCustomEvent;

        public void DoSomething()
        {
            // Write some code that does something useful here
            // then raise the event. You can also raise an event
            // before you execute a block of code.
            OnRaiseCustomEvent(new CustomEventArgs("Did something"));

        }

        // Wrap event invocations inside a protected virtual method
        // to allow derived classes to override the event invocation behavior
        protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<CustomEventArgs> handler = RaiseCustomEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                // Format the string to send inside the CustomEventArgs parameter
                e.Message += String.Format(" at {0}", DateTime.Now.ToString());

                // Use the () operator to raise the event.
                handler(this, e);
            }
        }
    }

    //Class that subscribes to an event
    class Subscriber
    {
        private string id;
        public Subscriber(string ID, Publisher pub)
        {
            id = ID;
            // Subscribe to the event using C# 2.0 syntax
            pub.RaiseCustomEvent += HandleCustomEvent;
        }

        // Define what actions to take when the event is raised.
        void HandleCustomEvent(object sender, CustomEventArgs e)
        {
            Console.WriteLine(id + " received this message: {0}", e.Message);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Publisher pub = new Publisher();
            Subscriber sub1 = new Subscriber("sub1", pub);
            Subscriber sub2 = new Subscriber("sub2", pub);

            // Call the method that raises the event.
            pub.DoSomething();

            // Keep the console window open
            Console.WriteLine("Press Enter to close this window.");
            Console.ReadLine();

        }
    }
}

请参见

概念

C# 编程指南
事件设计

参考

事件（C# 编程指南）
委托（C# 编程指南）
Delegate


如何：在派生类中引发基类事件（C# 编程指南）

[image]

C# 编程指南
如何：在派生类中引发基类事件（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

以下简单示例演示了在基类中声明可从派生类引发的事件的标准方法。此模式广泛应用于 .NET Framework 类库中的 Windows 窗体类。

在创建可用作其他类的基类的类时，应考虑如下事实：事件是特殊类型的委托，只可以从声明它们的类中调用。派生类无法直接调用基类中声明的事件。尽管有时需要事件仅由基类引发，但在大多数情形下，应该允许派生类调用基类事件。为此，您可以在包含该事件的基类中创建一个受保护的调用方法。通过调用或重写此调用方法，派生类便可以间接调用该事件。

说明：

不要在基类中声明虚拟事件，也不要在派生类中重写这些事件。在 Microsoft Visual Studio 2008 中 C# 编译器无法正确处理这些事件，并且无法预知派生事件的订户是否真正订阅了基类事件。

示例

C# 复制代码
namespace BaseClassEvents
{
    using System;
    using System.Collections.Generic;

    // Special EventArgs class to hold info about Shapes.
    public class ShapeEventArgs : EventArgs
    {
        private double newArea;

        public ShapeEventArgs(double d)
        {
            newArea = d;
        }
        public double NewArea
        {
            get { return newArea; }
        }
    }

    // Base class event publisher
    public abstract class Shape
    {
        protected double area;

        public double Area
        {
            get { return area; }
            set { area = value; }
        }
        // The event. Note that by using the generic EventHandler<T> event type
        // we do not need to declare a separate delegate type.
        public event EventHandler<ShapeEventArgs> ShapeChanged;

        public abstract void Draw();

        //The event-invoking method that derived classes can override.
        protected virtual void OnShapeChanged(ShapeEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<ShapeEventArgs> handler = ShapeChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    }

    public class Circle : Shape
    {
        private double radius;
        public Circle(double d)
        {
            radius = d;
            area = 3.14 * radius;
        }
        public void Update(double d)
        {
            radius = d;
            area = 3.14 * radius;
            OnShapeChanged(new ShapeEventArgs(area));
        }
        protected override void OnShapeChanged(ShapeEventArgs e)
        {
            // Do any circle-specific processing here.

            // Call the base class event invocation method.
            base.OnShapeChanged(e);
        }
        public override void Draw()
        {
            Console.WriteLine("Drawing a circle");
        }
    }

    public class Rectangle : Shape
    {
        private double length;
        private double width;
        public Rectangle(double length, double width)
        {
            this.length = length;
            this.width = width;
            area = length * width;
        }
        public void Update(double length, double width)
        {
            this.length = length;
            this.width = width;
            area = length * width;
            OnShapeChanged(new ShapeEventArgs(area));
        }
        protected override void OnShapeChanged(ShapeEventArgs e)
        {
            // Do any rectangle-specific processing here.

            // Call the base class event invocation method.
            base.OnShapeChanged(e);
        }
        public override void Draw()
        {
            Console.WriteLine("Drawing a rectangle");
        }

    }

    // Represents the surface on which the shapes are drawn
    // Subscribes to shape events so that it knows
    // when to redraw a shape.
    public class ShapeContainer
    {
        List<Shape> _list;

        public ShapeContainer()
        {
            _list = new List<Shape>();
        }

        public void AddShape(Shape s)
        {
            _list.Add(s);
            // Subscribe to the base class event.
            s.ShapeChanged += HandleShapeChanged;
        }

        // ...Other methods to draw, resize, etc.

        private void HandleShapeChanged(object sender, ShapeEventArgs e)
        {
            Shape s = (Shape)sender;

            // Diagnostic message for demonstration purposes.
            Console.WriteLine("Received event. Shape area is now {0}", e.NewArea);

            // Redraw the shape here.
            s.Draw();
        }
    }

    class Test
    {

        static void Main(string[] args)
        {
            //Create the event publishers and subscriber
            Circle c1 = new Circle(54);
            Rectangle r1 = new Rectangle(12, 9);
            ShapeContainer sc = new ShapeContainer();

            // Add the shapes to the container.
            sc.AddShape(c1);
            sc.AddShape(r1);

            // Cause some events to be raised.
            c1.Update(57);
            r1.Update(7, 7);

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }
}
/* Output:
        Received event. Shape area is now 178.98
        Drawing a circle
        Received event. Shape area is now 49
        Drawing a rectangle
 */

请参见

概念

C# 编程指南

参考

事件（C# 编程指南）
委托（C# 编程指南）
访问修饰符（C# 编程指南）

其他资源

在 Windows 窗体中创建事件处理程序


如何：实现接口事件（C# 编程指南）

[image]

C# 编程指南
如何：实现接口事件（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

接口可声明事件。下面的示例演示如何在类中实现接口事件。实现接口事件的规则与实现任何接口方法或属性的规则基本相同。

在类中实现接口事件

  • 在类中声明事件，然后在适当的区域调用该事件。

    复制代码
    public interface IDrawingObject
{
    event EventHandler ShapeChanged;
}
public class MyEventArgs : EventArgs {…}
public class Shape : IDrawingObject
{
    event EventHandler ShapeChanged;
    void ChangeShape()
    {
        // Do something before the event…
        OnShapeChanged(new MyEventsArgs(…));
        // or do something after the event.
    }
    protected virtual void OnShapeChanged(MyEventArgs e)
    {
        if(ShapeChanged != null)
        {
           ShapeChanged(this, e);
        }
    }
}

示例

下面的示例演示如何处理以下的不常见情况：您的类是从两个以上的接口继承的，每个接口都含有同名事件）。在这种情况下，您至少要为其中一个事件提供显式接口实现。为事件编写显式接口实现时，必须编写 add 和 remove 事件访问器。这两个事件访问器通常由编译器提供，但在这种情况下编译器不能提供。

您可以提供自己的访问器，以便指定这两个事件是由您的类中的同一事件表示，还是由不同事件表示。例如，根据接口规范，如果事件应在不同时间引发，则可以将每个事件与类中的一个单独实现关联。在下面的示例中，订户将形状引用强制转换为 IShape 或 IDrawingObject，从而确定自己将会接收哪个 OnDraw 事件。

C# 复制代码
namespace WrapTwoInterfaceEvents
{
    using System;

    public interface IDrawingObject
    {
        // Raise this event before drawing
        // the object.
        event EventHandler OnDraw;
    }
    public interface IShape
    {
        // Raise this event after drawing
        // the shape.
        event EventHandler OnDraw;
    }

    // Base class event publisher inherits two
    // interfaces, each with an OnDraw event
    public class Shape : IDrawingObject, IShape
    {
        // Create an event for each interface event
        event EventHandler PreDrawEvent;
        event EventHandler PostDrawEvent;

        object objectLock = new Object();

        // Explicit interface implementation required.
        // Associate IDrawingObject's event with
        // PreDrawEvent
        event EventHandler IDrawingObject.OnDraw
        {
            add
            {
                lock (objectLock)
                {
                    PreDrawEvent += value;
                }
            }
            remove
            {
                lock (objectLock)
                {
                    PreDrawEvent -= value;
                }
            }
        }
        // Explicit interface implementation required.
        // Associate IShape's event with
        // PostDrawEvent
        event EventHandler IShape.OnDraw
        {
            add
            {
                lock (objectLock)
                {
                    PostDrawEvent += value;
                }
            }
            remove
            {
                lock (objectLock)
                {
                    PostDrawEvent -= value;
                }
            }

        }

        // For the sake of simplicity this one method
        // implements both interfaces.
        public void Draw()
        {
            // Raise IDrawingObject's event before the object is drawn.
            EventHandler handler = PreDrawEvent;
            if (handler != null)
            {
                handler(this, new EventArgs());
            }
            Console.WriteLine("Drawing a shape.");

            // RaiseIShape's event after the object is drawn.
            handler = PostDrawEvent;
            if (handler != null)
            {
                handler(this, new EventArgs());
            }
        }
    }
    public class Subscriber1
    {
        // References the shape object as an IDrawingObject
        public Subscriber1(Shape shape)
        {
            IDrawingObject d = (IDrawingObject)shape;
            d.OnDraw += new EventHandler(d_OnDraw);
        }

        void d_OnDraw(object sender, EventArgs e)
        {
            Console.WriteLine("Sub1 receives the IDrawingObject event.");
        }
    }
    // References the shape object as an IShape
    public class Subscriber2
    {
        public Subscriber2(Shape shape)
        {
            IShape d = (IShape)shape;
            d.OnDraw += new EventHandler(d_OnDraw);
        }

        void d_OnDraw(object sender, EventArgs e)
        {
            Console.WriteLine("Sub2 receives the IShape event.");
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Shape shape = new Shape();
            Subscriber1 sub = new Subscriber1(shape);
            Subscriber2 sub2 = new Subscriber2(shape);
            shape.Draw();

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }

}
/* Output:
    Sub1 receives the IDrawingObject event.
    Drawing a shape.
    Sub2 receives the IShape event.
*/

请参见

任务

如何：在派生类中引发基类事件（C# 编程指南）

概念

C# 编程指南

参考

事件（C# 编程指南）
委托（C# 编程指南）
显式接口实现（C# 编程指南）


如何：使用字典存储事件实例（C# 编程指南）

[image]

C# 编程指南
如何：使用字典存储事件实例（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

accessor-declarations 的一种用法是公开很多事件但不为每个事件分配字段，而是使用字典来存储这些事件实例。这只在具有很多事件但您预计大多数事件都不会实现时才有用。

示例

C# 复制代码
public delegate void EventHandler1(int i);
public delegate void EventHandler2(string s);

public class PropertyEventsSample
{
    private System.Collections.Generic.Dictionary<string, System.Delegate> eventTable;

    public PropertyEventsSample()
    {
        eventTable = new System.Collections.Generic.Dictionary<string, System.Delegate>();
        eventTable.Add("Event1", null);
        eventTable.Add("Event2", null);
    }

    public event EventHandler1 Event1
    {
        add
        {
            lock (eventTable)
            {
                eventTable["Event1"] = (EventHandler1)eventTable["Event1"] + value;
            }
        }
        remove
        {
            lock (eventTable)
            {
                eventTable["Event1"] = (EventHandler1)eventTable["Event1"] - value;
            }
        }
    }

    public event EventHandler2 Event2
    {
        add
        {
            lock (eventTable)
            {
                eventTable["Event2"] = (EventHandler2)eventTable["Event2"] + value;
            }
        }
        remove
        {
            lock (eventTable)
            {
                eventTable["Event2"] = (EventHandler2)eventTable["Event2"] - value;
            }
        }
    }

    internal void RaiseEvent1(int i)
    {
        EventHandler1 handler1;
        if (null != (handler1 = (EventHandler1)eventTable["Event1"]))
        {
            handler1(i);
        }
    }

    internal void RaiseEvent2(string s)
    {
        EventHandler2 handler2;
        if (null != (handler2 = (EventHandler2)eventTable["Event2"]))
        {
            handler2(s);
        }
    }
}

public class TestClass
{
    public static void Delegate1Method(int i)
    {
        System.Console.WriteLine(i);
    }

    public static void Delegate2Method(string s)
    {
        System.Console.WriteLine(s);
    }

    static void Main()
    {
        PropertyEventsSample p = new PropertyEventsSample();

        p.Event1 += new EventHandler1(TestClass.Delegate1Method);
        p.Event1 += new EventHandler1(TestClass.Delegate1Method);
        p.Event1 -= new EventHandler1(TestClass.Delegate1Method);
        p.RaiseEvent1(2);

        p.Event2 += new EventHandler2(TestClass.Delegate2Method);
        p.Event2 += new EventHandler2(TestClass.Delegate2Method);
        p.Event2 -= new EventHandler2(TestClass.Delegate2Method);
        p.RaiseEvent2("TestString");

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    2
    TestString
*/

请参见

概念

C# 编程指南

参考

事件（C# 编程指南）
委托（C# 编程指南）


如何：实现自定义事件访问器（C# 编程指南）

[image]

C# 编程指南
如何：实现自定义事件访问器（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

事件是特殊的多路广播委托，仅能从声明该事件的类中调用。客户端代码通过提供对引发事件时要调用的方法的引用来订阅事件。这些方法通过事件访问器添加到委托的调用列表中，事件访问器类似于属性访问器，不同之处在于事件访问器被命名为 add 和 remove。在大多数情况下都不需要提供自定义的事件访问器。如果您在代码中没有提供自定义的事件访问器，编译器会自动添加事件访问器。但在某些情况下，您可能需要提供自定义行为。在主题如何：实现接口事件（C# 编程指南）中就演示了这样一种情况。

示例

下面的示例演示如何实现自定义的 add 和 remove 事件访问器。虽然可以替换这些访问器内的任何代码，但建议您在添加或移除新的事件处理程序方法之前先锁定该事件。

复制代码
event EventHandler IDrawingObject.OnDraw
        {
            add
            {
                lock (PreDrawEvent)
                {
                    PreDrawEvent += value;
                }
            }
            remove
            {
                lock (PreDrawEvent)
                {
                    PreDrawEvent -= value;
                }
            }
        }

请参见

参考

事件（C# 编程指南）
event（C# 参考）


泛型（C# 编程指南）

[image]

C# 编程指南
泛型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

2.0 版 C# 语言和公共语言运行时 (CLR) 中增加了泛型。泛型将类型参数的概念引入 .NET Framework，类型参数使得设计如下类和方法成为可能：这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如，通过使用泛型类型参数 T，您可以编写其他客户端代码能够使用的单个类，而不致引入运行时强制转换或装箱操作的成本或风险，如下所示：

C# 复制代码
// Declare the generic class.
public class GenericList<T>
{
    void Add(T input) { }
}
class TestGenericList
{
    private class ExampleClass { }
    static void Main()
    {
        // Declare a list of type int.
        GenericList<int> list1 = new GenericList<int>();

        // Declare a list of type string.
        GenericList<string> list2 = new GenericList<string>();

        // Declare a list of type ExampleClass.
        GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
    }
}

泛型概述

  • 使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。

  • 泛型最常见的用途是创建集合类。

  • .NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类，如 System.Collections 命名空间中的 ArrayList。

  • 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。

  • 可以对泛型类进行约束以访问特定数据类型的方法。

  • 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。

相关章节

更多信息：

  • 泛型介绍（C# 编程指南）

  • 泛型的优点（C# 编程指南）

  • 泛型类型参数（C# 编程指南）

  • 类型参数的约束（C# 编程指南）

  • 泛型类（C# 编程指南）

  • 泛型接口（C# 编程指南）

  • 泛型方法（C# 编程指南）

  • 泛型委托（C# 编程指南）

  • 泛型代码中的默认关键字（C# 编程指南）

  • C++ 模板和 C# 泛型之间的区别（C# 编程指南）

  • 泛型和反射（C# 编程指南）

  • 运行时中的泛型（C# 编程指南）

  • .NET Framework 类库中的泛型（C# 编程指南）

  • “泛型”示例 (C#)

C# 语言规范

有关更多信息，请参见 C# 语言规范。

请参见

概念

C# 编程指南

参考

System.Collections.Generic
类型（C# 编程指南）
<typeparam>（C# 编程指南）
<typeparamref>（C# 编程指南）


泛型介绍（C# 编程指南）

[image]

C# 编程指南
泛型介绍（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

泛型类和泛型方法同时具备可重用性、类型安全和效率，这是非泛型类和非泛型方法无法具备的。泛型通常用与集合以及作用于集合的方法一起使用。.NET Framework 2.0 版类库提供一个新的命名空间 System.Collections.Generic，其中包含几个新的基于泛型的集合类。建议面向 .NET Framework 2.0 及更高版本的所有应用程序都使用新的泛型集合类，而不要使用旧的非泛型集合类如 ArrayList。有关更多信息，请参见 .NET Framework 类库中的泛型（C# 编程指南）。

当然，也可以创建自定义泛型类型和方法，以提供自己的通用解决方案，设计类型安全的高效模式。下面的代码示例演示一个用于演示用途的简单泛型链接列表类。（大多数情况下，应使用 .NET Framework 类库提供的 List<(Of <(T>)>) 类，而不是自行创建类。）在通常使用具体类型来指示列表中存储的项的类型的场合，可使用类型参数 T。其使用方法如下：

  • 在 AddHead 方法中作为方法参数的类型。

  • 在 Node 嵌套类中作为公共方法 GetNext 和 Data 属性的返回类型。

  • 在嵌套类中作为私有成员数据的类型。

注意，T 可用于 Node 嵌套类。如果使用具体类型实例化 GenericList<T>（例如，作为 GenericList<T>），则所有的 GenericList<T> 都将被替换为 int。

C# 复制代码
// type parameter T in angle brackets
public class GenericList<T>
{
    // The nested class is also generic on T.
    private class Node
    {
        // T used in non-generic constructor.
        public Node(T t)
        {
            next = null;
            data = t;
        }

        private Node next;
        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        // T as private member data type.
        private T data;

        // T as return type of property.
        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }

    private Node head;

    // constructor
    public GenericList()
    {
        head = null;
    }

    // T as method parameter type:
    public void AddHead(T t)
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }
}

下面的代码示例演示客户端代码如何使用泛型 GenericList<T> 类来创建整数列表。只需更改类型参数，即可方便地修改下面的代码示例，创建字符串或任何其他自定义类型的列表：

C# 复制代码
class TestGenericList
{
    static void Main()
    {
        // int is the type argument
        GenericList<int> list = new GenericList<int>();

        for (int x = 0; x < 10; x++)
        {
            list.AddHead(x);
        }

        foreach (int i in list)
        {
            System.Console.Write(i + " ");
        }
        System.Console.WriteLine("\nDone");
    }
}

请参见

概念

C# 编程指南

参考

泛型（C# 编程指南）
System.Collections.Generic


泛型的优点（C# 编程指南）

[image]

C# 编程指南
泛型的优点（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在公共语言运行时和 C# 语言的早期版本中，通用化是通过在类型与通用基类型 Object 之间进行强制转换来实现的，泛型提供了针对这种限制的解决方案。通过创建泛型类，您可以创建一个在编译时类型安全的集合。

使用非泛型集合类的限制可以通过编写一小段程序来演示，该程序利用 .NET Framework 类库中的 ArrayList 集合类。ArrayList 是一个使用起来非常方便的集合类，无需进行修改即可用来存储任何引用或值类型。

C# 复制代码
// The .NET Framework 1.1 way to create a list:
System.Collections.ArrayList list1 = new System.Collections.ArrayList();
list1.Add(3);
list1.Add(105);

System.Collections.ArrayList list2 = new System.Collections.ArrayList();
list2.Add("It is raining in Redmond.");
list2.Add("It is snowing in the mountains.");

但这种方便是需要付出代价的。添加到 ArrayList 中的任何引用或值类型都将隐式地向上强制转换为 Object。如果项是值类型，则必须在将其添加到列表中时进行装箱操作，在检索时进行取消装箱操作。强制转换以及装箱和取消装箱操作都会降低性能；在必须对大型集合进行循环访问的情况下，装箱和取消装箱的影响非常明显。

另一个限制是缺少编译时类型检查；因为 ArrayList 会将所有项都强制转换为 Object，所以在编译时无法防止客户端代码执行类似如下的操作：

C# 复制代码
System.Collections.ArrayList list = new System.Collections.ArrayList();
// Add an integer to the list.
list.Add(3);
// Add a string to the list. This will compile, but may cause an error later.
list.Add("It is raining in Redmond.");

int t = 0;
// This causes an InvalidCastException to be returned.
foreach (int x in list)
{
    t += x;
}

尽管将字符串和 ints 组合在一个 ArrayList 中的做法在创建异类集合时是完全可接受的，并且有时需要有意为之，但这种做法很可能产生编程错误，并且直到运行时才能检测到此错误。

在 C# 语言的 1.0 和 1.1 版本中，只能通过编写自己的特定于类型的集合来避免 .NET Framework 基类库集合类中的通用代码的危险。当然，由于此类不可对多个数据类型重用，因此将丧失通用化的优点，并且您必须对要存储的每个类型重新编写该类。

ArrayList 和其他相似类真正需要的是：客户端代码基于每个实例指定这些类要使用的具体数据类型的方式。这样将不再需要向上强制转换为 T:System.Object，同时，也使得编译器可以进行类型检查。换句话说，ArrayList 需要一个类型参数。这正是泛型所能提供的。在 N:System.Collections.Generic 命名空间的泛型 List<(Of <(T>)>) 集合中，向集合添加项的操作类似于以下形式：

C# 复制代码
// The .NET Framework 2.0 way to create a list
List<int> list1 = new List<int>();

// No boxing, no casting:
list1.Add(3);

// Compile-time error:
// list1.Add("It is raining in Redmond.");

对于客户端代码，与 ArrayList 相比，使用 List<(Of <(T>)>) 时添加的唯一语法是声明和实例化中的类型参数。虽然这种方式稍微增加了编码的复杂性，但好处是您可以创建一个比 ArrayList 更安全并且速度更快的列表，对于列表项是值类型的情况尤为如此。

请参见

概念

C# 编程指南

参考

泛型介绍（C# 编程指南）
System.Collections.Generic
装箱和取消装箱（C# 编程指南）

其他资源

Collections Best Practices（有关集合的最佳做法）


泛型类型参数（C# 编程指南）

[image]

C# 编程指南
泛型类型参数（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在泛型类型或方法定义中，类型参数是客户端在实例化泛型类型的变量时指定的特定类型的占位符。泛型类（如 泛型介绍（C# 编程指南） 中列出的 GenericList<T>）不可以像这样使用，因为它实际上并不是一个类型，而更像是一个类型的蓝图。若要使用 GenericList<T>，客户端代码必须通过指定尖括号中的类型参数来声明和实例化构造类型。此特定类的类型参数可以是编译器识别的任何类型。可以创建任意数目的构造类型实例，每个实例使用不同的类型参数，如下所示：

C# 复制代码
GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();

在每个 GenericList<T> 实例中，类中出现的每个 T 都会在运行时替换为相应的类型参数。通过这种替换方式，我们使用一个类定义创建了三个独立的类型安全的有效对象。有关 CLR 如何执行此替换的更多信息，请参见 运行时中的泛型（C# 编程指南）。

类型参数命名准则

  • 务必使用描述性名称命名泛型类型参数，除非单个字母名称完全可以让人了解它表示的含义，而描述性名称不会有更多的意义。

    C# 复制代码
    public interface ISessionChannel<TSession> { /*...*/ }
public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class List<T> { /*...*/ }

  • 考虑使用 T 作为具有单个字母类型参数的类型的类型参数名。

    C# 复制代码
    public int IComparer<T>() { return 0; }
public delegate bool Predicate<T>(T item);
public struct Nullable<T> where T : struct { /*...*/ }

  • 务必将“T”作为描述性类型参数名的前缀。

    C# 复制代码
    public interface ISessionChannel<TSession>
{
    TSession Session { get; }
}

  • 考虑在参数名中指示对此类型参数的约束。例如，可以将带有 ISession 约束的参数命名为 TSession。

请参见

概念

C# 编程指南

参考

泛型（C# 编程指南）
C++ 模板和 C# 泛型之间的区别（C# 编程指南）
System.Collections.Generic


类型参数的约束（C# 编程指南）

[image]

C# 编程指南
类型参数的约束（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

在定义泛型类时，可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类，则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。下表列出了六种类型的约束：

约束 | 说明
T：结构 | 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息，请参见使用可空类型（C# 编程指南）。
T：类 | 类型参数必须是引用类型；这一点也适用于任何类、接口、委托或数组类型。
T：new() | 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时，new() 约束必须最后指定。
T：<基类名> | 类型参数必须是指定的基类或派生自指定的基类。
T：<接口名称> | 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
T：U | 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。

使用约束的原因

如果要检查泛型列表中的某个项以确定它是否有效，或者将它与其他某个项进行比较，则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。这种保证是通过对泛型类定义应用一个或多个约束获得的。例如，基类约束告诉编译器：仅此类型的对象或从此类型派生的对象才可用作类型参数。一旦编译器有了这个保证，它就能够允许在泛型类中调用该类型的方法。约束是使用上下文关键字 where 应用的。下面的代码示例演示可通过应用基类约束添加到 GenericList<T> 类（在泛型介绍（C# 编程指南）中）的功能。

C# 复制代码
public class Employee
{
    private string name;
    private int id;

    public Employee(string s, int i)
    {
        name = s;
        id = i;
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int ID
    {
        get { return id; }
        set { id = value; }
    }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        private Node next;
        private T data;

        public Node(T t)
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }

    private Node head;

    public GenericList() //constructor
    {
        head = null;
    }

    public void AddHead(T t)
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T FindFirstOccurrence(string s)
    {
        Node current = head;
        T t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property.
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}

约束使得泛型类能够使用 Employee.Name 属性，因为类型为 T 的所有项都保证是 Employee 对象或从 Employee 继承的对象。

可以对同一类型参数应用多个约束，并且约束自身可以是泛型类型，如下所示：

C# 复制代码
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}

通过约束类型参数，可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法调用的数量。因此，在设计泛型类或方法时，如果要对泛型成员执行除简单赋值之外的任何操作或调用 System.Object 不支持的任何方法，您将需要对该类型参数应用约束。

在应用 where T : class 约束时，避免对类型参数使用 == 和 != 运算符，因为这些运算符仅测试引用同一性而不测试值相等性。即使在用作参数的类型中重载这些运算符也是如此。下面的代码说明了这一点；即使 String 类重载 == 运算符，输出也为 false。

C# 复制代码
public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "foo";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("foo");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}

这种情况的原因在于，编译器在编译时仅知道 T 是引用类型，因此必须使用对所有引用类型都有效的默认运算符。如果必须测试值相等性，建议的方法是同时应用 where T : IComparable<T> 约束，并在将用于构造泛型类的任何类中实现该接口。

约束多个参数

可以对多个参数应用约束，并对一个参数应用多个约束，如下面的示例所示：

C# 复制代码
class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new() { }

未绑定的类型参数

没有约束的类型参数（如公共类 SampleClass<T>{} 中的 T）称为未绑定的类型参数。未绑定的类型参数具有以下规则：

  • 不能使用 != 和 == 运算符，因为无法保证具体类型参数能支持这些运算符。

  • 可以在它们与 System.Object 之间来回转换，或将它们显式转换为任何接口类型。

  • 可以将它们与 null 进行比较。将未绑定的参数与 null 进行比较时，如果类型参数为值类型，则该比较将始终返回 false。

裸类型约束

用作约束的泛型类型参数称为裸类型约束。当具有自己的类型参数的成员函数必须将该参数约束为包含类型的类型参数时，裸类型约束很有用，如下面的示例所示：

C# 复制代码
class List<T>
{
    void Add<U>(List<U> items) where U : T {/*...*/}
}

在上面的示例中，T 在 Add 方法的上下文中是一个裸类型约束，而在 List 类的上下文中是一个未绑定的类型参数。

裸类型约束还可以在泛型类定义中使用。注意，还必须已经和其他任何类型参数一起在尖括号中声明了裸类型约束：

C# 复制代码
//naked type constraint
public class SampleClass<T, U, V> where T : V { }

泛型类的裸类型约束的作用非常有限，因为编译器除了假设某个裸类型约束派生自 System.Object 以外，不会做其他任何假设。在希望强制两个类型参数之间的继承关系的情况下，可对泛型类使用裸类型约束。

请参见

概念

C# 编程指南

参考

泛型介绍（C# 编程指南）
new 约束（C# 参考）
System.Collections.Generic

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 增加了有关多个约束的段落。 | 内容 Bug 修复


泛型类（C# 编程指南）

[image]

C# 编程指南
泛型类（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合，如链接列表、哈希表、堆栈、队列、树等。像从集合中添加和移除项这样的操作都以大体上相同的方式执行，与所存储数据的类型无关。

对于大多数需要集合类的方案，推荐的方法是使用 .NET Framework 类库中所提供的类。有关使用这些类的更多信息，请参见.NET Framework 类库中的泛型（C# 编程指南）。

一般情况下，创建泛型类的过程为：从一个现有的具体类开始，逐一将每个类型更改为类型参数，直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时，需要特别注意以下事项：

  • 将哪些类型通用化为类型参数。

    通常，能够参数化的类型越多，代码就会变得越灵活，重用性就越好。但是，太多的通用化会使其他开发人员难以阅读或理解代码。

  • 如果存在约束，应对类型参数应用什么约束（请参见 类型参数的约束（C# 编程指南））。

    一条有用的规则是，应用尽可能最多的约束，但仍使您能够处理必须处理的类型。例如，如果您知道您的泛型类仅用于引用类型，则应用类约束。这可以防止您的类被意外地用于值类型，并允许您对 T 使用 as 运算符以及检查空值。

  • 是否将泛型行为分解为基类和子类。

    由于泛型类可以作为基类使用，此处适用的设计注意事项与非泛型类相同。请参见本主题后面有关从泛型基类继承的规则。

  • 是否实现一个或多个泛型接口。

    例如，如果您设计一个类，该类将用于创建基于泛型的集合中的项，则可能必须实现一个接口，如 IComparable<(Of <(T>)>)，其中 T 是您的类的类型。

有关简单泛型类的示例，请参见泛型介绍（C# 编程指南）。

类型参数和约束的规则对于泛型类行为有几方面的含义，特别是关于继承和成员可访问性。您应当先理解一些术语，然后再继续进行。对于泛型类 Node<T>,，客户端代码可通过指定类型参数来引用该类，以便创建封闭式构造类型 (Node<T>,)。或者可以让类型参数处于未指定状态（例如在指定泛型基类时）以创建开放式构造类型 (Node<T>)。泛型类可以从具体的、封闭式构造或开放式构造基类继承：

C# 复制代码
class BaseNode { }
class BaseNodeGeneric<T> { }

// concrete type
class NodeConcrete<T> : BaseNode { }

//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }

//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }

非泛型类（换句话说，即具体类）可以从封闭式构造基类继承，但无法从开放式构造类或裸类型形参继承，因为在运行时客户端代码无法提供实例化基类所需的类型实参。

C# 复制代码
//No error
class Node1 : BaseNodeGeneric<int> { }

//Generates an error
//class Node2 : BaseNodeGeneric<T> {}

//Generates an error
//class Node3 : T {}

从开放式构造类型继承的泛型类必须为任何未被继承类共享的基类类型参数提供类型变量，如以下代码所示：

C# 复制代码
class BaseNodeMultiple<T, U> { }

//No error
class Node4<T> : BaseNodeMultiple<T, int> { }

//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }

//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}

从开放式构造类型继承的泛型类必须指定约束，这些约束是基类型约束的超集或暗示基类型约束：

C# 复制代码
class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }

泛型类型可以使用多个类型参数和约束，如下所示：

C# 复制代码
class SuperKeyType<K, V, U>
    where U : System.IComparable<U>
    where V : new()
{ }

开放式构造类型和封闭式构造类型可以用作方法参数：

C# 复制代码
void Swap<T>(List<T> list1, List<T> list2)
{
    //code to swap items
}

void Swap(List<int> list1, List<int> list2)
{
    //code to swap items
}

如果某个泛型类实现了接口，则可以将该类的所有实例强制转换为该接口。

泛型类是不变的。也就是说，如果输入参数指定 List<BaseClass>，则当您尝试提供 List<BaseClass> 时，将会发生编译时错误。

请参见

概念

C# 编程指南

参考

泛型（C# 编程指南）
System.Collections.Generic

其他资源

Saving the State of Enumerators（保存枚举数的状态）
An Inheritance Puzzle, Part One（一个继承难题，第一部分）


泛型接口（C# 编程指南）

[image]

C# 编程指南
泛型接口（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

为泛型集合类或表示集合中项的泛型类定义接口通常很有用。对于泛型类，使用泛型接口十分可取，例如使用 IComparable<(Of <(T>)>) 而不使用 IComparable，这样可以避免值类型的装箱和取消装箱操作。.NET Framework 类库定义了若干泛型接口，以用于 System.Collections.Generic 命名空间中的集合类。

将接口指定为类型参数的约束时，只能使用实现此接口的类型。下面的代码示例显示从 SortedList<T> 类派生的 SortedList<T> 类。有关更多信息，请参见 泛型介绍（C# 编程指南）。SortedList<T> 添加了约束 SortedList<T>。这将使 SortedList<T> 中的 BubbleSort 方法能够对列表元素使用泛型 CompareTo 方法。在此示例中，列表元素为简单类，即实现 Person 的 Person。

C# 复制代码
//Type parameter T in angle brackets.
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
    protected Node head;
    protected Node current = null;

    // Nested class is also generic on T
    protected class Node
    {
        public Node next;
        private T data;  //T as private member datatype

        public Node(T t)  //T used in non-generic constructor
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data  //T as return type of property
        {
            get { return data; }
            set { data = value; }
        }
    }

    public GenericList()  //constructor
    {
        head = null;
    }

    public void AddHead(T t)  //T as method parameter type
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    // Implementation of the iterator
    public System.Collections.Generic.IEnumerator<T> GetEnumerator()
    {
        Node current = head;
        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    // IEnumerable<T> inherits from IEnumerable, therefore this class
    // must implement both the generic and non-generic versions of
    // GetEnumerator. In most cases, the non-generic method can
    // simply call the generic method.
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
    // A simple, unoptimized sort algorithm that
    // orders list elements from lowest to highest:

    public void BubbleSort()
    {
        if (null == head || null == head.Next)
        {
            return;
        }
        bool swapped;

        do
        {
            Node previous = null;
            Node current = head;
            swapped = false;

            while (current.next != null)
            {
                //  Because we need to call this method, the SortedList
                //  class is constrained on IEnumerable<T>
                if (current.Data.CompareTo(current.next.Data) > 0)
                {
                    Node tmp = current.next;
                    current.next = current.next.next;
                    tmp.next = current;

                    if (previous == null)
                    {
                        head = tmp;
                    }
                    else
                    {
                        previous.next = tmp;
                    }
                    previous = tmp;
                    swapped = true;
                }
                else
                {
                    previous = current;
                    current = current.next;
                }
            }
        } while (swapped);
    }
}

// A simple class that implements IComparable<T> using itself as the
// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
    string name;
    int age;

    public Person(string s, int i)
    {
        name = s;
        age = i;
    }

    // This will cause list elements to be sorted on age values.
    public int CompareTo(Person p)
    {
        return age - p.age;
    }

    public override string ToString()
    {
        return name + ":" + age;
    }

    // Must implement Equals.
    public bool Equals(Person p)
    {
        return (this.age == p.age);
    }
}

class Program
{
    static void Main()
    {
        //Declare and instantiate a new generic SortedList class.
        //Person is the type argument.
        SortedList<Person> list = new SortedList<Person>();

        //Create name and age values to initialize Person objects.
        string[] names = new string[]
        {
            "Franscoise",
            "Bill",
            "Li",
            "Sandra",
            "Gunnar",
            "Alok",
            "Hiroyuki",
            "Maria",
            "Alessandro",
            "Raul"
        };

        int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };

        //Populate the list.
        for (int x = 0; x < 10; x++)
        {
            list.AddHead(new Person(names[x], ages[x]));
        }

        //Print out unsorted list.
        foreach (Person p in list)
        {
            System.Console.WriteLine(p.ToString());
        }
        System.Console.WriteLine("Done with unsorted list");

        //Sort the list.
        list.BubbleSort();

        //Print out sorted list.
        foreach (Person p in list)
        {
            System.Console.WriteLine(p.ToString());
        }
        System.Console.WriteLine("Done with sorted list");
    }
}

可将多重接口指定为单个类型上的约束，如下所示：

C# 复制代码
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}

一个接口可定义多个类型参数，如下所示：

C# 复制代码
interface IDictionary<K, V>
{
}

适用于类的继承规则同样适用于接口：

C# 复制代码
interface IMonth<T> { }

interface IJanuary     : IMonth<int> { }  //No error
interface IFebruary<T> : IMonth<int> { }  //No error
interface IMarch<T>    : IMonth<T> { }    //No error
//interface IApril<T>  : IMonth<T, U> {}  //Error

如果泛型接口为逆变的，即仅使用其类型参数作为返回值，则此泛型接口可以从非泛型接口继承。在 .NET Framework 类库中，IEnumerable<(Of <(T>)>) 从 IEnumerable 继承，因为 IEnumerable<(Of <(T>)>) 只在 GetEnumerator 的返回值和 Current 属性 getter 中使用 T。

具体类可以实现已关闭的构造接口，如下所示：

C# 复制代码
interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

只要类参数列表提供了接口必需的所有参数，泛型类便可以实现泛型接口或已关闭的构造接口，如下所示：

C# 复制代码
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }

class SampleClass1<T> : IBaseInterface1<T> { }          //No error
class SampleClass2<T> : IBaseInterface2<T, string> { }  //No error

对于泛型类、泛型结构或泛型接口中的方法，控制方法重载的规则相同。有关更多信息，请参见泛型方法（C# 编程指南）。

请参见

概念

C# 编程指南

参考

泛型介绍（C# 编程指南）
接口（C# 参考）

其他资源

.NET Framework 中的泛型


泛型方法（C# 编程指南）

[image]

C# 编程指南
泛型方法（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

泛型方法是使用类型参数声明的方法，如下所示：

C# 复制代码
static void Swap<T>(ref T lhs, ref T rhs)
{
    T temp;
    temp = lhs;
    lhs = rhs;
    rhs = temp;
}

下面的代码示例演示一种使用 int 作为类型参数的方法调用方式：

C# 复制代码
public static void TestSwap()
{
    int a = 1;
    int b = 2;

    Swap<int>(ref a, ref b);
    System.Console.WriteLine(a + " " + b);
}

也可以省略类型参数，编译器将推断出该参数。下面对 Swap 的调用等效于前面的调用：

C# 复制代码
Swap(ref a, ref b);

相同的类型推理规则也适用于静态方法和实例方法。编译器能够根据传入的方法实参推断类型形参；它无法仅从约束或返回值推断类型形参。因此，类型推理不适用于没有参数的方法。类型推理在编译时、编译器尝试解析重载方法签名之前进行。编译器向共享相同名称的所有泛型方法应用类型推理逻辑。在重载解析步骤中，编译器仅包括类型推理取得成功的那些泛型方法。

在泛型类中，非泛型方法可以访问类级别类型参数，如下所示：

C# 复制代码
class SampleClass<T>
{
    void Swap(ref T lhs, ref T rhs) { }
}

如果定义采用相同类型参数作为包含类的泛型方法，编译器将生成警告 CS0693，因为在方法范围内为内部 T 提供的参数隐藏了为外部 T 提供的参数。如果需要使用其他类型参数（而不是实例化类时提供的类型参数）来灵活地调用泛型类方法，请考虑为方法的类型参数提供另一个标识符，如下面示例的 GenericList2<T> 中所示。

C# 复制代码
class GenericList<T>
{
    // CS0693
    void SampleMethod<T>() { }
}

class GenericList2<T>
{
    //No warning
    void SampleMethod<U>() { }
}

使用约束对方法中的类型参数启用更专门的操作。此版本的 Swap<T> 现在名为 Swap<T>，它只能与实现 IComparable<(Of <(T>)>) 的类型参数一起使用。

C# 复制代码
void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
{
    T temp;
    if (lhs.CompareTo(rhs) > 0)
    {
        temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}

泛型方法可以使用许多类型参数进行重载。例如，下列方法可以全部位于同一个类中：

C# 复制代码
void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }

C# 语言规范

有关更多信息，请参见 C# 语言规范。

请参见

概念

C# 编程指南

参考

泛型介绍（C# 编程指南）
System.Collections.Generic
方法（C# 编程指南）


泛型和数组（C# 编程指南）

[image]

C# 编程指南
泛型和数组（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 2.0 以及更高版本中，下限为零的一维数组自动实现 IList<(Of <(T>)>)。这使您可以创建能够使用相同代码循环访问数组和其他集合类型的泛型方法。此技术主要对读取集合中的数据很有用。IList<(Of <(T>)>) 接口不能用于在数组中添加或移除元素。如果尝试对此上下文中的数组调用 IList<(Of <(T>)>) 方法（例如 RemoveAt），则将引发异常。

下面的代码示例演示带有 IList<(Of <(T>)>) 输入参数的单个泛型方法如何同时循环访问列表和数组，本例中为整数数组。

C# 复制代码
class Program
{
    static void Main()
    {
        int[] arr = { 0, 1, 2, 3, 4 };
        List<int> list = new List<int>();

        for (int x = 5; x < 10; x++)
        {
            list.Add(x);
        }

        ProcessItems<int>(arr);
        ProcessItems<int>(list);
    }

    static void ProcessItems<T>(IList<T> coll)
    {
        foreach (T item in coll)
        {
            System.Console.Write(item.ToString() + " ");
        }
        System.Console.WriteLine();
    }
}
说明：

尽管 ProcessItems 方法无法添加或移除项，但对于 ProcessItems 内部的 T[]，IsReadOnly 属性返回 False，因为该数组本身未声明 ReadOnly 特性。

请参见

概念

C# 编程指南

参考

泛型（C# 编程指南）
数组（C# 编程指南）
System.Collections.Generic

其他资源

.NET Framework 中的泛型


泛型委托（C# 编程指南）

[image]

C# 编程指南
泛型委托（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

委托 可以定义自己的类型参数。引用泛型委托的代码可以指定类型参数以创建已关闭的构造类型，就像实例化泛型类或调用泛型方法一样，如下例所示：

C# 复制代码
public delegate void Del<T>(T item);
public static void Notify(int i) { }

Del<int> m1 = new Del<int>(Notify);

C# 2.0 版具有称为方法组转换的新功能，此功能适用于具体委托类型和泛型委托类型，并使您可以使用如下简化的语法写入上一行：

C# 复制代码
Del<int> m2 = Notify;

在泛型类内部定义的委托使用泛型类类型参数的方式可以与类方法所使用的方式相同。

C# 复制代码
class Stack<T>
{
    T[] items;
    int index;

    public delegate void StackDelegate(T[] items);
}

引用委托的代码必须指定包含类的类型变量，如下所示：

C# 复制代码
private static void DoWork(float[] items) { }

public static void TestStack()
{
    Stack<float> s = new Stack<float>();
    Stack<float>.StackDelegate d = DoWork;
}

根据典型设计模式定义事件时，泛型委托尤其有用，因为发送方参数可以为强类型，不再需要强制转换成 Object，或反向强制转换。

C# 复制代码
delegate void StackEventHandler<T, U>(T sender, U eventArgs);

class Stack<T>
{
    public class StackEventArgs : System.EventArgs { }
    public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;

    protected virtual void OnStackChanged(StackEventArgs a)
    {
        stackEvent(this, a);
    }
}

class SampleClass
{
    public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}

public static void Test()
{
    Stack<double> s = new Stack<double>();
    SampleClass o = new SampleClass();
    s.stackEvent += o.HandleStackChange;
}

请参见

概念

C# 编程指南

参考

泛型介绍（C# 编程指南）
泛型方法（C# 编程指南）
泛型类（C# 编程指南）
泛型接口（C# 编程指南）
委托（C# 编程指南）
System.Collections.Generic

其他资源

.NET Framework 中的泛型


泛型代码中的默认关键字（C# 编程指南）

[image]

C# 编程指南
泛型代码中的默认关键字（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在泛型类和泛型方法中产生的一个问题是，在预先未知以下情况时，如何将默认值分配给参数化类型 T：

  • T 是引用类型还是值类型。

  • 如果 T 为值类型，则它是数值还是结构。

给定参数化类型 T 的一个变量 t，只有当 T 为引用类型时，语句 t = null 才有效；只有当 T 为数值类型而不是结构时，语句 t = 0 才能正常使用。解决方案是使用 default 关键字，此关键字对于引用类型会返回 null，对于数值类型会返回零。对于结构，此关键字将返回初始化为零或 null 的每个结构成员，具体取决于这些结构是值类型还是引用类型。以下来自 GenericList<T> 类的示例显示了如何使用 default 关键字。有关更多信息，请参见泛型概述。

C# 复制代码
public class GenericList<T>
{
    private class Node
    {
        //...

        public Node Next;
        public T Data;
    }

    private Node head;

    //...

    public T GetNext()
    {
        T temp = default(T);

        Node current = head;
        if (current != null)
        {
            temp = current.Data;
            current = current.Next;
        }
        return temp;
    }
}

请参见

概念

C# 编程指南

参考

泛型（C# 编程指南）
泛型方法（C# 编程指南）
System.Collections.Generic

其他资源

.NET Framework 中的泛型


C++ 模板和 C# 泛型之间的区别（C# 编程指南）

[image]

C# 编程指南
C++ 模板和 C# 泛型之间的区别（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 泛型和 C++ 模板都是用于提供参数化类型支持的语言功能。然而，这两者之间存在许多差异。在语法层面上，C# 泛型是实现参数化类型的更简单方法，不具有 C++ 模板的复杂性。此外，C# 并不尝试提供 C++ 模板所提供的所有功能。在实现层面，主要区别在于，C# 泛型类型替换是在运行时执行的，从而为实例化的对象保留了泛型类型信息。有关更多信息，请参见 运行时中的泛型（C# 编程指南）。

以下是 C# 泛型和 C++ 模板之间的主要差异：

  • C# 泛型未提供与 C++ 模板相同程度的灵活性。例如，尽管在 C# 泛型类中可以调用用户定义的运算符，但不能调用算术运算符。

  • C# 不允许非类型模板参数，如 template C<int i> {}。

  • C# 不支持显式专用化，即特定类型的模板的自定义实现。

  • C# 不支持部分专用化：类型参数子集的自定义实现。

  • C# 不允许将类型参数用作泛型类型的基类。

  • C# 不允许类型参数具有默认类型。

  • 在 C# 中，尽管构造类型可用作泛型，但泛型类型参数自身不能是泛型。C++ 确实允许模板参数。

  • C++ 允许那些可能并非对模板中的所有类型参数都有效的代码，然后将检查该代码中是否有用作类型参数的特定类型。C# 要求相应地编写类中的代码，使之能够使用任何满足约束的类型。例如，可以在 C++ 中编写对类型参数的对象使用算术运算符 + 和 - 的函数，这会在使用不支持这些运算符的类型来实例化模板时产生错误。C# 不允许这样；唯一允许的语言构造是那些可从约束推导出来的构造。

请参见

概念

C# 编程指南

参考

泛型介绍（C# 编程指南）

其他资源

Templates


运行时中的泛型（C# 编程指南）

[image]

C# 编程指南
运行时中的泛型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

将泛型类型或方法编译为 Microsoft 中间语言 (MSIL) 时，它包含将其标识为具有类型参数的元数据。泛型类型的 MSIL 的使用因所提供的类型参数是值类型还是引用类型而不同。

第一次用值类型作为参数来构造泛型类型时，运行时会创建专用泛型类型，将提供的参数代入到 MSIL 中的适当位置。对于每个用作参数的唯一值类型，都会创建一次专用泛型类型。

例如，假设您的程序代码声明了一个由整数构造的堆栈：

C# 复制代码
Stack<int> stack;

在此位置，运行时生成 Stack<(Of <(T>)>) 类的专用版本，并相应地用整数替换其参数。现在，只要程序代码使用整数堆栈，运行时就会重用生成的专用 Stack<(Of <(T>)>) 类。在下面的示例中，创建了整数堆栈的两个实例，它们共享 Stack<int> 代码的单个实例：

C# 复制代码
Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

但是，假定在代码中的另一个位置创建了使用不同值类型（比如 long 或用户定义的结构）作为其参数的另一个 Stack<(Of <(T>)>) 类。因此，运行时将生成另一个版本的泛型类型，并在 MSIL 中的适当位置替换 long。由于每个专用泛型类本身就包含值类型，因此不再需要转换。

对于引用类型，泛型的工作方式略有不同。第一次使用任何引用类型构造泛型类型时，运行时会创建专用泛型类型，用对象引用替换 MSIL 中的参数。然后，每次使用引用类型作为参数来实例化构造类型时，无论引用类型的具体类型是什么，运行时都会重用以前创建的泛型类型的专用版本。之所以可以这样，是因为所有引用的大小相同。

例如，假设您有两个引用类型：一个 Customer 类和一个 Order 类，并且同时假设您创建了一个 Customer 类型的堆栈：

C# 复制代码
class Customer { }
class Order { }
C# 复制代码
Stack<Customer> customers;

此时，运行时将生成 Stack<(Of <(T>)>) 类的一个专用版本，该版本存储稍后将填写的对象引用，而不是存储数据。假设下一行代码创建另一个引用类型的堆栈，该堆栈名为 Order：

C# 复制代码
Stack<Order> orders = new Stack<Order>();

不同于值类型，对于 Order 类型不创建 Stack<(Of <(T>)>) 类的另一个专用版本。而是创建 Stack<(Of <(T>)>) 类的一个专用版本实例，并将 orders 变量设置为引用它。假设接下来您遇到一行创建 Customer 类型堆栈的代码：

C# 复制代码
customers = new Stack<Customer>();

与前面使用 Order 类型创建的 Stack<(Of <(T>)>) 类一样，创建了专用 Stack<(Of <(T>)>) 类的另一个实例。包含在其中的指针设置为引用 Customer 类型大小的内存区域。因为引用类型的数量会随程序的不同而大幅变化，C# 泛型实现将编译器为引用类型的泛型类创建的专用类的数量减小到一个，从而大幅减小代码量。

此外，使用值类型或引用类型参数实例化泛型 C# 类时，反射可以在运行时查询它，并且可以确定它的实际类型及其类型参数。

请参见

概念

C# 编程指南

参考

泛型介绍（C# 编程指南）
System.Collections.Generic

其他资源

.NET Framework 中的泛型


.NET Framework 类库中的泛型（C# 编程指南）

[image]

C# 编程指南
.NET Framework 类库中的泛型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

.NET Framework 2.0 版类库提供一个新的命名空间 System.Collections.Generic，其中包括几个随时可用的泛型集合类和关联接口。其他命名空间（如 System）也提供新的泛型接口，如 IComparable<(Of <(T>)>)。与早期版本的 .NET Framework 所提供的非泛型集合类相比，这些类和接口更为高效和类型安全。在设计和实现自己的自定义集合类之前，请考虑是否能够使用 .NET Framework 类库所提供的类，或者是否能从 .NET Framework 类库所提供的类派生一个类。

请参见

概念

C# 编程指南
何时使用泛型集合

参考

泛型介绍（C# 编程指南）

其他资源

集合和数据结构


泛型和反射（C# 编程指南）

[image]

C# 编程指南
泛型和反射（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

因为公共语言运行时 (CLR) 能够在运行时访问泛型类型信息，所以可以使用反射获取关于泛型类型的信息，方法与用于非泛型类型的方法相同。有关更多信息，请参见 运行时中的泛型（C# 编程指南）。

在 .NET Framework 2.0 中，有几个新成员添加到了 Type 类中，用以启用泛型类型的运行时信息。请参见有关这些类的文档来了解有关如何使用这些方法和属性的更多信息。System.Reflection.Emit 命名空间还包含支持泛型的新成员。请参见 如何：定义具有反射发出的泛型类型。

有关泛型反射中使用的术语的固定条件列表，请参见 IsGenericType 属性备注。

System.Type 成员名称 | 说明
IsGenericType | 如果类型为泛型，则返回 true。
GetGenericArguments | 返回 Type 对象数组，这些对象表示为构造类型提供的类型变量，或泛型类型定义的类型参数。
GetGenericTypeDefinition | 返回当前构造类型的基础泛型类型定义。
GetGenericParameterConstraints | 返回表示当前泛型类型参数约束的 Type 对象的数组。
ContainsGenericParameters | 如果类型或其任意封闭类型或方法包含没有被提供特定类型的类型参数，则返回 true。
GenericParameterAttributes | 获取 GenericParameterAttributes 标志的组合，这些标志描述当前泛型类型参数的特殊约束。
GenericParameterPosition | 对于表示类型参数的 Type 对象，获取类型参数在声明该类型参数的泛型类型定义或泛型方法定义的类型参数列表中的位置。
IsGenericParameter | 获取一个值，该值指示当前 Type 是表示泛型类型定义的类型参数，还是泛型方法定义的类型参数。
IsGenericTypeDefinition | 获取一个值，该值指示当前 Type 是否表示可以用来构造其他泛型类型的泛型类型定义。如果类型表示泛型类型的定义，则返回 true。
DeclaringMethod | 返回定义当前泛型类型参数的泛型方法；如果类型参数不是由泛型方法定义的，则返回空值。
MakeGenericType | 用类型数组的元素替代当前泛型类型定义的类型参数，并返回表示结果构造类型的 Type 对象。

此外，MethodInfo 类中还添加了新成员以启用泛型方法的运行时信息。有关泛型方法反射中使用的术语的固定条件列表，请参见 IsGenericMethod 属性备注。

System.Reflection.MemberInfo 成员名称 | 说明
IsGenericMethod | 如果方法为泛型，则返回 true。
GetGenericArguments | 返回 Type 对象数组，这些对象表示构造泛型方法的类型变量，或泛型方法定义的类型参数。
GetGenericMethodDefinition | 返回当前构造方法的基础泛型方法定义。
ContainsGenericParameters | 如果方法或其任意封闭类型包含没有被提供特定类型的任何类型参数，则返回 true。
IsGenericMethodDefinition | 如果当前 MethodInfo 表示泛型方法的定义，则返回 true。
MakeGenericMethod | 用类型数组的元素替代当前泛型方法定义的类型参数，并返回表示结果构造方法的 MethodInfo 对象。

请参见

概念

C# 编程指南
反射和泛型概述

参考

泛型（C# 编程指南）

其他资源

.NET Framework 中的泛型


泛型和属性（C# 编程指南）

[image]

C# 编程指南
泛型和属性（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

属性可以应用于泛型类型中，方式与应用于非泛型类型相同。有关应用属性的更多信息，请参见 属性（C# 编程指南）。

自定义属性只允许引用开放泛型类型（未提供类型参数的泛型类型）和封闭构造泛型类型（为所有类型参数提供参数）。

下面的示例使用此自定义属性：

C# 复制代码
class CustomAttribute : System.Attribute
{
    public System.Object info;
}

属性可以引用开放式泛型类型：

C# 复制代码
public class GenericClass1<T> { }

[CustomAttribute(info = typeof(GenericClass1<>))]
class ClassA { }

使用数目适当的若干个逗号指定多个类型参数。在此示例中，GenericClass2 有两个类型参数：

C# 复制代码
public class GenericClass2<T, U> { }

[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }

属性可以引用封闭式构造泛型类型：

C# 复制代码
public class GenericClass3<T, U, V> { }

[CustomAttribute(info = typeof(GenericClass3<int, double, string>))]
class ClassC { }

引用泛型类型参数的属性将导致编译时错误：

C# 复制代码
//[CustomAttribute(info = typeof(GenericClass3<int, T, string>))]  //Error
class ClassD<T> { }

不能从 Attribute 继承泛型类型：

C# 复制代码
//public class CustomAtt<T> : System.Attribute {}  //Error

若要在运行时获得有关泛型类型或类型参数的信息，可以使用 System.Reflection 的方法。有关更多信息，请参见泛型和反射（C# 编程指南）

请参见

概念

C# 编程指南
属性 (Attribute) 概述

参考

泛型（C# 编程指南）


泛型类型中的变化（C# 编程指南）

[image]

C# 编程指南
泛型类型中的变化（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 中添加泛型的一个主要好处是能够使用 System.Collections.Generic 命名空间中的类型轻松地创建强类型集合。例如，您可以创建一个类型为 List<int> 的变量，编译器将检查对该变量的所有访问，确保只将 List<int> 添加到该集合中。与 C# 1.0 版中的非类型化集合相比，这是可用性方面的一个很大改进。

遗憾的是强类型集合有自身的缺陷。例如，假设您有一个强类型 List<object>，您想将 List<int> 中的所有元素追加到 List<object> 中。您可能希望能够如下面的示例一样编写代码：

C# 复制代码
List<int> ints = new List<int>();
ints.Add(1);
ints.Add(10);
ints.Add(42);
List<object> objects = new List<object>();

// doesnt compile ints is not a IEnumerable<object>
//objects.AddRange(ints);

在这种情况下，您希望能够将 List<int>（它同时也是 IEnumerable<int>）作为 IEnumerable<object> 处理。这样做看起来似乎很合理，因为 int 可以转换为对象。这与能够将 string[] 当作 object[]（现在您就可以这样做）非常相似。如果您正面临这种情况，那么您需要一种称为泛型变化的功能，它将泛型类型的一种实例化（在本例中为 IEnumerable<int>）当成该类型的另一种实例化（在本例中为 IEnumerable<object>）。

由于 C# 不支持泛型类型的变化，所以当遇到这种情况时，您需要尝试几种可能的方法来解决此问题。对于最简单的情况，例如上例中的单个方法 AddRange，您可以声明一个简单的帮助器方法来为您执行转换。例如，您可以编写如下方法：

C# 复制代码
// Simple workaround for single method
// Variance in one direction only
public static void Add<S, D>(List<S> source, List<D> destination)
    where S : D
{
    foreach (S sourceElement in source)
    {
        destination.Add(sourceElement);
    }
}

它使您能够完成以下操作：

C# 复制代码
// does compile
VarianceWorkaround.Add<int, object>(ints, objects);

此示例演示了一种简单的变化解决方法的一些特征。帮助器方法带两个类型参数，分别对应于源和目标，源类型参数 S 有一个约束，即目标类型参数 D。这意味着读取的 List<> 所包含的元素必须可以转换为插入的 List<> 类型的元素。这使编译器可以强制 int 可转换为对象。将类型参数约束为从另一类型参数派生被称为裸类型参数约束。

定义一个方法来解决变化问题不算是一种过于拙劣的方法。遗憾的是变化问题很快就会变得非常复杂。下一级别的复杂性产生在当您想要将一个实例化的接口当作另一个实例化的接口时。例如，您有一个 IEnumerable<int>，您想将它传递给一个只以 IEnumerable<object> 为参数的方法。同样，这样做也是有一定意义的，因为您可以将 IEnumerable<object> 看作对象的序列，将 IEnumerable<int> 看作 ints 的序列。由于 ints 是对象，因此 ints 的序列应当可以被当作对象序列。例如：

C# 复制代码
static void PrintObjects(IEnumerable<object> objects)
{
    foreach (object o in objects)
    {
        Console.WriteLine(o);
    }
}

您可能希望能够如下面的示例一样调用：

C# 复制代码
// would like to do this, but cant ...
// ... ints is not an IEnumerable<object>
//PrintObjects(ints);

接口 case 的解决方法是：创建为接口的每个成员执行转换的包装对象。这可能类似于如下示例：

C# 复制代码
// Workaround for interface
// Variance in one direction only so type expressinos are natural
public static IEnumerable<D> Convert<S, D>(IEnumerable<S> source)
    where S : D
{
    return new EnumerableWrapper<S, D>(source);
}

private class EnumerableWrapper<S, D> : IEnumerable<D>
    where S : D
{

它使您能够完成以下操作：

C# 复制代码
PrintObjects(VarianceWorkaround.Convert<int, object>(ints));

同样，请注意包装类和帮助器方法的裸类型参数约束。此系统已经变得相当复杂，但是包装类中的代码非常简单；它只委托给所包装接口的成员，除了简单的类型转换外，不执行其他任何操作。为什么不让编译器允许从 IEnumerable<int> 直接转换为 IEnumerable<object> 呢？

尽管在查看集合的只读视图的情况下，变化是类型安全的，然而在同时涉及读写操作的情况下，变化不是类型安全的。例如，不能用此自动方法处理 IList<> 接口。您仍然可以编写一个帮助器，用类型安全的方式包装 IList<> 上的所有读操作，但是写操作的包装就不能如此轻松了。

下面是处理 IList<(Of <(T>)>) 接口的变化的包装的一部分，它显示在读和写两个方面的变化所引发的问题：

C# 复制代码
private class ListWrapper<S, D> : CollectionWrapper<S, D>, IList<D>
    where S : D
{
    public ListWrapper(IList<S> source) : base(source)
    {
        this.source = source;
    }

    public int IndexOf(D item)
    {
        if (item is S)
        {
            return this.source.IndexOf((S) item);
        }
        else
        {
            return -1;
        }
    }

    // variance the wrong way ...
    // ... can throw exceptions at runtime
    public void Insert(int index, D item)
    {
        if (item is S)
        {
            this.source.Insert(index, (S)item);
        }
        else
        {
            throw new Exception("Invalid type exception");
        }
    }

包装中的 Insert 方法有一个问题。它将 D 当作参数，但是它必须将 D 插入到 IList<S> 中。由于 D 是 S 的基类型，不是所有的 D 都是 S，因此 Insert 操作可能会失败。此示例与数组的变化有相似之处。当将对象插入 object[] 时，将执行动态类型检查，因为 object[] 在运行时可能实际为 string[]。例如：

C# 复制代码
object[] objects = new string[10];

// no problem, adding a string to a string[]
objects[0] = "hello";

// runtime exception, adding an object to a string[]
objects[1] = new object();

在 IList<> 示例中，当实际类型在运行时与需要的类型不匹配时，可以仅仅引发 Insert 方法的包装。所以，您同样可以想象得到编译器将为程序员自动生成此包装。然而，有时候并不应该执行此策略。IndexOf 方法在集合中搜索所提供的项，如果找到该项，则返回该项在集合中的索引。然而，如果没有找到该项，IndexOf 方法将仅仅返回 -1，而并不引发。这种类型的包装不能由自动生成的包装提供。

到目前为止，我们描述了泛型变化问题的两种最简单的解决方法。然而，变化问题可能变得要多复杂就有多复杂。例如，当您将 List<IEnumerable<int>>当作 List<IEnumerable<object>>，或将 List<IEnumerable<IEnumerable<int>>> 当作 List<IEnumerable<IEnumerable<object>>> 时。

当生成这些包装以解决代码中的变化问题时，可能给代码带来巨大的系统开销。同时，它还会带来引用标识问题，因为每个包装的标识都与原始集合的标识不一样，从而会导致不易察觉的 Bug。当使用泛型时，应选择类型实例化，以减少紧密关联的组件之间的不匹配问题。这可能要求在设计代码时做出一些妥协。与往常一样，设计程序时必须权衡相互冲突的要求，在设计过程中应当考虑语言中类型系统具有的约束。

有的类型系统将泛型变化作为语言的首要任务。Eiffel 是其中一个主要示例。然而，将泛型变化作为类型系统的首要任务会明显增加 C# 的类型系统的复杂性，即使在不涉及变化的相对简单方案中也是如此。因此，C# 的设计人员觉得不包括变化才是 C# 的适当选择。

下面是上述示例的完整源代码。

C# 复制代码
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

static class VarianceWorkaround
{
    // Simple workaround for single method
    // Variance in one direction only
    public static void Add<S, D>(List<S> source, List<D> destination)
        where S : D
    {
        foreach (S sourceElement in source)
        {
            destination.Add(sourceElement);
        }
    }

    // Workaround for interface
    // Variance in one direction only so type expressinos are natural
    public static IEnumerable<D> Convert<S, D>(IEnumerable<S> source)
        where S : D
    {
        return new EnumerableWrapper<S, D>(source);
    }

    private class EnumerableWrapper<S, D> : IEnumerable<D>
        where S : D
    {
        public EnumerableWrapper(IEnumerable<S> source)
        {
            this.source = source;
        }

        public IEnumerator<D> GetEnumerator()
        {
            return new EnumeratorWrapper(this.source.GetEnumerator());
        }

        IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private class EnumeratorWrapper : IEnumerator<D>
        {
            public EnumeratorWrapper(IEnumerator<S> source)
            {
                this.source = source;
            }

            private IEnumerator<S> source;

            public D Current
            {
                get { return this.source.Current; }
            }

            public void Dispose()
            {
                this.source.Dispose();
            }

            object IEnumerator.Current
            {
                get { return this.source.Current; }
            }

            public bool MoveNext()
            {
                return this.source.MoveNext();
            }

            public void Reset()
            {
                this.source.Reset();
            }
        }

        private IEnumerable<S> source;
    }

    // Workaround for interface
    // Variance in both directions, causes issues
    // similar to existing array variance
    public static ICollection<D> Convert<S, D>(ICollection<S> source)
        where S : D
    {
        return new CollectionWrapper<S, D>(source);
    }

    private class CollectionWrapper<S, D>
        : EnumerableWrapper<S, D>, ICollection<D>
        where S : D
    {
        public CollectionWrapper(ICollection<S> source)
            : base(source)
        {
        }

        // variance going the wrong way ...
        // ... can yield exceptions at runtime
        public void Add(D item)
        {
            if (item is S)
            {
                this.source.Add((S)item);
            }
            else
            {
                throw new Exception(@"Type mismatch exception, due to type hole introduced by variance.");
            }
        }

        public void Clear()
        {
            this.source.Clear();
        }

        // variance going the wrong way ...
        // ... but the semantics of the method yields reasonable semantics
        public bool Contains(D item)
        {
            if (item is S)
            {
                return this.source.Contains((S)item);
            }
            else
            {
                return false;
            }
        }

        // variance going the right way ...
        public void CopyTo(D[] array, int arrayIndex)
        {
            foreach (S src in this.source)
            {
                array[arrayIndex++] = src;
            }
        }

        public int Count
        {
            get { return this.source.Count; }
        }

        public bool IsReadOnly
        {
            get { return this.source.IsReadOnly; }
        }

        // variance going the wrong way ...
        // ... but the semantics of the method yields reasonable  semantics
        public bool Remove(D item)
        {
            if (item is S)
            {
                return this.source.Remove((S)item);
            }
            else
            {
                return false;
            }
        }

        private ICollection<S> source;
    }

    // Workaround for interface
    // Variance in both directions, causes issues similar to existing array variance
    public static IList<D> Convert<S, D>(IList<S> source)
        where S : D
    {
        return new ListWrapper<S, D>(source);
    }

    private class ListWrapper<S, D> : CollectionWrapper<S, D>, IList<D>
        where S : D
    {
        public ListWrapper(IList<S> source) : base(source)
        {
            this.source = source;
        }

        public int IndexOf(D item)
        {
            if (item is S)
            {
                return this.source.IndexOf((S) item);
            }
            else
            {
                return -1;
            }
        }

        // variance the wrong way ...
        // ... can throw exceptions at runtime
        public void Insert(int index, D item)
        {
            if (item is S)
            {
                this.source.Insert(index, (S)item);
            }
            else
            {
                throw new Exception("Invalid type exception");
            }
        }

        public void RemoveAt(int index)
        {
            this.source.RemoveAt(index);
        }

        public D this[int index]
        {
            get
            {
                return this.source[index];
            }
            set
            {
                if (value is S)
                    this.source[index] = (S)value;
                else
                    throw new Exception("Invalid type exception.");
            }
        }

        private IList<S> source;
    }
}

namespace GenericVariance
{
    class Program
    {
        static void PrintObjects(IEnumerable<object> objects)
        {
            foreach (object o in objects)
            {
                Console.WriteLine(o);
            }
        }

        static void AddToObjects(IList<object> objects)
        {
            // this will fail if the collection provided is a wrapped collection
            objects.Add(new object());
        }
        static void Main(string[] args)
        {
            List<int> ints = new List<int>();
            ints.Add(1);
            ints.Add(10);
            ints.Add(42);
            List<object> objects = new List<object>();

            // doesnt compile ints is not a IEnumerable<object>
            //objects.AddRange(ints);

            // does compile
            VarianceWorkaround.Add<int, object>(ints, objects);

            // would like to do this, but cant ...
            // ... ints is not an IEnumerable<object>
            //PrintObjects(ints);

            PrintObjects(VarianceWorkaround.Convert<int, object>(ints));

            AddToObjects(objects); // this works fine
            AddToObjects(VarianceWorkaround.Convert<int, object>(ints));
        }
        static void ArrayExample()
        {
            object[] objects = new string[10];

            // no problem, adding a string to a string[]
            objects[0] = "hello";

            // runtime exception, adding an object to a string[]
            objects[1] = new object();
        }
    }
}

请参见

概念

C# 编程指南

参考

泛型（C# 编程指南）


LINQ 查询表达式（C# 编程指南）

[image]

C# 编程指南
LINQ 查询表达式（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

语言集成查询 (LINQ) 是一组技术的名称，这些技术建立在将查询功能直接集成到 C# 语言（以及 Visual Basic 和可能的任何其他 .NET 语言）的基础上。借助于 LINQ，查询现在已是高级语言构造，就如同类、方法、事件等等。

对于编写查询的开发人员来说，LINQ 最明显的“语言集成”部分是查询表达式。查询表达式是使用 C# 3.0 中引入的声明性查询语法编写的。通过使用查询语法，您甚至可以使用最少的代码对数据源执行复杂的筛选、排序和分组操作。您使用相同的基本查询表达式模式来查询和转换 SQL 数据库、ADO.NET 数据集、XML 文档和流以及 .NET 集合中的数据。

下面的示例演示了完整的查询操作。完整操作包括创建数据源、定义查询表达式，以及在 foreach 语句中执行查询。

C# 复制代码
class LINQQueryExpressions
{
    static void Main()
    {

        // Specify the data source.
        int[] scores = new int[] { 97, 92, 81, 60 };

        // Define the query expression.
        IEnumerable<int> scoreQuery =
            from score in scores
            where score > 80
            select score;

        // Execute the query.
        foreach (int i in scoreQuery)
        {
            Console.Write(i + " ");
        }
    }
}
// Output: 97 92 81

有关 C# 中的 LINQ 基础知识的更多信息，请参见 C# 中的 LINQ 入门。

查询表达式概述

  • 查询表达式可用于查询和转换来自任意支持 LINQ 的数据源中的数据。例如，单个查询可以从 SQL 数据库检索数据，并生成 XML 流作为输出。

  • 查询表达式容易掌握，因为它们使用许多常见的 C# 语言构造。有关更多信息，请参见 C# 中的 LINQ 入门。

  • 查询表达式中的变量都是强类型的，但许多情况下您不需要显式提供类型，因为编译器可以推断类型。有关更多信息，请参见查询操作中的类型关系 (LINQ)。

  • 在您循环访问 foreach 语句中的查询变量之前，不会执行查询。有关更多信息，请参见 LINQ 查询介绍。

  • 在编译时，根据 C# 规范中设置的规则将查询表达式转换为“标准查询运算符”方法调用。任何可以使用查询语法表示的查询也可以使用方法语法表示。但是，在大多数情况下，查询语法更易读和简洁。有关更多信息，请参见 C# 语言规范和标准查询运算符概述。

  • 作为编写 LINQ 查询的一项规则，建议尽量使用查询语法，只在必需的情况下才使用方法语法。这两种不同形式在语义或性能上没有区别。查询表达式通常比用方法语法编写的等效表达式更易读。

  • 一些查询操作，如 Count 或 Max，没有等效的查询表达式子句，因此必须表示为方法调用。方法语法可以通过多种方式与查询语法组合。有关更多信息，请参见查询语法与方法语法 (LINQ)。

  • 查询表达式可以编译为表达式目录树或委托，具体取决于查询所应用到的类型。IEnumerable<(Of <(T>)>) 查询编译为委托。IQueryable 和 IQueryable<(Of <(T>)>) 查询编译为表达式目录树。有关更多信息，请参见表达式目录树。

下表列出了一些主题，提供有关常见任务的查询和代码示例的其他信息。

主题 | 说明
查询表达式基础（C# 编程指南） | 介绍基本查询概念并提供 C# 查询语法的示例。
如何：在 C# 中编写 LINQ 查询 | 提供若干基本查询表达式类型的示例。
如何：在查询表达式中处理异常（C# 编程指南） | 如何以及何时将可能会引发异常的代码移出查询表达式。
如何：从多个源填充对象集合 (LINQ) | 如何使用 select 语句将来自不同源的数据合并为新类型。
如何：以各种方式对结果进行分组（C# 编程指南） | 演示使用 group 子句的不同方法。
如何：对组进行分组（C# 编程指南） | 演示如何创建嵌套组。
如何：对分组操作执行子查询（C# 编程指南） | 演示如何使用查询中的子表达式作为新查询的数据源。
如何：按连续键对结果进行分组（C# 编程指南） | 演示如何实现线程安全的标准查询运算符，该运算符可对流式数据源执行分组操作。
如何：在运行时动态指定谓词筛选器（C# 编程指南） | 演示如何为 where 子句的相等性比较提供任意数目的值。
如何：在内存中存储查询结果（C# 编程指南） | 阐释如何具体化和存储查询结果，而不必使用 foreach 循环。
如何：从方法中返回查询（C# 编程指南） | 演示如何从方法返回查询变量，以及如何将它们作为输入参数传递给方法。
如何：执行自定义联接操作（C# 编程指南） | 演示如何基于任何类型的谓词函数执行联接运算。
如何：使用复合键进行联接（C# 编程指南） | 演示如何基于多个匹配键联接两个源。
如何：对 Join 子句的结果进行排序（C# 编程指南） | 演示如何对联接运算生成的序列进行排序。
如何：执行内部联接（C# 编程指南） | 演示如何在 LINQ 中执行内联。
如何：执行分组联接（C# 编程指南） | 演示如何在 LINQ 中生成已分组的联接。
如何：执行左外部联接（C# 编程指南） | 演示如何在 LINQ 中生成左外部联接。
如何：在查询表达式中处理 Null 值（C# 编程指南） | 演示如何在 LINQ 查询中处理 Null 值。

请参见

任务

演练：在 C# 中编写查询 (LINQ)

概念

基本查询操作 (LINQ)
查询语法与方法语法 (LINQ)
标准查询运算符概述

参考

LINQ 常规编程指南

其他资源

语言集成查询 (LINQ)
查询关键字（C# 参考）
How Linq to Objects Queries Work（Linq to Objects 查询如何工作）
Reading and Writing Queries（阅读和编写查询）
What is a collection?（什么是集合？）
Link to Everything: A List of LINQ Providers（链接到完整内容：LINQ 提供程序列表）


查询表达式基础（C# 编程指南）

[image]

C# 编程指南
查询表达式基础（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

什么是查询？它有什么用途？

“查询”是指一组指令，这些指令描述要从一个或多个给定数据源检索的数据以及返回的数据应该使用的格式和组织形式。查询不同于它所产生的结果。

通常，源数据会在逻辑上组织为相同种类的元素序列。SQL 数据库表包含一个行序列。与此类似，ADO.NETDataRow 包含一个 DataTable 对象序列。在 XML 文件中，有一个 XML 元素“序列”（不过这些元素按分层形式组织为树结构）。内存中的集合包含一个对象序列。

从应用程序的角度来看，原始源数据的具体类型和结构并不重要。应用程序始终将源数据视为一个 IEnumerable<(Of <(T>)>) 或 IQueryable<(Of <(T>)>) 集合。在 LINQ to XML 中，源数据显示为一个 IEnumerable<XElement>。在 LINQ to DataSet 中，它是一个 IEnumerable<DataRow>。在 LINQ to SQL 中，它是您定义用来表示 SQL 表中数据的任何自定义对象的 IEnumerable 或 IQueryable。

指定此源序列后，查询可以进行下列三项工作之一：

  • 检索一个元素子集以产生一个新序列，但不修改单个元素。然后，查询可以按各种方式对返回的序列进行排序或分组，如下面的示例所示（假定 scores 是 int[]）：

    C# 复制代码
    IEnumerable<int> highScoresQuery =
    from score in scores
    where score > 80
    orderby score descending
    select score;

  • 如上一个示例所述检索一个元素序列，但是将这些元素转换为具有新类型的对象。例如，查询可以只从数据源中的某些客户记录检索姓氏。或者，查询可以检索完整的记录，再使用它构建另一个内存中对象类型甚至 XML 数据，然后生成最终的结果序列。下面的示例演示了从 int 到 string 的转换。请注意 highScoresQuery 的新类型。

    C# 复制代码
    IEnumerable<string> highScoresQuery2 =
    from score in scores
    where score > 80
    orderby score descending
    select String.Format("The score is {0}", score);

  • 检索有关源数据的单一值，例如：

    • 符合某个条件的元素的数量。

    • 具有最大值或最小值的元素。

    • 符合某个条件的第一个元素，或一组指定元素中的特定值之和。例如，下面的查询从 scores 整数数组中返回高于 80 的分数的数量。

    C# 复制代码
    int highScoreCount =
    (from score in scores
     where score > 80
     select score)
     .Count();

    在上一个示例中，请注意在 Count 方法调用之前的查询表达式两旁使用了括号。另一种表示方式是使用一个新变量来存储具体结果。此技术的可读性更好，因为它将存储查询的变量与存储结果的查询区分开来。

    C# 复制代码
    IEnumerable<int> highScoresQuery3 =
    from score in scores
    where score > 80
    select score;

int scoreCount = highScoresQuery3.Count();

在上一个示例中，查询是在 Count 调用中执行的，因为 Count 必须循环访问结果以便确定 highScoresQuery 返回的元素数量。

什么是查询表达式？

“查询表达式”是用查询语法表示的查询，是一流的语言构造。它就像任何其他表达式一样，并且可以用在 C# 表达式有效的任何上下文中。查询表达式由一组用类似于 SQL 或 XQuery 的声明性语法编写的子句组成。每个子句又包含一个或多个 C# 表达式，而这些表达式本身又可能是查询表达式或包含查询表达式。

查询表达式必须以 from 子句开头，并且必须以 select 或 group 子句结尾。在第一个 from 子句和最后一个 select 或 group 子句之间，查询表达式可以包含一个或多个下列可选子句：where、orderby、join、let 甚至附加的 from 子句。还可以使用 into 关键字使 join 或 group 子句的结果能够充当同一查询表达式中附加查询子句的源。

查询变量

在 LINQ 中，查询变量是任何存储查询（而非查询结果）的变量。更具体地说，查询变量始终是一个可枚举的类型，当在 foreach 语句中或在对其 IEnumerator.MoveNext 方法的直接调用中循环访问它时，它会生成一序列元素。

下面的代码示例演示了一个简单的查询表达式，它含有一个数据源、一个筛选子句和一个排序子句，但不对源元素进行转换。select 子句结束了该查询。

C# 复制代码
static void Main()
{
    // Data source.
    int[] scores = { 90, 71, 82, 93, 75, 82 };

    // Query Expression.
    IEnumerable<int> scoreQuery = //query variable
        from score in scores //required
        where score > 80 // optional
        orderby score descending // optional
        select score; //must end with select or group

    // Execute the query to produce the results
    foreach (int testScore in scoreQuery)
    {
        Console.WriteLine(testScore);
    }
}
// Outputs: 90 82 93 82

在上一个示例中，scoreQuery 是一个查询变量，有时简称为“查询”。查询变量并不存储实际的结果数据（这些数据是在 foreach 循环中产生的）。另外，当 foreach 语句执行时，查询结果并不是通过查询变量 scoreQuery 返回的。相反，它们是通过迭代变量 testScore 返回的。可以在另一个 foreach 循环中迭代 scoreQuery 变量。只要该变量和数据源都没有修改，该变量都将产生相同的结果。

查询变量可以存储用查询语法或方法语法（或二者的组合）表示的查询。在下面的示例中，queryMajorCities 和 queryMajorCities2 都是查询变量：

C# 复制代码
//Query syntax
IEnumerable<City> queryMajorCities =
    from city in cities
    where city.Population > 100000
    select city;

// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

另一方面，下面的两个示例演示了不是查询变量的变量，即使每个变量都用查询进行了初始化。它们不是查询变量的原因是它们存储了结果：

C# 复制代码
int highestScore =
    (from score in scores
     select score)
    .Max();

// or split the expression
IEnumerable<int> scoreQuery =
    from score in scores
    select score;

int highScore = scoreQuery.Max();

IEnumerable<City> largeCityList =
    (from country in countries
     from city in country.Cities
        where city.Population > 10000
        select city)
       .ToList();

// or split the expression
IEnumerable<City> largeCityList2 =
    from country in countries
     from city in country.Cities
     where city.Population > 10000
     select city;

List<City> largeCities = largeCityList2.ToList();
说明：

在 LINQ 文档中，存储查询的变量在其名称中包含单词“query”，而存储实际结果的变量在其名称中不包含单词“query”。

有关用于表示查询的不同方式的更多信息，请参见查询语法与方法语法 (LINQ)。

查询变量的显式类型化和隐式类型化

本文档通常提供查询变量的显式类型，以便演示查询变量和 select 子句之间的类型关系。但是，也可以使用 var 关键字指示编译器在编译时推断查询变量（或任何其他本地变量）的类型。例如，还可以使用隐式类型化表示本主题前面部分中演示的查询示例：

C# 复制代码
// Use of var is optional here and in all queries.
// queryCities is an IEnumerable<City> just as
// when it is explicitly typed.
var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

有关更多信息，请参见隐式类型的局部变量（C# 编程指南）和查询操作中的类型关系 (LINQ)。

开始查询表达式

查询表达式必须以 from 子句开头。它同时指定了数据源和范围变量。在对源序列进行遍历的过程中，范围变量表示源序列中的每个后续元素。将根据数据源中元素的类型对范围变量进行强类型化。在下面的示例中，因为 countries 是 Country 对象数组，所以范围变量也被类型化为 Country，这样就可以使用点运算符来访问该类型的任何可用成员。

C# 复制代码
IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //sq km
    select country;

在使用分号或延续子句退出查询之前，范围变量将一直位于范围中。

查询表达式可以包含多个 from 子句。当源序列中的每个元素本身就是集合或包含集合时，可使用附加的 from 子句。例如，假定您具有一个 Country 对象集合，而其中每个对象都包含一个名为 Cities 的 City 对象集合。若要查询每个 Country 中的 City 对象，请使用两个from 子句，如下所示：

C# 复制代码
IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

有关更多信息，请参见 from 子句（C# 参考）。

结束查询表达式

查询表达式必须以 select 子句或 group 子句结尾。

group 子句

使用 group 子句可产生按照指定的键组织的组序列。键可以采用任何数据类型。例如，下面的查询创建一个组序列，该序列包含一个或多个 Country 对象，并且它的键是字符串值。

C# 复制代码
var queryCountryGroups =
    from country in countries
    group country by country.Name[0];

有关分组的更多信息，请参见 group 子句（C# 参考）。

select 子句

使用 select 子句可产生所有其他类型的序列。简单的 select 子句只是产生与数据源中包含的对象具有相同类型的对象的序列。在此示例中，数据源包含 Country 对象。orderby 子句只是将元素重新排序，而 select 子句则产生重新排序的 Country 对象的序列。

C# 复制代码
IEnumerable<Country> sortedQuery =
    from country in countries
    orderby country.Area
    select country;

可以使用 select 子句将源数据转换为新类型的序列。这一转换也称为“投影”。在下面的示例中，select 子句对一个匿名类型序列进行投影，该序列仅包含原始元素中各字段的子集。请注意，新对象是使用对象初始值设定项初始化的。

C# 复制代码
// Here var is required because the query
// produces an anonymous type.
var queryNameAndPop =
    from country in countries
    select new { Name = country.Name, Pop = country.Population };

有关使用 select 子句转换源数据的所有方式的更多信息，请参见 select 子句（C# 参考）。

使用“into”进行延续

可以在 select 或 group 子句中使用 into 关键字来创建用于存储查询的临时标识符。当您必须在分组或选择操作之后对查询执行附加查询操作时，需要这样做。在下面的示例中，以一千万人口范围为界对 countries 进行分组。在创建这些组之后，使用附加子句筛选掉某些组，然后按升序对剩下的组进行排序。若要执行这些附加操作，需要使用由 countryGroup 表示的延续。

C# 复制代码
// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
    from country in countries
    let percentile = (int) country.Population / 10000000
    group country by percentile into countryGroup
    where countryGroup.Key >= 20
    orderby countryGroup.Key
    select countryGroup;

// grouping is an IGrouping<int, Country>
foreach (var grouping in percentileQuery)
{
    Console.WriteLine(grouping.Key);
    foreach (var country in grouping)
        Console.WriteLine(country.Name + ":" + country.Population);
}

有关更多信息，请参见 into（C# 参考）。

筛选、排序和联接

在 from 开始子句以及 select 或 group 结束子句之间，所有其他子句（where、join、orderby、from、let）都是可选的。任何可选子句都可以在查询正文中使用零次或多次。

where 子句

使用 where 子句可以根据一个或多个谓词表达式筛选掉源数据中的某些元素。以下示例中的 where 子句含有两个谓词。

C# 复制代码
IEnumerable<City> queryCityPop =
    from city in cities
    where city.Population < 200000 && city.Population > 100000
    select city;

有关更多信息，请参见 where 子句（C# 参考）。

orderby 子句

使用 orderby 子句可以按升序或降序对结果进行排序。您还可以指定次要排序顺序。下面的示例使用 Area 属性对 country 对象执行主要排序，然后使用 Population 属性执行次要排序。

C# 复制代码
IEnumerable<Country> querySortedCountries =
    from country in countries
    orderby country.Area > 500000, country.Population descending
    select country;

ascending 关键字是可选的；如果未指定顺序，则它是默认排序顺序。有关更多信息，请参见 orderby 子句（C# 参考）。

join 子句

使用 join 子句可以根据每个元素中指定键之间的相等比较，对一个数据源中的元素与另外一个数据源中的元素进行关联和/或组合。在 LINQ 中，联接操作是针对其元素具有不同类型的对象序列执行的。在联接两个序列之后，必须使用 select 或 group 语句指定要存储到输出序列中的元素。还可以使用匿名类型将每组关联元素中的属性组合为输出序列的新类型。下面的示例对其 Category 属性与 categories 字符串数组中的某个类别相匹配的 prod 对象进行关联。其 Category 不与 categories 中的任何字符串匹配的产品会被筛选掉。select 语句投影了一个新类型，其属性取自 cat 和 prod。

C# 复制代码
var categoryQuery =
    from cat in categories
    join prod in products on cat equals prod.Category
    select new { Category = cat, Name = prod.Name };

通过使用 into 关键字将 join 操作的结果存储到临时变量中，还可以执行分组联接。有关更多信息，请参见 join 子句（C# 参考）。

let 子句

使用 let 子句可以将表达式（如方法调用）的结果存储到新的范围变量中。在下面的示例中，范围变量 s 存储了 Split 返回的字符串数组的第一个元素。

C# 复制代码
string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" };
IEnumerable<string> queryFirstNames =
    from name in names
    let firstName = name.Split(new char[] { ' ' })[0]
    select firstName;

foreach (string s in queryFirstNames)
    Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar

有关更多信息，请参见 let 子句（C# 参考）。

查询表达式中的子查询

查询子句本身可能包含一个查询表达式，该查询表达式有时称为“子查询”。每个子查询都以它自己的 from 子句开头，该子句不一定指向第一个 from 子句中的同一数据源。例如，下面的查询演示了一个在 select 语句中使用的查询表达式，用来检索分组操作的结果。

C# 复制代码
var queryGroupMax =
    from student in students
    group student by student.GradeLevel into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore =
            (from student2 in studentGroup
             select student2.Scores.Average())
             .Max()
    };

有关更多信息，请参见如何：对分组操作执行子查询（C# 编程指南）。

请参见

概念

C# 编程指南
LINQ 查询表达式（C# 编程指南）

参考

LINQ 常规编程指南

其他资源

语言集成查询 (LINQ)
查询关键字（C# 参考）


如何：在 C# 中编写 LINQ 查询

[image]

C# 编程指南
如何：在 C# 中编写 LINQ 查询
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本主题演示在 C# 中编写 LINQ 查询的三种方式：

  1. 使用查询语法。

  2. 使用方法语法。

  3. 组合使用查询语法和方法语法。

下面的示例使用前面列出的每种方式演示一些简单的 LINQ 查询。一般的规则是尽可能使用 (1)，而在必要时使用 (2) 和 (3)。

说明：

这些查询作用于简单的内存中集合；但是，基本语法与 LINQ to SQL 和 LINQ to XML 中使用的语法相同。

示例

查询语法

编写大多数查询的推荐方式是使用查询语法来创建查询表达式。下面的示例演示了三个查询表达式。第一个查询表达式演示如何通过用 where 子句应用条件来筛选或限制结果，它返回源序列中值大于 7 或小于 3 的所有元素。第二个表达式演示如何对返回的结果进行排序。第三个表达式演示如何按照键对结果进行分组，此查询可根据单词的第一个字母返回两个组。

C# 复制代码
// Query #1.
List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// The query variable can also be implicitly typed by using var
IEnumerable<int> filteringQuery =
    from num in numbers
    where num < 3 || num > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num < 3 || num > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" };
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

请注意，这些查询的类型是 IEnumerable<(Of <(T>)>)。所有这些查询都可以使用 var 编写，如下面的示例所示：

var query = from num in numbers...

在上述每个示例中，直到您在 foreach 语句中循环访问查询变量时，查询才会实际执行。有关更多信息，请参见 LINQ 查询介绍。

方法语法

某些查询操作必须表示为方法调用。最常见的此类方法是那些返回单一数值的方法，如 Sum、Max、Min、Average 等。这些方法在任何查询中都必须总是最后调用，因为它们仅表示单个值，不能充当其他查询操作的数据源。下面的示例演示查询表达式中的方法调用：

C# 复制代码
List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };
// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

如果该方法具有参数，则这些参数以 lambda 表达式的形式提供，如下面的示例所示：

C# 复制代码
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

在上述查询中，只有查询 4 立即执行。这是因为它返回单个值，而不是一个泛型 IEnumerable<(Of <(T>)>) 集合。方法本身必须使用 foreach 才能计算它的值。

上述每个查询都可以通过结合使用隐式类型化与 var 进行编写，如下面的示例所示：

C# 复制代码
// var is used for convenience in these queries
var average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

混合的查询和方法语法

此示例演示如何对查询子句的结果使用方法语法。只需将查询表达式括在括号内，然后应用点运算符并调用此方法。在下面的示例中，查询 7 返回其值在 3 和 7 之间的数字个数。但是，通常更好的做法是使用另一个变量来存储方法调用的结果。这样就不太容易将查询本身与查询结果相混淆。

C# 复制代码
// Query #7.

// Using a query expression with method syntax
int numCount1 =
    (from num in numbers1
     where num < 3 || num > 7
     select num).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num < 3 || num > 7
    select num;

int numCount2 = numbersQuery.Count();

由于查询 7 返回单个值而不是一个集合，因此该查询立即执行。

上述查询可以通过结合使用隐式类型化与 var 进行编写，如下所示：

复制代码
var numCount = (from num in numbers...

它可以按如下方式使用方法语法进行编写：

复制代码
var numCount = numbers.Where(n => n < 3 || n > 7).Count();

它可以按如下方式使用显式类型化进行编写：

复制代码
int numCount = numbers.Where(n => n < 3 || n > 7).Count();

请参见

任务

演练：在 C# 中编写查询 (LINQ)

概念

LINQ 查询表达式（C# 编程指南）

参考

where 子句（C# 参考）

其他资源

语言集成查询 (LINQ)


如何：查询对象集合（C# 编程指南）

[image]

C# 编程指南
如何：查询对象集合（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例演示如何对一个 Student 对象列表执行简单查询。每个 Student 对象都包含一些有关该学生的基本信息，以及一个表示该学生的四次考试得分的列表。

此应用程序充当此部分中其他很多示例的框架，这些示例都使用相同的 students 数据源。

示例

下面的查询返回那些在其第一次考试中得分为 85 分或更高的学生。

C# 复制代码
public class StudentClass
{
    #region data
    protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
    protected class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int ID { get; set; }
        public GradeLevel Year;
        public List<int> ExamScores;
    }

    protected static List<Student> students = new List<Student>
    {
        new Student {FirstName = "Terry", LastName = "Adams", ID = 120, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 99, 82, 81, 79}},
        new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, Year = GradeLevel.ThirdYear,ExamScores = new List<int>{ 99, 86, 90, 94}},
        new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 93, 92, 80, 87}},
        new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, Year = GradeLevel.FourthYear,ExamScores = new List<int>{ 97, 89, 85, 82}},
        new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 35, 72, 91, 70}},
        new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 92, 90, 83, 78}},
        new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 88, 94, 65, 91}},
        new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 75, 84, 91, 39}},
        new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 97, 92, 81, 60}},
        new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 68, 79, 88, 92}},
        new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 94, 92, 91, 91}},
        new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 96, 85, 91, 60}}
    };
    #endregion

    //Helper method
    protected static int GetPercentile(Student s)
    {
        double avg = s.ExamScores.Average();
        return avg > 0 ? (int)avg / 10 : 0;
    }

    public void QueryHighScores(int exam, int score)
    {
        var highScores = from student in students
                         where student.ExamScores[exam] > score
                         select new {Name = student.FirstName, Score = student.ExamScores[exam]};

        foreach (var item in highScores)
        {
            Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
        }
    }
}

public class Program
{
    public static void Main()
    {
        StudentClass sc = new StudentClass();
        sc.QueryHighScores(1, 90);

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

我们特意将此查询编写得非常简单，以使您能够进行试验。例如，您可以在 where 子句中尝试使用更多的谓词，也可以使用 orderby 子句对结果进行排序。

编译代码

  • 创建面向 .NET Framework 3.5 版的 Visual Studio 项目。默认情况下，该项目具有一个对 System.Core.dll 的引用以及一条针对 System.Linq 命名空间的 using 指令。

  • 将代码复制到项目中。

  • 按 F5 编译并运行程序。

  • 按任意键退出控制台窗口。

请参见

概念

LINQ 查询表达式（C# 编程指南）


如何：从方法中返回查询（C# 编程指南）

[image]

C# 编程指南
如何：从方法中返回查询（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例演示如何以返回值和输出参数的形式从方法中返回查询。

任何查询的类型都必须为 IEnumerable 或 IEnumerable<(Of <(T>)>)，或一种派生类型（如 IQueryable<(Of <(T>)>)）。因此，返回查询的方法的任何返回值或输出参数也必须具有该类型。如果某个方法将查询具体化为具体的 List<(Of <(T>)>) 或 Array 类型，则认为该方法在返回查询结果（而不是查询本身）。仍然能够编写或修改从方法中返回的查询变量。

有关如何执行传递到方法的查询的示例，请参见对象转储程序示例。

示例

在下面的示例中，第一个方法以返回值的形式返回查询，第二个方法以输出参数的形式返回查询。请注意，在这两种情况下，返回的都是查询，而不是查询结果。

C# 复制代码
class MQ
{
    IEnumerable<string> QueryMethod1(ref int[] ints)
    {
        var intsToStrings = from i in ints
                            where i > 4
                            select i.ToString();
        return intsToStrings;
    }

    static void Main()
    {
        MQ app = new MQ();

        int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        var myQuery = app.QueryMethod1(ref nums);

        //execute myQuery
        foreach (string s in myQuery)
        {
            Console.WriteLine(s);
        }

        //modify myQuery
        myQuery = (from str in myQuery
                   orderby str descending
                   select str).
                  Take(3);

        // Executing myQuery after more
        // composition
        Console.WriteLine("After modification:");
        foreach (string s in myQuery)
        {
            Console.WriteLine(s);
        }

        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

编译代码

  • 创建面向 .NET Framework 3.5 版的 Visual Studio 项目。默认情况下，该项目具有一个对 System.Core.dll 的引用以及一条针对 System.Linq 命名空间的 using 指令。

  • 将代码复制到项目中。

  • 按 F5 编译并运行程序。

  • 按任意键退出控制台窗口。

请参见

概念

LINQ 查询表达式（C# 编程指南）


如何：在内存中存储查询结果（C# 编程指南）

[image]

C# 编程指南
如何：在内存中存储查询结果（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

查询基本上是一组有关如何检索和组织数据的指令。若要执行查询，需要调用它的 GetEnumerator 方法。当您使用 foreach 循环来循环访问元素时，将执行此调用。若要在执行 foreach 循环之前或之后的任何时间存储结果，只需对查询变量调用下列方法之一：

  • ToList<(Of <(TSource>)>)

  • ToArray<(Of <(TSource>)>)

  • ToDictionary

  • ToLookup

建议在存储查询结果时，将返回的集合对象分配给一个新变量，如下面的示例所示：

示例

C# 复制代码
class StoreQueryResults
{
    static List<int> numbers = new List<int>() { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
    static void Main()
    {

        IEnumerable<int> queryFactorsOfFour =
            from num in numbers
            where num % 4 == 0
            select num;

        // Store the results in a new variable
        // without executing a foreach loop.
        List<int> factorsofFourList = queryFactorsOfFour.ToList();

        // Iterate the list just to prove it holds data.
        foreach (int n in factorsofFourList)
        {
            Console.WriteLine(n);
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key");
        Console.ReadKey();
    }
}

编译代码

  • 创建面向 .NET Framework 3.5 版的 Visual Studio 项目。默认情况下，该项目具有一个对 System.Core.dll 的引用以及一条针对 System.Linq 命名空间的 using 指令。

  • 将代码复制到项目中。

  • 按 F5 编译并运行程序。

  • 按任意键退出控制台窗口。

请参见

概念

LINQ 查询表达式（C# 编程指南）


如何：以各种方式对结果进行分组（C# 编程指南）

[image]

C# 编程指南
如何：以各种方式对结果进行分组（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

分组是 LINQ 最强大的功能之一。下面的示例演示如何以各种方式对数据进行分组：

  • 按照单个属性。

  • 按照字符串属性的首字母。

  • 按照计算出的数值范围。

  • 按照布尔谓词或其他表达式。

  • 按照复合键。

此外，最后两个查询将它们的结果投影到一个新的匿名类型中，该类型仅包含学生的名字和姓氏。有关更多信息，请参见 group 子句（C# 参考）。

示例

本主题中的所有示例都使用下列帮助器类和数据源。

C# 复制代码
public class StudentClass
{
    #region data
    protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear };
    protected class Student
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int ID { get; set; }
        public GradeLevel Year;
        public List<int> ExamScores;
    }

    protected static List<Student> students = new List<Student>
    {
        new Student {FirstName = "Terry", LastName = "Adams", ID = 120, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 99, 82, 81, 79}},
        new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, Year = GradeLevel.ThirdYear,ExamScores = new List<int>{ 99, 86, 90, 94}},
        new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 93, 92, 80, 87}},
        new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, Year = GradeLevel.FourthYear,ExamScores = new List<int>{ 97, 89, 85, 82}},
        new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 35, 72, 91, 70}},
        new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 92, 90, 83, 78}},
        new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 88, 94, 65, 91}},
        new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 75, 84, 91, 39}},
        new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 97, 92, 81, 60}},
        new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 68, 79, 88, 92}},
        new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 94, 92, 91, 91}},
        new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 96, 85, 91, 60}}
    };
    #endregion

    //Helper method
    protected static int GetPercentile(Student s)
    {
        double avg = s.ExamScores.Average();
        return avg > 0 ? (int)avg / 10 : 0;
    }

    public void QueryHighScores(int exam, int score)
    {
        var highScores = from student in students
                         where student.ExamScores[exam] > score
                         select new {Name = student.FirstName, Score = student.ExamScores[exam]};

        foreach (var item in highScores)
        {
            Console.WriteLine("{0,-15}{1}", item.Name, item.Score);
        }
    }
}

public class Program
{
    public static void Main()
    {
        StudentClass sc = new StudentClass();
        sc.QueryHighScores(1, 90);

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}

下面的示例演示如何通过使用元素的单个属性作为组键对源元素进行分组。在这种情况下，该键为 string。还可以使用子字符串作为键。分组操作将对该类型使用默认的相等比较器。

C# 复制代码
private static void GroupBySingleProperty()
{
    Console.WriteLine("Group by a single property in an object");

    // queryLastNames is an IEnumerable<IGrouping<string, DataClass.Student>>
    // var is easier to type.
    var queryLastNames =
        from student in students
        group student by student.LastName into newGroup
        orderby newGroup.Key
        select newGroup;

    foreach (var nameGroup in queryLastNames)
    {
        Console.WriteLine("Key: {0}", nameGroup.Key);
        foreach (var student in nameGroup)
        {
            Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
        }
    }
}
/* Output:
  Group by a single property in an object
  Key: Feng
          Feng, Hanying
  Key: Garcia
          Garcia, Hugo
          Garcia, Cesar
          Garcia, Debra
  Key: Mortensen
          Mortensen, Sven
  Key: O'Donnell
          O'Donnell, Claire
  Key: Omelchenko
          Omelchenko, Svetlana
  Key: Tucker
          Tucker, Michael
          Tucker, Lance
 */

下面的示例演示如何通过使用除对象属性以外的某个项作为组键对源元素进行分组。

C# 复制代码
private static void GroupBySubstring()
{
    Console.WriteLine("\r\nGroup by something other than a property of the object:");

    var queryFirstLetters =
        from student in students
        group student by student.LastName[0];

    foreach (var studentGroup in queryFirstLetters)
    {
        Console.WriteLine("Key: {0}", studentGroup.Key);
        // Nested foreach is required to access group items
        foreach (var student in studentGroup)
        {
            Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName);
        }
    }
}
/* Output:
        Group by first character:
        Key: O
                Omelchenko, Svetlana
                O'Donnell, Claire
        Key: G
                Garcia, Hugo
                Garcia, Cesar
                Garcia, Debra
        Key: M
                Mortensen, Sven
        Key: T
                Tucker, Michael
                Tucker, Lance
        Key: F
                Feng, Hanying
     */

下面的示例演示如何通过使用某个数值范围作为组键对源元素进行分组。然后，查询将结果投影到一个匿名类型中，该类型仅包含学生的名字和姓氏以及该学生所属的百分点范围。使用匿名类型的原因是没有必要使用完整的 Student 对象来显示结果。GetPercentile 是一个帮助器函数，它根据学生的平均得分计算百分点：

C# 复制代码
static int GetPercentile(Student s)
{
   double avg = s.Scores.Average();
   return avg > 0 ? (int)avg / 10 : 0;
}
C# 复制代码
private static void GroupByRange()
{
    Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:");

    var queryNumericRange =
        from student in students
        let percentile = GetPercentile(student)
        group new { student.FirstName, student.LastName } by percentile into percentGroup
        orderby percentGroup.Key
        select percentGroup;

    // Nested foreach required to iterate over groups and group items.
    foreach (var studentGroup in queryNumericRange)
    {
        Console.WriteLine("Key: {0}", (studentGroup.Key * 10));
        foreach (var item in studentGroup)
        {
            Console.WriteLine("\t{0}, {1}", item.LastName, item.FirstName);
        }
    }
}
/* Output:
     Group by numeric range and project into a new anonymous type:
     Key: 60
             Garcia, Debra
     Key: 70
             Omelchenko, Svetlana
             O'Donnell, Claire
     Key: 80
             Garcia, Hugo
             Mortensen, Sven
             Garcia, Cesar
             Feng, Hanying
             Tucker, Lance
     Key: 90
             Tucker, Michael
     */

下面的示例演示如何通过使用布尔比较表达式对源元素进行分组。与上述示例一样，结果被投影到一个匿名类型中，因为不需要完整的源元素。请注意，在执行查询时，该匿名类型中的属性将变成 Key 成员上的属性，并且可以通过名称进行访问。

C# 复制代码
private static void GroupByBoolean()
{
    Console.WriteLine("\r\nGroup by a boolean into two groups with string keys");
    Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:");
    var queryGroupByAverages = from student in students
                               group new { student.FirstName, student.LastName }
                                    by student.ExamScores.Average() > 75 into studentGroup
                               select studentGroup;

    foreach (var studentGroup in queryGroupByAverages)
    {
        Console.WriteLine("Key: {0}", studentGroup.Key);
        foreach (var student in studentGroup)
            Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName);
    }
}
/* Output:
         Group by a boolean into two groups with string keys
         "True" and "False" and project into a new anonymous type:
         Key: True
                 Svetlana Omelchenko
                 Hugo Garcia
                 Sven Mortensen
                 Michael Tucker
                 Cesar Garcia
                 Hanying Feng
                 Lance Tucker
         Key: False
                 Claire O'Donnell
                 Debra Garcia
*/

下面的示例演示如何使用匿名类型来封装包含多个值的键。在这种情况下，第二个键值是一个布尔值，它指定该学生在第一次考试中的得分是否超过了 85 分。可以按照该键中的任何属性对多组值进行排序。

C# 复制代码
private static void GroupByCompositeKey()
{

    var queryHighScoreGroups =
        from student in students
        group student by new { FirstLetter = student.LastName[0], Score = student.ExamScores[0] > 85 } into studentGroup
        orderby studentGroup.Key.FirstLetter
        select studentGroup;

    Console.WriteLine("\r\nGroup and order by a compound key:");
    foreach (var scoreGroup in queryHighScoreGroups)
    {
        string s = scoreGroup.Key.Score == true ? "more than" : "less than";
        Console.WriteLine("Name starts with {0} who scored {1} 85", scoreGroup.Key.FirstLetter, s);
        foreach (var item in scoreGroup)
        {
            Console.WriteLine("\t{0} {1}", item.FirstName, item.LastName);
        }
    }
}
/* Output:
          Group and order by a compound key:
          Name starts with F who scored more than 85
                  Hanying Feng
          Name starts with G who scored more than 85
                  Hugo Garcia
                  Cesar Garcia
          Name starts with G who scored less than 85
                  Debra Garcia
          Name starts with M who scored more than 85
                  Sven Mortensen
          Name starts with O who scored more than 85
                  Svetlana Omelchenko
          Name starts with O who scored less than 85
                  Claire O'Donnell
          Name starts with T who scored more than 85
                  Michael Tucker
          Name starts with T who scored less than 85
                  Lance Tucker
       */

编译代码

此示例包含对如何：查询对象集合（C# 编程指南）内示例应用程序中定义的对象的引用。若要编译和运行此方法，请将它粘贴到该应用程序的 StudentClass 类中，并且在 Main 方法中添加对它的调用。

在改写此方法以适合您自己的应用程序时，请记住 LINQ 需要 .NET Framework 3.5 版，并且项目必须包含一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。LINQ to SQL、LINQ to XML 和 LINQ to DataSet 类型需要其他 using 指令和引用。有关更多信息，请参见 如何：创建 LINQ 项目。

请参见

任务

如何：对分组操作执行子查询（C# 编程指南）
如何：对组进行分组（C# 编程指南）

概念

LINQ 查询表达式（C# 编程指南）
数据分组

参考

group 子句（C# 参考）
匿名类型（C# 编程指南）
GroupBy
IGrouping<(Of <(TKey, TElement>)>)


如何：对组进行分组（C# 编程指南）

[image]

C# 编程指南
如何：对组进行分组（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的示例演示如何在 LINQ 查询表达式中创建嵌套组。

示例

C# 复制代码
private static void QueryNestedGroups()
{
    var queryNestedGroups =
        from student in students
        group student by student.Year into newGroup1
        from newGroup2 in
            (from student in newGroup1
             group student by student.LastName)
        group newGroup2 by newGroup1.Key;

    // Three nested foreach loops are required to iterate
    // over all elements of a grouped group. Hover the mouse
    // cursor over the iteration variables to see their actual type.
    foreach (var outerGroup in queryNestedGroups)
    {
        Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key);
        foreach (var innerGroup in outerGroup)
        {
            Console.WriteLine("\tNames that begin with: {0}", innerGroup.Key);
            foreach (var innerGroupElement in innerGroup)
            {
                Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName);
            }
        }
    }
}

请注意，需要使用三个嵌套的 foreach 循环来循环访问嵌套组的内部元素。

编译代码

此示例包含对如何：查询对象集合（C# 编程指南）内示例应用程序中定义的对象的引用。若要编译和运行此方法，请将它粘贴到该应用程序的 StudentClass 类中，并且在 Main 方法中添加对它的调用。

在改写此方法以适合您自己的应用程序时，请记住 LINQ 需要 .NET Framework 3.5 版，并且项目必须包含一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。LINQ to SQL、LINQ to XML 和 LINQ to DataSet 类型需要其他 using 指令和引用。有关更多信息，请参见 如何：创建 LINQ 项目。

请参见

概念

LINQ 查询表达式（C# 编程指南）


如何：对分组操作执行子查询（C# 编程指南）

[image]

C# 编程指南
如何：对分组操作执行子查询（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本主题演示执行以下任务的两种不同方式：创建一个查询，以便将源数据排序到不同的组中，然后分别对每个组执行子查询。每个示例中的基本技术都是使用一个名为 newGroup 的“延续”对源元素进行分组，然后生成一个针对 newGroup 的新的子查询。此子查询是针对外部查询所创建的每个新组运行的。请注意，在此特定示例中，最终的输出不是组，而是匿名类型的平面序列。

有关如何分组的更多信息，请参见group 子句（C# 参考）。

有关延续的更多信息，请参见 into（C# 参考）。下面的示例使用内存中数据结构作为数据源，但相同的原理适用于任何种类的 LINQ 数据源。

示例

C# 复制代码
private static void QueryMax()
{
    var queryGroupMax =
        from student in students
        group student by student.Year into studentGroup
        select new
        {
            Level = studentGroup.Key,
            HighestScore =
            (from student2 in studentGroup
             select student2.ExamScores.Average()).Max()
        };

    int count = queryGroupMax.Count();
    Console.WriteLine("Number of groups = {0}", count);

    foreach (var item in queryGroupMax)
    {
        Console.WriteLine("  {0} Highest Score={1}", item.Level, item.HighestScore);
    }
}

编译代码

此示例包含对如何：查询对象集合（C# 编程指南）内示例应用程序中定义的对象的引用。若要编译和运行此方法，请将它粘贴到该应用程序的 StudentClass 类中，并且在 Main 方法中添加对它的调用。

在改写此方法以适合您自己的应用程序时，请记住 LINQ 需要 .NET Framework 3.5 版，并且项目必须包含一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。LINQ to SQL、LINQ to XML 和 LINQ to DataSet 类型需要其他 using 指令和引用。有关更多信息，请参见 如何：创建 LINQ 项目。

请参见

概念

LINQ 查询表达式（C# 编程指南）


如何：按连续键对结果进行分组（C# 编程指南）

[image]

C# 编程指南
如何：按连续键对结果进行分组（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的示例演示如何将元素归到不同的块区中，块区表示连续键的子序列。例如，假设您有下面的键值对序列：

键 | 值
A | We
A | think
A | that
B | Linq
C | is
A | really
B | cool
B | !

将按照下面的顺序创建以下各组：

  1. We, think, that

  2. Linq

  3. is

  4. really

  5. cool, !

此解决方案作为扩展方法实现，扩展方法是线程安全的且以流的方式返回其结果。也就是说，它在移动通过源序列的过程中生成自己的各个组。与 group 或 orderby 运算符不同，它可以在整个序列读取完毕之前就开始将组返回给调用方。

线程安全性是通过在迭代源序列的过程中创建每个组或块区的副本来实现的，如源代码注释中所述。如果源序列包含一个由连续项组成的很大的序列，则公共语言运行时可能会引发 OutOfMemoryException。

示例

下面的示例显示了此扩展方法及使用它的客户端代码。

C# 复制代码
using System;
using System.Collections.Generic;
using System.Linq;

namespace ChunkIt
{
    // Static class to contain the extension methods.
    public static class MyExtensions
    {
        public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            return source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);
        }

        public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            // Flag to signal end of source sequence.
            const bool noMoreSourceElements = true;

            // Auto-generated iterator for the source array.
            var enumerator = source.GetEnumerator();

            // Move to the first element in the source sequence.
            if (!enumerator.MoveNext()) yield break;

            // Iterate through source sequence and create a copy of each Chunk.
            // On each pass, the iterator advances to the first element of the next "Chunk"
            // in the source sequence. This loop corresponds to the outer foreach loop that
            // executes the query.
            Chunk<TKey, TSource> current = null;
            while (true)
            {
                // Get the key for the current Chunk. The source iterator will churn through
                // the source sequence until it finds an element with a key that doesn't match.
                var key = keySelector(enumerator.Current);

                // Make a new Chunk (group) object that initially has one GroupItem, which is a copy of the current source element.
                current = new Chunk<TKey, TSource>(key, enumerator, value => comparer.Equals(key, keySelector(value)));

                // Return the Chunk. A Chunk is an IGrouping<TKey,TSource>, which is the return value of the ChunkBy method.
                // At this point the Chunk only has the first element in its source sequence. The remaining elements will be
                // returned only when the client code foreach's over this chunk. See Chunk.GetEnumerator for more info.
                yield return current;

                // Check to see whether (a) the chunk has made a copy of all its source elements or
                // (b) the iterator has reached the end of the source sequence. If the caller uses an inner
                // foreach loop to iterate the chunk items, and that loop ran to completion,
                // then the Chunk.GetEnumerator method will already have made
                // copies of all chunk items before we get here. If the Chunk.GetEnumerator loop did not
                // enumerate all elements in the chunk, we need to do it here to avoid corrupting the iterator
                // for clients that may be calling us on a separate thread.
                if (current.CopyAllChunkElements() == noMoreSourceElements)
                {
                    yield break;
                }
            }
        }

        // A Chunk is a contiguous group of one or more source elements that have the same key. A Chunk
        // has a key and a list of ChunkItem objects, which are copies of the elements in the source sequence.
        class Chunk<TKey, TSource> : IGrouping<TKey, TSource>
        {
            // INVARIANT: DoneCopyingChunk == true ||
            //   (predicate != null && predicate(enumerator.Current) && current.Value == enumerator.Current)

            // A Chunk has a linked list of ChunkItems, which represent the elements in the current chunk. Each ChunkItem
            // has a reference to the next ChunkItem in the list.
            class ChunkItem
            {
                public ChunkItem(TSource value)
                {
                    Value = value;
                }
                public readonly TSource Value;
                public ChunkItem Next = null;
            }
            // The value that is used to determine matching elements
            private readonly TKey key;

            // Stores a reference to the enumerator for the source sequence
            private IEnumerator<TSource> enumerator;

            // A reference to the predicate that is used to compare keys.
            private Func<TSource, bool> predicate;

            // Stores the contents of the first source element that
            // belongs with this chunk.
            private readonly ChunkItem head;

            // End of the list. It is repositioned each time a new
            // ChunkItem is added.
            private ChunkItem tail;

            // Flag to indicate the source iterator has reached the end of the source sequence.
            internal bool isLastSourceElement = false;

            // Private object for thread syncronization
            private object m_Lock;

            // REQUIRES: enumerator != null && predicate != null
            public Chunk(TKey key, IEnumerator<TSource> enumerator, Func<TSource, bool> predicate)
            {
                this.key = key;
                this.enumerator = enumerator;
                this.predicate = predicate;

                // A Chunk always contains at least one element.
                head = new ChunkItem(enumerator.Current);

                // The end and beginning are the same until the list contains > 1 elements.
                tail = head;

                m_Lock = new object();
            }

            // Indicates that all chunk elements have been copied to the list of ChunkItems,
            // and the source enumerator is either at the end, or else on an element with a new key.
            // the tail of the linked list is set to null in the CopyNextChunkElement method if the
            // key of the next element does not match the current chunk's key, or there are no more elements in the source.
            private bool DoneCopyingChunk { get { return tail == null; } }

            // Adds one ChunkItem to the current group
            // REQUIRES: !DoneCopyingChunk && lock(this)
            private void CopyNextChunkElement()
            {
                // Try to advance the iterator on the source sequence.
                // If MoveNext returns false we are at the end, and isLastSourceElement is set to true
                isLastSourceElement = !enumerator.MoveNext();

                // If we are (a) at the end of the source, or (b) at the end of the current chunk
                // then null out the enumerator and predicate for reuse with the next chunk.
                if (isLastSourceElement || !predicate(enumerator.Current))
                {
                    enumerator = null;
                    predicate = null;
                }
                else
                {
                    tail.Next = new ChunkItem(enumerator.Current);
                }

                // tail will be null if we are at the end of the chunk elements
                // This check is made in DoneCopyingChunk.
                tail = tail.Next;
            }

            // Called after the end of the last chunk was reached. It first checks whether
            // there are more elements in the source sequence. If there are, it
            // Returns true if enumerator for this chunk was exhausted.
            internal bool CopyAllChunkElements()
            {
                while (true)
                {
                    lock (m_Lock)
                    {
                        if (DoneCopyingChunk)
                        {
                            // If isLastSourceElement is false,
                            // it signals to the outer iterator
                            // to continue iterating.
                            return isLastSourceElement;
                        }
                        else
                        {
                            CopyNextChunkElement();
                        }
                    }
                }
            }

            public TKey Key { get { return key; } }

            // Invoked by the inner foreach loop. This method stays just one step ahead
            // of the client requests. It adds the next element of the chunk only after
            // the clients requests the last element in the list so far.
            public IEnumerator<TSource> GetEnumerator()
            {
                //Specify the initial element to enumerate.
                ChunkItem current = head;

                // There should always be at least one ChunkItem in a Chunk.
                while (current != null)
                {
                    // Yield the current item in the list.
                    yield return current.Value;

                    // Copy the next item from the source sequence,
                    // if we are at the end of our local list.
                    lock (m_Lock)
                    {
                        if (current == tail)
                        {
                            CopyNextChunkElement();
                        }
                    }

                    // Move to the next ChunkItem in the list.
                    current = current.Next;
                }
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
    }

    // A simple named type is used for easier viewing in the debugger. Anonymous types
    // work just as well with the ChunkBy operator.
    public class KeyValPair
    {
        public string Key { get; set; }
        public string Value { get; set; }
    }

    class Program
    {
        // The source sequence.
        public static IEnumerable<KeyValPair> list;

        // Query variable declared as class member to be available
        // on different threads.
        static IEnumerable<IGrouping<string, KeyValPair>> query;

        static void Main(string[] args)
        {
            // Initialize the source sequence with an array initializer.
            list = new[]
        {
            new KeyValPair{ Key = "A", Value = "We" },
            new KeyValPair{ Key = "A", Value = "Think" },
            new KeyValPair{ Key = "A", Value = "That" },
            new KeyValPair{ Key = "B", Value = "Linq" },
            new KeyValPair{ Key = "C", Value = "Is" },
            new KeyValPair{ Key = "A", Value = "Really" },
            new KeyValPair{ Key = "B", Value = "Cool" },
            new KeyValPair{ Key = "B", Value = "!" }
        };

            // Create the query by using our user-defined query operator.
            query = list.ChunkBy(p => p.Key);

            // ChunkBy returns IGrouping objects, therefore a nested
            // foreach loop is required to access the elements in each "chunk".
            foreach (var item in query)
            {
                Console.WriteLine("Group key = {0}", item.Key);
                foreach (var inner in item)
                {
                    Console.WriteLine("\t{0}", inner.Value);
                }
            }

            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }

     }
}

若要在您的项目中使用此扩展方法，请将 MyExtensions 静态类复制到一个新的或现有源代码文件中，并在必要时，为该类所在的命名空间添加一条 using 指令。

请参见

概念

LINQ 查询表达式（C# 编程指南）
标准查询运算符按执行方式的分类

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


如何：在运行时动态指定谓词筛选器（C# 编程指南）

[image]

C# 编程指南
如何：在运行时动态指定谓词筛选器（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在某些情况下，直到运行时才能知道必须将多少个谓词应用于 where 子句中的源元素。动态指定多个谓词筛选器的一种方法是使用 Contains 方法，如下面的示例所示：

示例

C# 复制代码
// To run this sample, first specify some integer values for the command line.
// The numbers 111 through 122 are all valid student IDs.
// In Visual Studio or C# Express, click on Project > Properties > Debug.
// Call the method: QueryByID(args);
static void QueryByID(string[] ids)
{
    var queryNames =
        from student in students
        let i = student.ID.ToString()
        where ids.Contains(i)
        select new { student.LastName, student.ID };

    foreach (var name in queryNames)
    {
        Console.WriteLine("{0}: {1}", name.LastName, name.ID);
    }
}
/*
 Output (depends on args):
   111 114 118

   Garcia: 114
   Garcia: 118
   Omelchenko: 111
*/

如果必须在预先确定的备选查询中进行选择，可以使用 switch 语句。在下面的示例中，studentQuery 的 where 子句随着在命令行指定的年级或年份的不同而不同。

C# 复制代码
// To run this sample, first specify an integer value of 1 to 4 for the command line.
// This number will be converted to a GradeLevel value that specifies which set of students
// to query. In Visual Studio or C# Express, click on Project > Properties > Debug to specify
// command line arguments.
// Call the method: QueryByYear(args[0]);

static void QueryByYear(string level)
{
    GradeLevel year = (GradeLevel)Convert.ToInt32(level);
    IEnumerable<Student> studentQuery = null;
    switch (year)
    {
        case GradeLevel.FirstYear:
            studentQuery = from student in students
                           where student.Year == GradeLevel.FirstYear
                           select student;
            break;
        case GradeLevel.SecondYear:
            studentQuery = from student in students
                           where student.Year == GradeLevel.FirstYear
                           select student;
            break;
        case GradeLevel.ThirdYear:
            studentQuery = from student in students
                           where student.Year == GradeLevel.FirstYear
                           select student;
            break;
        case GradeLevel.FourthYear:
            studentQuery = from student in students
                           where student.Year == GradeLevel.FourthYear
                           select student;
            break;

        default:
            break;
    }
    Console.WriteLine("The following students are at level {0}", year.ToString());
    foreach (Student name in studentQuery)
    {
        Console.WriteLine("{0}: {1}", name.LastName, name.ID);
    }
}

编译代码

这些示例包含对在如何：查询对象集合（C# 编程指南）中的示例应用程序中定义的对象的引用。若要编译和运行示例，请将它们粘贴到该应用程序中的 StudentClass 类中，并在 Main 方法中添加对此方法的调用。

在改写此方法以适合您自己的应用程序时，请记住 LINQ 需要 .NET Framework 3.5 版，并且项目必须包含一个对 System.Core.dll 的引用和一条针对 System.Linq 的 using 指令。LINQ to SQL、LINQ to XML 和 LINQ to DataSet 类型需要其他 using 指令和引用。有关更多信息，请参见 如何：创建 LINQ 项目。

动态查询示例示例演示使用 LINQ 动态构造查询的另一种方法。

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

where 子句（C# 参考）


如何：执行内部联接（C# 编程指南）

[image]

C# 编程指南
如何：执行内部联接（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

按照关系数据库的说法，“内部联接”产生一个结果集，对于该结果集内第一个集合中的每个元素，只要在第二个集合中存在一个匹配元素，该元素就会出现一次。如果第一个集合中的某个元素没有匹配元素，则它不会出现在结果集内。Join 方法（在 C# 中由 join 子句调用，在 Visual Basic 中由 Join 子句调用）实现内部联接。

本主题演示如何执行内部联接的四种变体：

  • 简单的内部联接，它基于一个简单的键将来自两个数据源的元素相互关联。

  • 内部联接，它基于一个复合键将来自两个数据源的元素相互关联。使用复合键（即由多个值组成的键）可以基于多个属性将元素相互关联。

  • 多联接，在其中连续的联接操作被相互拼接在一起。

  • 通过使用分组联接实现的内部联接。

示例

简单键联接示例

下面的示例创建了两个集合，其中分别包含以下两个用户定义类型的对象：Person 和 Pet。查询使用 join 子句 (C#) 或 Join 子句 (Visual Basic) 将 Person 对象与其 Owner 为该 Person 的 Pet 对象进行匹配。C# 中的 select 子句或 Visual Basic 中的 Select 子句可定义生成的对象的外观。在此示例中，生成的对象是由主人的名字和宠物的名字组成的匿名类型。

C# 复制代码
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

/// <summary>
/// Simple inner join.
/// </summary>
public static void InnerJoinExample()
{
    Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
    Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
    Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
    Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
    Person rui = new Person { FirstName = "Rui", LastName = "Raposo" };

    Pet barley = new Pet { Name = "Barley", Owner = terry };
    Pet boots = new Pet { Name = "Boots", Owner = terry };
    Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
    Pet bluemoon = new Pet { Name = "Blue Moon", Owner = rui };
    Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

    // Create two lists.
    List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui };
    List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

    // Create a collection of person-pet pairs. Each element in the collection
    // is an anonymous type containing both the person's name and their pet's name.
    var query = from person in people
                join pet in pets on person equals pet.Owner
                select new { OwnerName = person.FirstName, PetName = pet.Name };

    foreach (var ownerAndPet in query)
    {
        Console.WriteLine("\"{0}\" is owned by {1}", ownerAndPet.PetName, ownerAndPet.OwnerName);
    }
}

// This code produces the following output:
//
// "Daisy" is owned by Magnus
// "Barley" is owned by Terry
// "Boots" is owned by Terry
// "Whiskers" is owned by Charlotte
// "Blue Moon" is owned by Rui

请注意，其 LastName 为“Huff”的 Person 对象未出现在结果集内，因为不存在 Pet.Owner 等于该 Person 的 Pet 对象。

复合键联接示例

与仅仅基于一个属性将元素相互关联不同，使用复合键可基于多个属性来比较元素。为此，需要为每个集合指定键选择器函数，以便返回一个由要比较的属性组成的匿名类型。如果给属性加上了标签，则这些属性必须在每个键的匿名类型中都有相同的标签，而且还必须以相同顺序出现。

下面的示例使用一个 Employee 对象列表和一个 Student 对象列表来确定哪些雇员同时还是学生。这两个类型都具有 String 类型的 FirstName 和 LastName 属性。能够从每个列表的元素创建联接键的函数可返回一个由每个元素的 FirstName 和 LastName 属性组成的匿名类型。联接操作比较这些复合键是否相等，并且从每个列表中返回名字和姓氏都匹配的对象对。

C# 复制代码
class Employee
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int StudentID { get; set; }
}

/// <summary>
/// Performs a join operation using a composite key.
/// </summary>
public static void CompositeKeyJoinExample()
{
    // Create a list of employees.
    List<Employee> employees = new List<Employee> {
        new Employee { FirstName = "Terry", LastName = "Adams", EmployeeID = 522459 },
         new Employee { FirstName = "Charlotte", LastName = "Weiss", EmployeeID = 204467 },
         new Employee { FirstName = "Magnus", LastName = "Hedland", EmployeeID = 866200 },
         new Employee { FirstName = "Vernette", LastName = "Price", EmployeeID = 437139 } };

    // Create a list of students.
    List<Student> students = new List<Student> {
        new Student { FirstName = "Vernette", LastName = "Price", StudentID = 9562 },
        new Student { FirstName = "Terry", LastName = "Earls", StudentID = 9870 },
        new Student { FirstName = "Terry", LastName = "Adams", StudentID = 9913 } };

    // Join the two data sources based on a composite key consisting of first and last name,
    // to determine which employees are also students.
    IEnumerable<string> query = from employee in employees
                                join student in students
                                on new { employee.FirstName, employee.LastName }
                                equals new { student.FirstName, student.LastName }
                                select employee.FirstName + " " + employee.LastName;

    Console.WriteLine("The following people are both employees and students:");
    foreach (string name in query)
        Console.WriteLine(name);
}

// This code produces the following output:
//
// The following people are both employees and students:
// Terry Adams
// Vernette Price

多联接示例

可以将任意数量的联接操作拼接在一起以执行多联接。每个 join 子句 (C#) 或 Join 子句 (Visual Basic) 都可以将指定的数据源与前一个联接的结果相互关联。

下面的示例创建了三个集合：一个 Person 对象列表、一个 Cat 对象列表以及一个 Dog 对象列表。

第一个 join 子句 (C#) 或 Join 子句 (Visual Basic) 基于与 Cat.Owner 匹配的Person 对象将主人与猫进行匹配，并返回包含 Person 对象和 Cat.Name 的匿名类型的序列。

第二个 join 子句 (C#) 或 Join 子句 (Visual Basic) 基于一个复合键将第一个联接返回的匿名类型与所提供的犬列表中的 Dog 对象相互关联，而该复合键由 Person 类型的 Owner 属性和动物名字的首字母组成。该子句返回一个匿名类型序列，这些类型包含每个匹配对中的 Cat.Name 和 Dog.Name 属性。由于这是一个内部联接，因此仅返回第一个数据源中那些在第二个数据源中具有匹配对象的对象。

C# 复制代码
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

class Cat : Pet
{ }

class Dog : Pet
{ }

public static void MultipleJoinExample()
{
    Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
    Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
    Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
    Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
    Person rui = new Person { FirstName = "Rui", LastName = "Raposo" };
    Person phyllis = new Person { FirstName = "Phyllis", LastName = "Harris" };

    Cat barley = new Cat { Name = "Barley", Owner = terry };
    Cat boots = new Cat { Name = "Boots", Owner = terry };
    Cat whiskers = new Cat { Name = "Whiskers", Owner = charlotte };
    Cat bluemoon = new Cat { Name = "Blue Moon", Owner = rui };
    Cat daisy = new Cat { Name = "Daisy", Owner = magnus };

    Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner = phyllis };
    Dog duke = new Dog { Name = "Duke", Owner = magnus };
    Dog denim = new Dog { Name = "Denim", Owner = terry };
    Dog wiley = new Dog { Name = "Wiley", Owner = charlotte };
    Dog snoopy = new Dog { Name = "Snoopy", Owner = rui };
    Dog snickers = new Dog { Name = "Snickers", Owner = arlene };

    // Create three lists.
    List<Person> people =
        new List<Person> { magnus, terry, charlotte, arlene, rui, phyllis };
    List<Cat> cats =
        new List<Cat> { barley, boots, whiskers, bluemoon, daisy };
    List<Dog> dogs =
        new List<Dog> { fourwheeldrive, duke, denim, wiley, snoopy, snickers };

    // The first join matches Person and Cat.Owner from the list of people and
    // cats, based on a common Person. The second join matches dogs whose names start
    // with the same letter as the cats that have the same owner.
    var query = from person in people
                join cat in cats on person equals cat.Owner
                join dog in dogs on
                new { Owner = person, Letter = cat.Name.Substring(0, 1) }
                equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) }
                select new { CatName = cat.Name, DogName = dog.Name };

    foreach (var obj in query)
    {
        Console.WriteLine(
            "The cat \"{0}\" shares a house, and the first letter of their name, with \"{1}\".",
            obj.CatName, obj.DogName);
    }
}

// This code produces the following output:
//
// The cat "Daisy" shares a house, and the first letter of their name, with "Duke".
// The cat "Whiskers" shares a house, and the first letter of their name, with "Wiley".

使用分组联接实现内部联接的示例

下面的示例演示如何使用分组联接来实现内部联接。

在 query1 中，Person 对象列表基于与 Pet.Owner 属性匹配的 Person 分组联接到 Pet 对象列表。分组联接创建了一个中间组集合，该集合中的每个组都由一个 Person 对象和匹配的 Pet 对象序列组成。

通过向查询中添加另一个 from 子句（在 Visual Basic 中为 From 子句），此序列的序列被组合（或展平）为一个较长的序列。最终序列的元素类型由 select 子句（在 Visual Basic 中为 Select 子句）指定。在此示例中，该类型是由每个匹配对的 Person.FirstName 和 Pet.Name 属性组成的匿名类型。

query1 的结果等效于使用不带 into 子句的 join 子句 (C#) 或使用 Join 子句 (Visual Basic) 执行内部联接所获得的结果集。query2 变量演示了这一等效查询。

C# 复制代码
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

/// <summary>
/// Performs an inner join by using GroupJoin().
/// </summary>
public static void InnerGroupJoinExample()
{
    Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
    Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
    Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
    Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

    Pet barley = new Pet { Name = "Barley", Owner = terry };
    Pet boots = new Pet { Name = "Boots", Owner = terry };
    Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
    Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
    Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

    // Create two lists.
    List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
    List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

    var query1 = from person in people
                 join pet in pets on person equals pet.Owner into gj
                 from subpet in gj
                 select new { OwnerName = person.FirstName, PetName = subpet.Name };

    Console.WriteLine("Inner join using GroupJoin():");
    foreach (var v in query1)
    {
        Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName);
    }

    var query2 = from person in people
                 join pet in pets on person equals pet.Owner
                 select new { OwnerName = person.FirstName, PetName = pet.Name };

    Console.WriteLine("\nThe equivalent operation using Join():");
    foreach (var v in query2)
        Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName);
}

// This code produces the following output:
//
// Inner join using GroupJoin():
// Magnus - Daisy
// Terry - Barley
// Terry - Boots
// Terry - Blue Moon
// Charlotte - Whiskers
//
// The equivalent operation using Join():
// Magnus - Daisy
// Terry - Barley
// Terry - Boots
// Terry - Blue Moon
// Charlotte - Whiskers

编译代码

  • 在 Visual Studio 中创建一个新的控制台应用程序项目。

  • 添加对 System.Core.dll 的引用（如果尚未引用它的话）。

  • 包含 System.Linq 命名空间。

  • 从示例中复制代码，并将其粘贴到 program.cs 文件中的 Main 方法之下。向 Main 方法添加一行代码，以调用粘入的方法。

  • 运行该程序。

请参见

任务

如何：执行分组联接（C# 编程指南）
如何：执行左外部联接（C# 编程指南）

概念

联接运算
匿名类型

参考

Join
GroupJoin
匿名类型（C# 编程指南）


如何：执行分组联接（C# 编程指南）

[image]

C# 编程指南
如何：执行分组联接（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

分组联接可用于产生分层数据结构。它将第一个集合中的每个元素与第二个集合中的一组相关元素进行配对。

例如，一个名为 Student 的类或关系数据库表可能包含两个字段：Id 和 Name。另一个名为 Course 的类或关系数据库表可能包含两个字段：StudentId 和 CourseTitle。这两个数据源的分组联接（基于匹配的 Student.Id 和 Course.StudentId）会将每个 Student 与一个 Course 对象集合（可能为空）组合在一起。

说明：

第一个集合中的每个元素都会出现在分组联接的结果集内，而无论是否在第二个集合中找到相关元素。如果未找到相关元素，则该元素的相关元素序列为空。因此，结果选择器可以访问第一个集合的每个元素。非分组联接中的结果选择器与此不同，它无法访问第一个集合中的那些在第二个集合中没有匹配元素的元素。

本主题中的第一个示例演示如何执行分组联接；第二个示例演示如何使用分组联接创建 XML 元素。

示例

分组联接示例

下面的示例根据与 Pet.Owner 属性匹配的 Person 来对 Person 和 Pet 类型的对象执行分组联接。与为每个匹配产生一对元素的非分组联接不同，分组联接只为第一个集合的每个元素产生一个结果对象（在此示例中为一个 Person 对象）。第二个集合中的相应元素（在此示例中为 Pet 对象）被分组到一个集合中。最后，结果选择器函数为每个包含 Person.FirstName 和一个 Pet 对象集合的匹配创建一个匿名类型。

C# 复制代码
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

/// <summary>
/// This example performs a grouped join.
/// </summary>
public static void GroupJoinExample()
{
    Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
    Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
    Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
    Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

    Pet barley = new Pet { Name = "Barley", Owner = terry };
    Pet boots = new Pet { Name = "Boots", Owner = terry };
    Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
    Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
    Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

    // Create two lists.
    List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
    List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

    // Create a list where each element is an anonymous type
    // that contains the person's first name and a collection of
    // pets that are owned by them.
    var query = from person in people
                join pet in pets on person equals pet.Owner into gj
                select new { OwnerName = person.FirstName, Pets = gj };

    foreach (var v in query)
    {
        // Output the owner's name.
        Console.WriteLine("{0}:", v.OwnerName);
        // Output each of the owner's pet's names.
        foreach (Pet pet in v.Pets)
            Console.WriteLine("  {0}", pet.Name);
    }
}

// This code produces the following output:
//
// Magnus:
//   Daisy
// Terry:
//   Barley
//   Boots
//   Blue Moon
// Charlotte:
//   Whiskers
// Arlene:

执行分组联接以创建 XML 的示例

分组联接非常适合于使用 LINQ to XML 来创建 XML。下面的示例与上一个示例类似，不同之处在于：结果选择器函数创建表示已联接对象的 XML 元素，而不是创建匿名类型。有关 LINQ to XML 的更多信息，请参见 LINQ to XML。

C# 复制代码
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

/// <summary>
/// This example creates XML output from a grouped join.
/// </summary>
public static void GroupJoinXMLExample()
{
    Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
    Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
    Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
    Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

    Pet barley = new Pet { Name = "Barley", Owner = terry };
    Pet boots = new Pet { Name = "Boots", Owner = terry };
    Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
    Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
    Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

    // Create two lists.
    List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
    List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

    // Create XML to display the hierarchical organization of people and their pets.
    XElement ownersAndPets = new XElement("PetOwners",
        from person in people
        join pet in pets on person equals pet.Owner into gj
        select new XElement("Person",
            new XAttribute("FirstName", person.FirstName),
            new XAttribute("LastName", person.LastName),
            from subpet in gj
            select new XElement("Pet", subpet.Name)));

    Console.WriteLine(ownersAndPets);
}

// This code produces the following output:
//
// <PetOwners>
//   <Person FirstName="Magnus" LastName="Hedlund">
//     <Pet>Daisy</Pet>
//   </Person>
//   <Person FirstName="Terry" LastName="Adams">
//     <Pet>Barley</Pet>
//     <Pet>Boots</Pet>
//     <Pet>Blue Moon</Pet>
//   </Person>
//   <Person FirstName="Charlotte" LastName="Weiss">
//     <Pet>Whiskers</Pet>
//   </Person>
//   <Person FirstName="Arlene" LastName="Huff" />
// </PetOwners>

编译代码

  • 在 Visual Studio 中创建一个新的“控制台应用程序”项目。

  • 添加一个对 System.Core.dll 的引用和一个对 System.Xml.Linq.dll 的引用（如果它们尚未被引用的话）。

  • 包括 System.Linq 和 System.Xml.Linq 命名空间。

  • 从示例中复制代码，并将其粘贴到 program.cs 文件中的 Main 方法之下。向 Main 方法添加一行代码，以调用粘入的方法。

  • 运行该程序。

请参见

任务

如何：执行内部联接（C# 编程指南）
如何：执行左外部联接（C# 编程指南）

概念

联接运算
匿名类型

参考

Join
GroupJoin
匿名类型（C# 编程指南）

其他资源

LINQ to XML


如何：执行左外部联接（C# 编程指南）

[image]

C# 编程指南
如何：执行左外部联接（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

左外部联接是这样一个联接：在其中返回第一个集合的每个元素，而无论该元素在第二个集合中是否具有相关元素。可以使用 LINQ，通过对分组联接的结果调用 DefaultIfEmpty 来执行左外部联接。

示例

下面的示例演示如何对分组联接的结果调用 DefaultIfEmpty 方法来执行左外部联接。

若要生成两个集合的左外部联接，第一步是使用分组联接执行内部联接。（有关此过程的说明，请参见如何：执行内部联接（C# 编程指南）。）在此示例中，基于一个与 Pet.Owner 匹配的 Person 对象，将 Person 对象的列表内部联接到 Pet 对象的列表。

第二步是在结果集内包含第一个（左）集合的每个元素，即使该元素在右集合中没有匹配的元素也是如此。这是通过对分组联接中的每个匹配元素序列调用 DefaultIfEmpty 来实现的。在此示例中，对每个匹配的 Pet 对象序列调用 DefaultIfEmpty。如果对于任何 Person 对象，匹配的 Pet 对象序列都为空，则该方法将返回一个包含单个默认值的集合，从而确保每个 Person 对象都在结果集合中得到表示。

说明：

对于引用类型，默认值为 null；因此，该示例在访问每个 Pet 集合的每个元素之前都会检查是否存在 null 引用。

C# 复制代码
class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

public static void LeftOuterJoinExample()
{
    Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
    Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
    Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
    Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

    Pet barley = new Pet { Name = "Barley", Owner = terry };
    Pet boots = new Pet { Name = "Boots", Owner = terry };
    Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
    Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
    Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

    // Create two lists.
    List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
    List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

    var query = from person in people
                join pet in pets on person equals pet.Owner into gj
                from subpet in gj.DefaultIfEmpty()
                select new { person.FirstName, PetName = (subpet == null ? String.Empty : subpet.Name) };

    foreach (var v in query)
    {
        Console.WriteLine("{0,-15}{1}", v.FirstName + ":", v.PetName);
    }
}

// This code produces the following output:
//
// Magnus:         Daisy
// Terry:          Barley
// Terry:          Boots
// Terry:          Blue Moon
// Charlotte:      Whiskers
// Arlene:

编译代码

  • 在 Visual Studio 中创建一个新的控制台应用程序项目。

  • 添加对 System.Core.dll 的引用（如果尚未引用它的话）。

  • 包含 System.Linq 命名空间。

  • 从示例中复制代码，并将其粘贴到 program.cs 文件中的 Main 方法之下。向 Main 方法添加一行代码，以调用粘入的方法。

  • 运行该程序。

请参见

任务

如何：执行内部联接（C# 编程指南）
如何：执行分组联接（C# 编程指南）

概念

联接运算
匿名类型

参考

Join
GroupJoin
匿名类型（C# 编程指南）


如何：对 Join 子句的结果进行排序（C# 编程指南）

[image]

C# 编程指南
如何：对 Join 子句的结果进行排序（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例演示如何对联接运算的结果进行排序。请注意，排序是在联接之后执行的。尽管可以在联接之前将 orderby 子句用于一个或多个源序列，但通常我们不建议这样做。某些 LINQ 提供程序可能不会在联接之后保留排序。

示例

此查询将创建一个分组联接，然后基于类别元素（仍然在范围中）对组进行排序。在匿名类型初始值设定项中，子查询将对产品序列中的所有匹配元素进行排序。

C# 复制代码
class HowToOrderJoins
{
    #region Data
    class Product
    {
        public string Name { get; set; }
        public int CategoryID { get; set; }
    }

    class Category
    {
        public string Name { get; set; }
        public int ID { get; set; }
    }

    // Specify the first data source.
    List<Category> categories = new List<Category>()
    {
        new Category(){Name="Beverages", ID=001},
        new Category(){ Name="Condiments", ID=002},
        new Category(){ Name="Vegetables", ID=003},
        new Category() {  Name="Grains", ID=004},
        new Category() {  Name="Fruit", ID=005}
    };

    // Specify the second data source.
    List<Product> products = new List<Product>()
   {
      new Product{Name="Cola",  CategoryID=001},
      new Product{Name="Tea",  CategoryID=001},
      new Product{Name="Mustard", CategoryID=002},
      new Product{Name="Pickles", CategoryID=002},
      new Product{Name="Carrots", CategoryID=003},
      new Product{Name="Bok Choy", CategoryID=003},
      new Product{Name="Peaches", CategoryID=005},
      new Product{Name="Melons", CategoryID=005},
    };
    #endregion
    static void Main()
    {
        HowToOrderJoins app = new HowToOrderJoins();
        app.OrderJoin1();

        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();

    }

    void OrderJoin1()
    {
        var groupJoinQuery2 =
            from category in categories
            join prod in products on category.ID equals prod.CategoryID into prodGroup
            orderby category.Name
            select new
            {
                Category = category.Name,
                Products = from prod2 in prodGroup
                           orderby prod2.Name
                           select prod2
            };

        foreach (var productGroup in groupJoinQuery2)
        {
            Console.WriteLine(productGroup.Category);
            foreach (var prodItem in productGroup.Products)
            {
                Console.WriteLine("  {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
            }
        }
    }
    /* Output:
        Beverages
          Cola       1
          Tea        1
        Condiments
          Mustard    2
          Pickles    2
        Fruit
          Melons     5
          Peaches    5
        Grains
        Vegetables
          Bok Choy   3
          Carrots    3
     */
}

编译代码

  • 创建面向 .NET Framework 3.5 版的 Visual Studio 项目。默认情况下，该项目具有一个对 System.Core.dll 的引用以及一条针对 System.Linq 命名空间的 using 指令。

  • 将代码复制到项目中。

  • 按 F5 编译并运行程序。

  • 按任意键退出控制台窗口。

请参见

概念

LINQ 查询表达式（C# 编程指南）
联接运算

参考

orderby 子句（C# 参考）
join 子句（C# 参考）


如何：使用复合键进行联接（C# 编程指南）

[image]

C# 编程指南
如何：使用复合键进行联接（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例演示如何执行想要使用多个键来定义匹配的联接操作。使用组合键来完成此操作。以匿名类型或包含要比较的值的命名类型的形式来创建组合键。如果将跨方法边界传递查询变量，请使用为该键重写 Equals 和 GetHashCode 的命名类型。属性的名称以及属性出现的顺序在每个键中必须相同。

示例

下面的示例演示如何使用组合键来联接来自三个表中的数据：

复制代码
var query = from o in db.Orders
    from p in db.Products
    join d in db.OrderDetails
        on new {o.OrderID, p.ProductID} equals new {d.OrderID,        d.ProductID} into details
        from d in details
        select new {o.OrderID, p.ProductID, d.UnitPrice};

组合键上的类型推理取决于这些键中属性的名称，以及属性的出现顺序。如果源序列中属性的名称不同，您必须在键中分配新名称。例如，如果 Orders 表和 OrderDetails 表各自为它们的列使用不同的名称，则您将通过在匿名类型中分配相同的名称来创建组合键：

复制代码
join...on new {Name = o.CustomerName, ID = o.CustID} equals
    new {Name = d.CustName, ID = d.CustID }

还可以在 group 子句中使用组合键。

编译代码

  • 若要编译并运行此代码，请按下面的步骤进行操作：

  • 打开 LINQ to Northwind 示例，并按照说明进行操作来设置项目并创建数据库连接。

  • 在 samples.cs 中创建一个新的空方法，该方法采用名为 db 的 Northwind 输入参数（与该文件中的其他方法类似）。将此示例中的代码粘贴到方法体中。

  • 修改 program.cs 以从 Main 中调用该新方法。

  • 按 F5 编译并运行查询。

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

join 子句（C# 参考）
group 子句（C# 参考）


如何：执行自定义联接操作（C# 编程指南）

[image]

C# 编程指南
如何：执行自定义联接操作（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例演示如何执行无法使用 join 子句执行的联接操作。在查询表达式中，join 子句仅适用于同等联接（这是迄今为止最常见的联接操作类型），并针对同等联接进行了优化。执行同等联接时，一般总是可以通过使用 join 子句获得最佳性能。

但在下面一些情况下无法使用 join 子句：

  • 联接是在不等式（非同等联接）上断言的。

  • 联接是在多个等式或不等式上断言的。

  • 必须在联接操作之前为右侧（内部）序列引入一个临时范围变量。

若要执行非同等联接，可以使用多个 from 子句单独引入每个数据源。然后，在 where 子句中将谓词表达式应用于每个源的范围变量。该表达式还可以采用方法调用的形式。

说明：

不要将这种自定义联接操作与使用多个 from 子句访问内部集合相混淆。有关更多信息，请参见 join 子句（C# 参考）。

示例

下面示例中的第一个方法演示了一个简单的交叉联接。必须慎用交叉联接，因为它们可能产生非常大的结果集。但在某些方案中，可以使用它们创建源序列以供运行附加查询。

第二个方法产生其类别 ID 列在左侧类别列表中的所有产品的序列。请注意，这种方法使用 let 子句和 Contains 方法创建了一个临时数组。也可以在查询前创建该数组并去掉第一个 from 子句。

C# 复制代码
    class CustomJoins
    {

        #region Data

        class Product
        {
            public string Name { get; set; }
            public int CategoryID { get; set; }
        }

        class Category
        {
            public string Name { get; set; }
            public int ID { get; set; }
        }

        // Specify the first data source.
        List<Category> categories = new List<Category>()
        {
            new Category(){Name="Beverages", ID=001},
            new Category(){ Name="Condiments", ID=002},
            new Category(){ Name="Vegetables", ID=003},
        };

        // Specify the second data source.
        List<Product> products = new List<Product>()
       {
          new Product{Name="Tea",  CategoryID=001},
          new Product{Name="Mustard", CategoryID=002},
          new Product{Name="Pickles", CategoryID=002},
          new Product{Name="Carrots", CategoryID=003},
          new Product{Name="Bok Choy", CategoryID=003},
          new Product{Name="Peaches", CategoryID=005},
          new Product{Name="Melons", CategoryID=005},
          new Product{Name="Ice Cream", CategoryID=007},
          new Product{Name="Mackerel", CategoryID=012},
        };
        #endregion

        static void Main()
        {
            CustomJoins app = new CustomJoins();
            app.CrossJoin();
            app.NonEquijoin();

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        void CrossJoin()
        {
            var crossJoinQuery =
                from c in categories
                from p in products
                select new { c.ID, p.Name };

            Console.WriteLine("Cross Join Query:");
            foreach (var v in crossJoinQuery)
            {
                Console.WriteLine("{0,-5}{1}", v.ID, v.Name);
            }
        }

        void NonEquijoin()
        {
            var nonEquijoinQuery =
                from p in products
                let catIds = from c in categories
                             select c.ID
                where catIds.Contains(p.CategoryID) == true
                select new { Product = p.Name, CategoryID = p.CategoryID };

            Console.WriteLine("Non-equijoin query:");
            foreach (var v in nonEquijoinQuery)
            {
                Console.WriteLine("{0,-5}{1}",  v.CategoryID, v.Product);
            }
        }
    }
    /* Output:
Cross Join Query:
1    Tea
1    Mustard
1    Pickles
1    Carrots
1    Bok Choy
1    Peaches
1    Melons
1    Ice Cream
1    Mackerel
2    Tea
2    Mustard
2    Pickles
2    Carrots
2    Bok Choy
2    Peaches
2    Melons
2    Ice Cream
2    Mackerel
3    Tea
3    Mustard
3    Pickles
3    Carrots
3    Bok Choy
3    Peaches
3    Melons
3    Ice Cream
3    Mackerel
Non-equijoin query:
1    Tea
2    Mustard
2    Pickles
3    Carrots
3    Bok Choy
Press any key to exit.
     */

在下面的示例中，查询必须基于匹配键联接两个序列，而对于内部（右侧）序列而言，无法在 join 子句本身之前获取这些键。如果此联接操作是使用 join 子句执行的，则必须为每个元素调用 Split 方法。使用多个 from 子句可使查询避免反复进行方法调用的系统开销。但由于联接进行了优化，因此在此特定情况下，它仍然可能比使用多个 from 子句快。结果会有所不同，主要取决于方法调用的系统开销有多大。

C# 复制代码
class MergeTwoCSVFiles : StudentClass
{
    static void Main()
    {
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type
        // var could be used instead of an explicit type
        IEnumerable<Student> queryNamesScores =
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            where x[2] == s[0]
            select new Student()
            {
                FirstName = x[0],
                LastName = x[1],
                ID = Convert.ToInt32(x[2]),
                ExamScores = (from scoreAsText in s.Skip(1)
                              select Convert.ToInt32(scoreAsText)).
                              ToList()
            };

        // Optional. Store the newly created student objects in memory
        // for faster access in future queries
        List<Student> students = queryNamesScores.ToList();

        foreach (var student in students)
        {
            Console.WriteLine("The average score of {0} {1} is {2}.",
                student.FirstName, student.LastName, student.ExamScores.Average());
        }

        //Keep console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    The average score of Adams Terry is 85.25.
    The average score of Fakhouri Fadi is 92.25.
    The average score of Feng Hanying is 88.
    The average score of Garcia Cesar is 88.25.
    The average score of Garcia Debra is 67.
    The average score of Garcia Hugo is 85.75.
    The average score of Mortensen Sven is 84.5.
    The average score of O'Donnell Claire is 72.25.
    The average score of Omelchenko Svetlana is 82.5.
    The average score of Tucker Lance is 81.75.
    The average score of Tucker Michael is 92.
    The average score of Zabokritski Eugene is 83.
 */

此示例也包含在如何：从多个源填充对象集合 (LINQ) 一文中。若要编译和运行此示例，请按照该文章中的说明操作。

编译代码

  • 创建面向 .NET Framework 3.5 版的 Visual Studio 项目。默认情况下，该项目具有一个对 System.Core.dll 的引用以及一条针对 System.Linq 命名空间的 using 指令。

  • 将代码复制到项目中。

  • 按 F5 编译并运行程序。

  • 按任意键退出控制台窗口。

请参见

任务

如何：对 Join 子句的结果进行排序（C# 编程指南）

概念

LINQ 查询表达式（C# 编程指南）
联接运算

参考

join 子句（C# 参考）


如何：在查询表达式中处理 Null 值（C# 编程指南）

[image]

C# 编程指南
如何：在查询表达式中处理 Null 值（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例演示如何处理源集合中可能的 null 值。诸如 IEnumerable<(Of <(T>)>) 等对象集合可能包含值为 null 的元素。如果源集合为 null 或包含值为 null 的元素，并且查询未处理 null 值，当您执行查询时将会引发 NullReferenceException。

示例

您可以采用防御方式进行编码以避免 null 引用异常，如下面的示例中所示：

C# 复制代码
var query1 =
    from c in categories
    where c != null
    join p in products on c.ID equals
        (p == null ? null : p.CategoryID)
    select new { Category = c.Name, Name = p.Name };

在前面的示例中，where 子句筛选出类别序列中的所有 null 元素。此技术与 join 子句中的 null 检查无关。此示例中包含 null 的条件表达式将发挥作用，因为 Products.CategoryID 的类型为 int?（Nullable<int> 的简写形式）。

在 join 子句中，只要其中一个比较键为可 null 的类型，您就可以在查询表达式中将另一个比较键强制转换为可译为 null 的类型。在下面的示例中，假定 EmployeeID 是一个列，其中包含类型为 int? 的值：

C# 复制代码
void TestMethod(Northwind db)
{
    var query =
        from o in db.Orders
        join e in db.Employees
            on o.EmployeeID equals (int?)e.EmployeeID
        select new { o.OrderID, e.FirstName };
}

请参见

任务

LINQ to Northwind 示例

概念

LINQ 查询表达式（C# 编程指南）

参考

可空类型（C# 编程指南）
Nullable<(Of <(T>)>)


如何：在查询表达式中处理异常（C# 编程指南）

[image]

C# 编程指南
如何：在查询表达式中处理异常（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可以在查询表达式的上下文中调用任何方法。但是，建议您避免在查询表达式中调用任何可能产生副作用（例如，修改数据源的内容或引发异常）的方法。此示例演示在查询表达式中调用方法时如何避免引发异常，同时又不违反 .NET Framework 的有关异常处理的一般性准则。这些准则说明：如果您了解为什么在给定上下文中会引发特定异常，那么就可以捕捉该异常。有关更多信息，请参见异常处理。

最后一个示例演示如何处理那些必须在查询执行过程中引发异常的情况。

示例

下面的示例演示如何将异常处理代码移至查询表达式外部。仅当该方法不依赖于查询的任何本地变量时，才能这样做。

C# 复制代码
class ExceptionsOutsideQuery
{
    static void Main()
    {
        // DO THIS with a datasource that might
        // throw an exception. It is easier to deal with
        // outside of the query expression.
        IEnumerable<int> dataSource;
        try
        {
            dataSource = GetData();
        }
        catch (InvalidOperationException)
        {
            // Handle (or don't handle) the exception
            // in the way that is appropriate for your application.
            Console.WriteLine("Invalid operation");
            goto Exit;
        }

        // If we get here, it is safe to proceed.
        var query = from i in dataSource
                    select i * i;

        foreach (var i in query)
            Console.WriteLine(i.ToString());

        //Keep the console window open in debug mode
        Exit:
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }

    // A data source that is very likely to throw an exception!
    static IEnumerable<int> GetData()
    {
        throw new InvalidOperationException();
    }
}

在某些情况下，对在查询内引发的异常的最佳响应可能是立即停止执行查询。下面的示例演示如何处理可能从查询正文内部引发的异常。假定 SomeMethodThatMightThrow 可能导致要求停止执行查询的异常。

请注意，try 块将 foreach 循环而不是查询本身封闭起来。这是因为 foreach 循环是实际执行查询的场所。有关更多信息，请参见 LINQ 查询介绍。

C# 复制代码
class QueryThatThrows
{
    static void Main()
    {
        // Data source.
        string[] files = { "fileA.txt", "fileB.txt", "fileC.txt" };

        // Demonstration query that throws.
        var exceptionDemoQuery =
            from file in files
            let n = SomeMethodThatMightThrow(file)
            select n;

        // Runtime exceptions are thrown when query is executed.
        // Therefore they must be handled in the foreach loop.
        try
        {
            foreach (var item in exceptionDemoQuery)
            {
                Console.WriteLine("Processing {0}", item);
            }
        }

        // Catch whatever exception you expect to raise
        // and/or do any necessary claanup in a finally block
        catch (InvalidOperationException e)
        {
            Console.WriteLine(e.Message);
        }

        //Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }

    // Not very useful as a general purpose method.
    static string SomeMethodThatMightThrow(string s)
    {
        if (s[4] == 'C')
            throw new InvalidOperationException();
        return @"C:\newFolder\" + s;
    }
}
/* Output:
    Processing C:\newFolder\fileA.txt
    Processing C:\newFolder\fileB.txt
    Operation is not valid due to the current state of the object.
 */

编译代码

  • 创建面向 .NET Framework 3.5 版的 Visual Studio 项目。默认情况下，该项目具有一个对 System.Core.dll 的引用以及一条针对 System.Linq 命名空间的 using 指令。

  • 将代码复制到项目中。

  • 按 F5 编译并运行程序。

按任意键退出控制台窗口。

请参见

概念

LINQ 查询表达式（C# 编程指南）


迭代器（C# 编程指南）

[image]

C# 编程指南
迭代器（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

迭代器是一种方法、get 访问器或运算符，它通过使用 yield 关键字对数组或集合类执行自定义迭代。yield 返回语句会导致源序列中的元素在访问源序列中的下一个元素之前立即返回给调用方。尽管您以方法的形式编写迭代器，但编译器会将其转换为一个实际上是状态机的嵌套类。只要客户端代码中的 foreach 循环继续进行，此类就会跟踪迭代器的位置。

说明：

若要了解编译器在后台执行了什么操作，请使用 ILDASM.exe 工具来查看为迭代器方法生成的中间语言 (IL) 代码。

将使用 foreach 语句从客户端代码中调用迭代器。例如，您可以为类创建一个迭代器，该迭代器将按相反顺序返回元素，或在迭代器返回元素之前对每个元素执行操作。在为类或结构创建迭代器时，您不必实现整个 IEnumerator 接口。当编译器检测到迭代器时，它将自动生成 IEnumerator 或 IEnumerator<(Of <(T>)>) 接口的 Current、MoveNext 和 Dispose 方法。

迭代器概述

  • 迭代器是可以返回相同类型的值的有序序列的一段代码。

  • 迭代器可用作方法、运算符或 get 访问器的代码体。

  • 迭代器代码使用 yield return 语句依次返回每个元素。yield break 将终止迭代。

  • 可以在类中实现多个迭代器。每个迭代器都必须像任何类成员一样有唯一的名称，并且可以在 foreach 语句中被客户端代码调用，如下所示：foreach(int x in SampleClass.Iterator2){}。

  • 迭代器的返回类型必须为 IEnumerable、IEnumerator、IEnumerable<(Of <(T>)>) 或 IEnumerator<(Of <(T>)>)。

  • 迭代器是 LINQ 查询中延迟执行行为的基础。

yield 关键字用于指定返回的一个或多个值。到达 yield return 语句时，会保存当前位置。下次调用迭代器时将从此位置重新开始执行。

迭代器对集合类特别有用，它提供一种简单的方法来迭代复杂的数据结构（如二进制树）。

相关章节

更多信息：

  • 使用迭代器（C# 编程指南）

  • 如何：为整数列表创建迭代器块（C# 编程指南）

  • 如何：为泛型列表创建迭代器块（C# 编程指南）

  • 泛型接口（C# 编程指南）

示例

在本示例中，DaysOfTheWeek 类是将一周中的各天作为字符串进行存储的简单集合类。foreach 循环每迭代一次，都返回集合中的下一个字符串。

C# 复制代码
public class DaysOfTheWeek : System.Collections.IEnumerable
{
    string[] days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };

    public System.Collections.IEnumerator GetEnumerator()
    {
        for (int i = 0; i < days.Length; i++)
        {
            yield return days[i];
        }
    }
}

class TestDaysOfTheWeek
{
    static void Main()
    {
        // Create an instance of the collection class
        DaysOfTheWeek week = new DaysOfTheWeek();

        // Iterate with foreach
        foreach (string day in week)
        {
            System.Console.Write(day + " ");
        }
    }
}
// Output: Sun Mon Tue Wed Thr Fri Sat

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.14 迭代器

请参见

概念

C# 编程指南

参考

泛型（C# 编程指南）


使用迭代器（C# 编程指南）

[image]

C# 编程指南
使用迭代器（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

创建迭代器最常用的方法是对 IEnumerable 接口实现 GetEnumerator 方法，例如：

C# 复制代码
public System.Collections.IEnumerator GetEnumerator()
{
    for (int i = 0; i < 10; i++)
    {
        yield return i;
    }
}

GetEnumerator 方法的存在使得类型成为可枚举的类型，并允许使用 foreach 语句。如果上面的方法是 ListClass 的类定义的一部分，则可以对该类使用 foreach，如下所示：

C# 复制代码
static void Main()
{
    ListClass listClass1 = new ListClass();

    foreach (int i in listClass1)
    {
        System.Console.WriteLine(i);
    }
}

foreach 语句调用 ListClass.GetEnumerator() 并使用返回的枚举数来循环访问值。有关如何创建返回 IEnumerator<(Of <(T>)>) 接口的泛型迭代器的示例，请参见如何：为泛型列表创建迭代器块（C# 编程指南）。

还可以使用命名的迭代器以支持通过不同的方式循环访问同一数据集合。例如，您可以提供一个按升序返回元素的迭代器，而提供按降序返回元素的另一个迭代器。迭代器还可以带有参数，以便允许客户端控制全部或部分迭代行为。下面的迭代器使用命名的迭代器 SampleIterator 实现 IEnumerable 接口：

C# 复制代码
// Implementing the enumerable pattern
public System.Collections.IEnumerable SampleIterator(int start, int end)
{
    for (int i = start; i <= end; i++)
    {
        yield return i;
    }
}

命名的迭代器的调用方法如下：

C# 复制代码
ListClass test = new ListClass();
foreach (int n in test.SampleIterator(1, 10))
{
    System.Console.WriteLine(n);
}

可以在同一个迭代器中使用多个 yield 语句，如下面的示例所示：

C# 复制代码
public System.Collections.IEnumerator GetEnumerator()
{
    yield return "With an iterator, ";
    yield return "more than one ";
    yield return "value can be returned";
    yield return ".";
}

然后可以使用下面的 foreach 语句输出结果：

C# 复制代码
foreach (string element in new TestClass())
{
    System.Console.Write(element);
}

此示例显示以下文本：

With an iterator, more than one value can be returned.

在 foreach 循环的每次后续迭代（或对 IEnumerator.MoveNext 的直接调用）中，下一个迭代器代码体将从前一个 yield 语句之后开始，并继续下一个语句直至到达迭代器体的结尾或遇到 yield break 语句。

迭代器不支持 IEnumeratorReset()()() 方法。若要从头开始重新循环访问，必须获取新迭代器。

请参见

任务

如何：为整数列表创建迭代器块（C# 编程指南）
如何：为泛型列表创建迭代器块（C# 编程指南）

概念

C# 编程指南

参考

yield（C# 参考）
对数组使用 foreach（C# 编程指南）
foreach，in（C# 参考）


如何：为整数列表创建迭代器块（C# 编程指南）

[image]

C# 编程指南
如何：为整数列表创建迭代器块（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例使用一个整数数组来构建列表 SampleCollection。for 循环对集合进行循环访问，并返回每项的值。然后使用 foreach 循环来显示该集合的项。

示例

C# 复制代码
// Declare the collection:
public class SampleCollection
{
    public int[] items;

    public SampleCollection()
    {
        items = new int[5] { 5, 4, 7, 9, 3 };
    }

    public System.Collections.IEnumerable BuildCollection()
    {
        for (int i = 0; i < items.Length; i++)
        {
            yield return items[i];
        }
    }
}

class MainClass
{
    static void Main()
    {
        SampleCollection col = new SampleCollection();

        // Display the collection items:
        System.Console.WriteLine("Values in the collection are:");
        foreach (int i in col.BuildCollection())
        {
            System.Console.Write(i + " ");
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    Values in the collection are:
    5 4 7 9 3
*/

请参见

任务

如何：为泛型列表创建迭代器块（C# 编程指南）

概念

C# 编程指南

参考

迭代器（C# 编程指南）
使用迭代器（C# 编程指南）
Array
yield（C# 参考）


如何：为泛型列表创建迭代器块（C# 编程指南）

[image]

C# 编程指南
如何：为泛型列表创建迭代器块（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在此示例中，泛型类 Stack<T> 实现泛型接口 IEnumerator<(Of <(T>)>)。声明了一个类型 T 的数组，并使用 Push 方法给数组赋值。在 GetEnumerator 方法中，使用 yield return 语句返回数组的值。

还实现非泛型 GetEnumerator，因为 IEnumerable<(Of <(T>)>) 继承自 IEnumerable。此示例显示了典型的实现，其中，非泛型方法仅将调用转给泛型方法。

示例

C# 复制代码
using System.Collections;
using System.Collections.Generic;

namespace GenericIteratorExample
{
    public class Stack<T> : IEnumerable<T>
    {
        private T[] values = new T[100];
        private int top = 0;

        public void Push(T t) { values[top++] = t; }
        public T Pop() { return values[--top]; }

        // These make Stack<T> implement IEnumerable<T> allowing
        // a stack to be used in a foreach statement.
        public IEnumerator<T> GetEnumerator()
        {
            for (int i = top - 1; i >= 0; i-- )
            {
                yield return values[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        // Iterate from top to bottom.
        public IEnumerable<T> TopToBottom
        {
            get
            {
                // Since we implement IEnumerable<T>
                // and the default iteration is top to bottom,
                // just return the object.
                return this;
            }
        }

        // Iterate from bottom to top.
        public IEnumerable<T> BottomToTop
        {
            get
            {
                for (int i = 0; i < top; i++)
                {
                    yield return values[i];
                }
            }
        }

        // A parameterized iterator that return n items from the top
        public IEnumerable<T> TopN(int n)
        {
            // in this example we return less than N if necessary
            int j = n >= top ? 0 : top - n;

            for (int i = top; --i >= j; )
            {
                yield return values[i];
            }
        }
    }

    // This code uses a stack and the TopToBottom and BottomToTop properties
    // to enumerate the elements of the stack.
    class Test
    {
        static void Main()
        {
            Stack<int> s = new Stack<int>();
            for (int i = 0; i < 10; i++)
            {
                s.Push(i);
            }

            // Prints: 9 8 7 6 5 4 3 2 1 0
            // Foreach legal since s implements IEnumerable<int>
            foreach (int n in s)
            {
                System.Console.Write("{0} ", n);
            }
            System.Console.WriteLine();

            // Prints: 9 8 7 6 5 4 3 2 1 0
            // Foreach legal since s.TopToBottom returns IEnumerable<int>
            foreach (int n in s.TopToBottom)
            {
                System.Console.Write("{0} ", n);
            }
            System.Console.WriteLine();

            // Prints: 0 1 2 3 4 5 6 7 8 9
            // Foreach legal since s.BottomToTop returns IEnumerable<int>
            foreach (int n in s.BottomToTop)
            {
                System.Console.Write("{0} ", n);
            }
            System.Console.WriteLine();

            // Prints: 9 8 7 6 5 4 3
            // Foreach legal since s.TopN returns IEnumerable<int>
            foreach (int n in s.TopN(7))
            {
                System.Console.Write("{0} ", n);
            }
            System.Console.WriteLine();

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }
}
/* Output:
    9 8 7 6 5 4 3 2 1 0
    9 8 7 6 5 4 3 2 1 0
    0 1 2 3 4 5 6 7 8 9
    9 8 7 6 5 4 3
*/

请参见

任务

如何：为整数列表创建迭代器块（C# 编程指南）

概念

C# 编程指南

参考

迭代器（C# 编程指南）
使用迭代器（C# 编程指南）
System.Collections.Generic
IEnumerable<(Of <(T>)>)


命名空间（C# 编程指南）

[image]

C# 编程指南
命名空间（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

使用 C# 编程时，通过两种方式来大量使用命名空间。首先，.NET Framework 使用命名空间来组织它的众多类，如下所示：

C# 复制代码
System.Console.WriteLine("Hello World!");

System 是一个命名空间，Console 是该命名空间中的类。可以使用 using 关键字，因此不必使用完整的名称，如以下示例所示：

C# 复制代码
using System;
C# 复制代码
Console.WriteLine("Hello");
Console.WriteLine("World!");

有关更多信息，请参见 using 指令（C# 参考）。

其次，在较大的编程项目中，声明自己的命名空间可以帮助控制类名称和方法名称的范围。使用 namespace 关键字可声明命名空间，如下例所示：

C# 复制代码
namespace SampleNamespace
{
    class SampleClass
    {
        public void SampleMethod()
        {
            System.Console.WriteLine(
              "SampleMethod inside SampleNamespace");
        }
    }
}

命名空间概述

命名空间具有以下属性：

  • 组织大型代码项目。

  • 使用 . 运算符将它们分隔。

  • using directive 不必为每个类指定命名空间的名称。

  • global 命名空间是“根”命名空间：global::System 始终引用 .NET Framework 命名空间 System。

相关章节

有关命名空间的更多信息，请参见下列主题：

  • 使用命名空间（C# 编程指南）

  • 如何：使用命名空间别名限定符（C# 编程指南）

  • 如何：使用 My 命名空间（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 9 命名空间

请参见

概念

C# 编程指南

参考

命名空间关键字（C# 参考）
using 指令（C# 参考）
:: 运算符（C# 参考）
. 运算符（C# 参考）


使用命名空间（C# 编程指南）

[image]

C# 编程指南
使用命名空间（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 程序中，通过两种方式来大量使用命名空间。首先，.NET Framework 类使用命名空间来组织它的众多类。其次，在较大的编程项目中，声明自己的命名空间可以帮助控制类和方法名的范围。

访问命名空间

大多数 C# 应用程序从一个 using 指令节开始。该节列出应用程序将会频繁使用的命名空间，避免程序员在每次使用其中包含的方法时都要指定完全限定的名称。

例如，通过在程序开头包括行：

C# 复制代码
using System;

程序员可以使用代码：

C# 复制代码
Console.WriteLine("Hello, World!");

而不是：

C# 复制代码
System.Console.WriteLine("Hello, World!");

命名空间别名

using 指令（C# 参考）还可用于创建命名空间的别名。例如，如果使用包含嵌套命名空间的以前编写的命名空间，您可能希望声明一个别名来提供引用特定命名空间的简写方法，如以下示例中所示：

C# 复制代码
using Co = Company.Proj.Nested;  // define an alias to represent a namespace

使用命名空间来控制范围

namespace 关键字用于声明一个范围。在项目中创建范围的能力有助于组织代码，并可让您创建全局唯一的类型。在下面的示例中，名为 SampleClass 的类在两个命名空间中定义，其中一个命名空间嵌套在另一个之内。. 运算符（C# 参考）用于区分所调用的方法。

C# 复制代码
namespace SampleNamespace
{
    class SampleClass
    {
        public void SampleMethod()
        {
            System.Console.WriteLine(
              "SampleMethod inside SampleNamespace");
        }
    }

    // Create a nested namespace, and define another class.
    namespace NestedNamespace
    {
        class SampleClass
        {
            public void SampleMethod()
            {
                System.Console.WriteLine(
                  "SampleMethod inside NestedNamespace");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Displays "SampleMethod inside SampleNamespace."
            SampleClass outer = new SampleClass();
            outer.SampleMethod();

            // Displays "SampleMethod inside SampleNamespace."
            SampleNamespace.SampleClass outer2 = new SampleNamespace.SampleClass();
            outer2.SampleMethod();

            // Displays "SampleMethod inside NestedNamespace."
            NestedNamespace.SampleClass inner = new NestedNamespace.SampleClass();
            inner.SampleMethod();
        }
    }
}

完全限定名

命名空间和类型的名称必须唯一，由指示逻辑层次结构的完全限定名描述。例如，语句 A.B 表示 A 是命名空间或类型的名称，而 B 则嵌套在其中。

下面的示例中有嵌套的类和命名空间。在每个实体的后面，需要完全限定名作为注释。

C# 复制代码
namespace N1     // N1
{
    class C1      // N1.C1
    {
        class C2   // N1.C1.C2
        {
        }
    }
    namespace N2  // N1.N2
    {
        class C2   // N1.N2.C2
        {
        }
    }
}

在以上代码段中：

  • 命名空间 N1 是全局命名空间的成员。它的完全限定名是 N1。

  • 命名空间 N2 是命名空间 N1 的成员。它的完全限定名是 N1.N2。

  • 类 C1 是 N1 的成员。它的完全限定名是 N1.C1。

  • 在此代码中使用了两次 C2 类名。但是，完全限定名是唯一的。C2 的第一个实例是在 C1 中声明的；因此，其完全限定名为：N1.C1.C2。C2 的第二个实例是在命名空间 N2 中声明的；因此，其完全限定名为：N1.N2.C2。

使用以上代码段，可以用以下方法将新的类成员 C3 添加到命名空间 N1.N2 内：

C# 复制代码
namespace N1.N2
{
    class C3   // N1.N2.C3
    {
    }
}

一般情况下，应使用 :: 来引用命名空间别名或使用 global:: 来引用全局命名空间，并使用 . 来限定类型或成员。

与引用类型而不是命名空间的别名一起使用 :: 是错误的。例如：

C# 复制代码
using Alias = System.Console;
C# 复制代码
class TestClass
{
    static void Main()
    {
        // Error
        //Alias::WriteLine("Hi");

        // OK
        Alias.WriteLine("Hi");
    }
}

记住单词 global 不是预定义的别名，因此 global.X 没有任何特殊的含义。仅当与 :: 一起使用时，它才获得特殊的含义。

定义名为 global 的别名将会产生警告（请参见编译器警告（等级 2）CS0440），因为 global:: 始终引用全局命名空间而不是别名。例如，下面的行将产生警告：

C# 复制代码
using global = System.Collections;   // Warning

最好将 :: 与别名一起使用，这样可以避免意外引入其他类型。以下面的代码为例：

C# 复制代码
using Alias = System;
C# 复制代码
namespace Library
{
    public class C : Alias.Exception { }
}

这样做可行，但是如果接着引入一个名为 Alias 的类型，则 Alias. 将改为绑定到该类型。使用 Alias::Exception 可以确保 Alias 被当作命名空间别名，而不会被误认为类型。

有关 global 别名的更多信息，请参见主题如何：使用命名空间别名限定符（C# 编程指南）。

请参见

概念

C# 编程指南

参考

命名空间（C# 编程指南）
命名空间关键字（C# 参考）
. 运算符（C# 参考）
:: 运算符（C# 参考）
extern（C# 参考）


如何：使用命名空间别名限定符（C# 编程指南）

[image]

C# 编程指南
如何：使用命名空间别名限定符（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

当成员可能被同名的其他实体隐藏时，能够访问全局命名空间中的成员非常有用。

例如，在下面的代码中，Console 在 System 命名空间中解析为 TestApp.Console 而不是 Console 类型。

C# 复制代码
using System;
C# 复制代码
class TestApp
{
    // Define a new class called 'System' to cause problems.
    public class System { }

    // Define a constant called 'Console' to cause more problems.
    const int Console = 7;
    const int number = 66;

    static void Main()
    {
        // Error  Accesses TestApp.Console
        //Console.WriteLine(number);
    }
}

由于类 TestApp.System 隐藏了 System 命名空间，因此使用 System.Console 仍然会导致错误：

C# 复制代码
// Error  Accesses TestApp.System
System.Console.WriteLine(number);

但是，可以通过使用 global::System.Console 避免这一错误，如下所示：

C# 复制代码
// OK
global::System.Console.WriteLine(number);

当左侧的标识符为 global 时，对右侧标识符的搜索将从全局命名空间开始。例如，下面的声明将 TestApp 作为全局空间的一个成员进行引用。

C# 复制代码
class TestClass : global::TestApp

显然，并不推荐创建自己的名为 System 的命名空间，您不可能遇到出现此情况的任何代码。但是，在较大的项目中，很有可能在一个窗体或其他窗体中出现命名空间重复。在这种情况下，全局命名空间限定符可保证您可以指定根命名空间。

示例

在此示例中，命名空间 System 用于包括类 TestClass，因此必须使用 global::System.Console 来引用 System.Console 类，该类被 System 命名空间隐藏。而且，别名 colAlias 用于引用命名空间 System.Collections；因此，将使用此别名而不是命名空间来创建 System.Collections..::.Hashtable 的实例。

C# 复制代码
using colAlias = System.Collections;
namespace System
{
    class TestClass
    {
        static void Main()
        {
            // Searching the alias:
            colAlias::Hashtable test = new colAlias::Hashtable();

            // Add items to the table.
            test.Add("A", "1");
            test.Add("B", "2");
            test.Add("C", "3");

            foreach (string name in test.Keys)
            {
                // Seaching the gloabal namespace:
                global::System.Console.WriteLine(name + " " + test[name]);
            }
        }
    }
}
复制代码
A 1
B 2
C 3

请参见

概念

C# 编程指南

参考

命名空间（C# 编程指南）
. 运算符（C# 参考）
:: 运算符（C# 参考）
extern（C# 参考）


如何：使用 My 命名空间（C# 编程指南）

[image]

C# 编程指南
如何：使用 My 命名空间（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

Microsoft.VisualBasic.MyServices 命名空间（Visual Basic 中的 My）提供对许多 .NET Framework 类的简单直观的访问，使您能够编写可与计算机、应用程序、设置、资源等交互的代码。虽然 MyServices 命名空间最初是为使用 Visual Basic 而设计的，但它也可以在 C# 应用程序中使用。

有关在 Visual Basic 中使用 MyServices 命名空间的更多信息，请参见 使用 My 开发。

添加引用

在解决方案中使用 MyServices 类之前，必须添加一个对 Visual Basic 库的引用。

添加对 Visual Basic 库的引用

  1. 在“解决方案资源管理器”中右击“引用”节点，再选择“添加引用”。

  2. 出现“引用”对话框后，向下滚动列表，选择“Microsoft.VisualBasic.dll”。

    您可能还需要在程序开头的 using 节中包括以下行。

    C# 复制代码
    using Microsoft.VisualBasic.Devices;

示例

此示例调用 MyServices 命名空间中包含的各种静态方法。要编译此代码，必须在项目中添加一个对 Microsoft.VisualBasic.DLL 的引用。

C# 复制代码
using System;
using Microsoft.VisualBasic.Devices;

class TestMyServices
{
    static void Main()
    {
        // Play a sound with the Audio class:
        Audio myAudio = new Audio();
        Console.WriteLine("Playing sound...");
        myAudio.Play(@"c:\WINDOWS\Media\chimes.wav");

        // Display time information with the Clock class:
        Clock myClock = new Clock();
        Console.Write("Current day of the week: ");
        Console.WriteLine(myClock.LocalTime.DayOfWeek);
        Console.Write("Current date and time: ");
        Console.WriteLine(myClock.LocalTime);

        // Display machine information with the Computer class:
        Computer myComputer = new Computer();
        Console.WriteLine("Computer name: " + myComputer.Name);

        if (myComputer.Network.IsAvailable)
        {
            Console.WriteLine("Computer is connected to network.");
        }
        else
        {
            Console.WriteLine("Computer is not connected to network.");
        }
    }
}

并不是 MyServices 命名空间中的所有的类都可以从 C# 应用程序调用：例如 FileSystemProxy 类就不兼容。在这种特定情况下，可以改用作为 FileSystem（它也包含在 VisualBasic.dll 中）的一部分的静态方法。例如，下面介绍了如何使用这样的方法来复制目录：

C# 复制代码
// Duplicate a directory
Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(
    @"C:\original_directory",
    @"C:\copy_of_original_directory");

请参见

概念

C# 编程指南

参考

命名空间（C# 编程指南）
使用命名空间（C# 编程指南）


可空类型（C# 编程指南）

[image]

C# 编程指南
可空类型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

可以为 null 的类型是 System..::.Nullable<(Of <(T>)>) 结构的实例。可以为 null 的类型可以表示其基础值类型正常范围内的值，再加上一个 null 值。例如，Nullable<Int32> 读作“可以为 null 的 Int32”，可以将其赋值为 -2147483648 到 2147483647 之间的任意值，也可以将其赋值为 null 值。可以赋给 Nullable<bool> 的值包括 true、false 或 null。在处理数据库和其他包含不可赋值的元素的数据类型时，将 null 赋值给数值类型或布尔型的功能特别有用。例如，数据库中的布尔型字段可以存储值 true 或 false，或者，该字段也可以未定义。

C# 复制代码
class NullableExample
{
    static void Main()
    {
        int? num = null;
        if (num.HasValue == true)
        {
            System.Console.WriteLine("num = " + num.Value);
        }
        else
        {
            System.Console.WriteLine("num = Null");
        }

        // y is set to zero
        int y = num.GetValueOrDefault();

        // num.Value throws an InvalidOperationException if num.HasValue is false
        try
        {
            y = num.Value;
        }
        catch (System.InvalidOperationException e)
        {
            System.Console.WriteLine(e.Message);
        }
    }
}

此示例将显示输出：

num = Null

Nullable object must have a value.

可以为 null 的类型概述

可以为 null 的类型具有以下特性：

  • 可以为 null 的类型表示可被赋值为 null 值的值类型变量。无法创建基于引用类型的可以为 null 的类型。（引用类型已支持 null 值。）

  • 语法 T? 是 Nullable<(Of <(T>)>) 的简写，此处的 T 为值类型。这两种形式可以互换。

  • 为可以为 null 的类型赋值的方法与为一般值类型赋值的方法相同，如 int? x = 10; 或 double? d = 4.108;。

  • 如果基础类型的值为 null，请使用 Nullable<(Of <(T>)>)..::.GetValueOrDefault 方法返回该基础类型所赋的值或默认值，例如 int j = x.GetValueOrDefault();

  • 请使用 HasValue 和 Value 只读属性测试是否为空和检索值，例如 if(x.HasValue) j = x.Value;

    • 如果此变量包含值，则 HasValue 属性返回 True；或者，如果此变量的值为空，则返回 False。

    • 如果已赋值，则 Value 属性返回该值。否则，将引发 System..::.InvalidOperationException。

    • 可空类型变量的默认值将 HasValue 设置为 false。未定义 Value。

  • 使用 ?? 运算符分配默认值，当前值为空的可以为 null 的类型被赋值给非空类型时将应用该默认值，如 int? x = null; int y = x ?? -1;。

  • 不允许使用嵌套的可以为 null 的类型。将不编译下面一行：Nullable<Nullable<int>> n;

相关章节

更多信息：

  • 使用可空类型（C# 编程指南）

  • 装箱可空类型（C# 编程指南）

  • ?? 运算符（C# 参考）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 4.1.10 种可以为 null 的类型

请参见

任务

可空示例

概念

C# 编程指南

参考

Nullable

其他资源

Visual C#
C# 参考
What exactly does 'lifted' mean?（“提升”的准确含义是什么？）


使用可空类型（C# 编程指南）

[image]

C# 编程指南
使用可空类型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

可以为 null 的类型可以表示基础类型的所有值，另外还可以表示 null 值。可以为 null 的类型可通过下面两种方式中的一种声明：

System.Nullable<T> variable

- 或 -

T? variable

T 是可以为 null 的类型的基础类型。T 可以是包括 struct 在内的任何值类型；但不能是引用类型。

有关可能使用可以为 null 的类型的示例，请考虑普通的布尔变量如何能够具有两个值：true 和 false。不存在表示“未定义”的值。在很多编程应用中（最突出的是数据库交互），变量可以以未定义的状态出现。例如，数据库中的某个字段可能包含值 true 或 false，但是它也可能根本不包含值。同样，可以将引用类型设置为 null，以指示它们未初始化。

这种不一致会导致额外的编程工作，如使用附加变量来存储状态信息、使用特殊值，等等。可以为 null 的类型修饰符使 C# 能够创建表示未定义值的值类型变量。

可以为 null 的类型示例

任何值类型都可用作可以为 null 的类型的基础。例如：

C# 复制代码
int? i = 10;
double? d1 = 3.14;
bool? flag = null;
char? letter = 'a';
int?[] arr = new int?[10];

可以为 null 的类型的成员

可以为 null 的类型的每个实例都具有两个公共的只读属性：

  • HasValue

    HasValue 属于 bool 类型。当变量包含非 null 值时，它被设置为 true。

  • Value

    Value 的类型与基础类型相同。如果 HasValue 为 true，则说明 Value 包含有意义的值。如果 HasValue 为 false，则访问 Value 将引发 InvalidOperationException。

在此示例中，HasValue 成员用于在尝试显示变量之前测试它是否包含值。

C# 复制代码
int? x = 10;
if (x.HasValue)
{
    System.Console.WriteLine(x.Value);
}
else
{
    System.Console.WriteLine("Undefined");
}

也可以如下面的示例所示对值进行测试：

C# 复制代码
int? y = 10;
if (y != null)
{
    System.Console.WriteLine(y.Value);
}
else
{
    System.Console.WriteLine("Undefined");
}

显式转换

可以为 null 的类型可强制转换为常规类型，方法是使用强制转换来显式转换或者通过使用 Value 属性来转换。例如：

C# 复制代码
int? n = null;

//int m1 = n;      // Will not compile.
int m2 = (int)n;   // Compiles, but will create an exception if x is null.
int m3 = n.Value;  // Compiles, but will create an exception if x is null.

如果两种数据类型之间定义了用户定义的转换，则同一转换也可用于这些数据类型的可以为 null 的版本。

隐式转换

可使用 null 关键字将可以为 null 的类型的变量设置为 null，如以下示例所示：

C# 复制代码
int? n1 = null;

从普通类型到可以为 null 的类型的转换是隐式的。

C# 复制代码
int? n2;
n2 = 10;  // Implicit conversion.

运算符

可以为 null 的类型还可以使用预定义的一元和二元运算符，以及现有的任何用户定义的值类型运算符。如果操作数为 null，这些运算符将产生一个 null 值；否则运算符将使用包含的值来计算结果。例如：

C# 复制代码
int? a = 10;
int? b = null;

a++;         // Increment by 1, now a is 11.
a = a * 10;  // Multiply by 10, now a is 110.
a = a + b;   // Add b, now a is null.

在执行可以为 null 的类型的比较时，如果其中一个可以为 null 的类型为 null，则比较结果将始终为 false。因此，一定不要以为由于一个比较结果为 false，相反的情况就会为 true。例如：

C# 复制代码
int? num1 = 10;
int? num2 = null;
if (num1 >= num2)
{
    System.Console.WriteLine("num1 is greater than or equal to num1");
}
else
{
    // num1 is NOT less than num2
}

else 语句中的结论无效，因为 num2 为 null，因此不包含值。

比较两个均为 null 的可以为 null 的类型时结果为 true。

??运算符

?? 运算符定义在将可以为 null 的类型分配给非可以为 null 的类型时返回的默认值。

C# 复制代码
int? c = null;

// d = c, unless c is null, in which case d = -1.
int d = c ?? -1;

此运算符还可用于多个可以为 null 的类型。例如：

C# 复制代码
int? e = null;
int? f = null;

// g = e or f, unless e and f are both null, in which case g = -1.
int g = e ?? f ?? -1;

bool? 类型

bool? 可以为 null 的类型可以包含三个不同的值：true、false 和 null。有关如何从 bool? 强制转换为 bool 的信息，请参见如何：从 bool? 安全地强制转换为 bool（C# 编程指南）。

可以为 null 的布尔值类似于 SQL 中使用的布尔变量类型。为了确保 & 和 | 运算符产生的结果与 SQL 中的三值布尔类型一致，提供了以下预定义的运算符：

bool? operator &(bool? x, bool? y)

bool? operator |(bool? x, bool? y)

下表中列出了这些运算符的结果：

X | y | x&y | x|y
true | true | true | true
true | false | false | true
true | null | null | true
false | true | false | true
false | false | false | false
false | null | false | null
null | true | null | true
null | false | false | null
null | null | null | null

请参见

概念

C# 编程指南

参考

可空类型（C# 编程指南）
装箱可空类型（C# 编程指南）


装箱可空类型（C# 编程指南）

[image]

C# 编程指南
装箱可空类型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

基于可以为 null 的类型的对象只在该对象为非空时装箱。如果 HasValue 为 false，则将对象引用赋值为 null，而不进行装箱。例如：

复制代码
bool? b = null;
object o = b;
// Now o is null.

如果对象非空，也就是说，如果 HasValue 为 true，则会发生装箱过程，但只将可以为 null 的对象所基于的基础类型装箱。如果将非空的可以为 null 的值类型装箱，将使值类型本身（而不是包装该值类型的 System..::.Nullable<(Of <(T>)>)）装箱。例如：

复制代码
bool? b = false;
int? i = 44;
object bBoxed = b; // bBoxed contains a boxed bool.
object iBoxed = i; // iBoxed contains a boxed int.

对于那些通过装箱非可以为 null 的类型而创建的类型来说，两种装箱对象是完全相同的。并且，像非可以为 null 的装箱类型一样，可以将它们取消装箱，使其成为可以为 null 的类型，如以下示例所示：

复制代码
bool? b2 = (bool?)bBoxed;
int? i2 = (int?)iBoxed;

备注

可以为 null 的类型在装箱时的行为具有两个优点：

  1. 可以测试可以为 null 的对象及其装箱的对应项是否为空：

    复制代码
      bool? b = null;
  object boxedB = b;
  if (b == null)
  {
    // True.
  }
  if (boxedB == null)
  {
    // Also true.
  }

  2. 装箱的可以为 null 的类型完全支持基础类型的功能：

    复制代码
      double? d = 44.4;
  object iBoxed = d;
  // Access IConvertible interface implemented by double.
  IConvertible ic = (IConvertible)iBoxed;
  int i = ic.ToInt32(null);
  string str = ic.ToString();

有关可以为 null 的类型和装箱行为的更多示例，请参见可空示例。

请参见

任务

如何：标识可空类型（C# 编程指南）

概念

C# 编程指南

参考

可空类型（C# 编程指南）


如何：标识可空类型（C# 编程指南）

[image]

C# 编程指南
如何：标识可空类型（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可以使用 C# typeof 运算符来创建表示可以为 null 的类型的 Type 对象：

复制代码
System.Type type = typeof(int?);

还可以使用 System.Reflection 命名空间的类和方法来生成表示可以为 null 的类型的 Type 对象。但是，如果您试图使用 GetType 方法或 is 运算符在运行时获得可以为 null 的类型变量的类型信息，得到的结果是表示基础类型而不是可以为 null 的类型本身的 Type 对象。

如果对可以为 null 的类型调用 GetType，则在该类型被隐式转换为 Object 时将执行装箱操作。因此，GetType 总是返回表示基础类型而不是可以为 null 的类型的 Type 对象。

复制代码
  int? i = 5;
  Type t = i.GetType();
  Console.WriteLine(t.FullName); //"System.Int32"

C# 的 is 运算符还可以作用于可以为 null 的的基础类型。因此，不能使用 is 来确定变量是否为可以为 null 的类型。下面的示例演示 is 运算符将 Nullable<int> 变量视为 int 变量。

复制代码
  static void Main(string[] args)
  {
    int? i = 5;
    if (i is int) // true
      //…
  }

示例

使用下面的代码来确定 Type 对象是否表示可以为 null 的类型。请记住，如果 Type 对象是通过调用 GetType 返回的，则此代码始终返回 false，如本主题中先前所述。

复制代码
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {…}

请参见

参考

可空类型（C# 编程指南）
装箱可空类型（C# 编程指南）


如何：从 bool? 安全地强制转换为 bool（C# 编程指南）

[image]

C# 语言参考
如何：从 bool? 安全地强制转换为 bool（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

bool? 可以为 null 的类型可以包含三个不同的值：true、false 和 null。因此，bool? 类型不能用于条件语句，如 if、for 或 while。例如，此代码无法编译，并将报告编译器错误 CS0266：

复制代码
bool? b = null;
if (b) // Error CS0266.
{
}

这是不允许的，因为 null 在条件上下文中的含义并不清楚。若要在条件语句中使用 bool?，请首先检查其 HasValue 属性以确保其值不是 null，然后将它强制转换为 bool。有关更多信息，请参见 bool。如果对使用 null 值的 bool? 执行强制转换，则在条件测试中将引发 InvalidOperationException。下面的示例演示了一种从 bool? 安全地强制转换为 bool 的方法：

示例

复制代码
            bool? test = null;
             ...// Other code that may or may not
                // give a value to test.
            if(!test.HasValue) //check for a value
            {
                // Assume that IsInitialized
                // returns either true or false.
                test = IsInitialized();
            }
            if((bool)test) //now this cast is safe
            {
               // Do something.
            }

请参见

概念

C# 编程指南

参考

文字关键字（C# 参考）
可空类型（C# 编程指南）


不安全代码和指针（C# 编程指南）

[image]

C# 编程指南
不安全代码和指针（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

为了保持类型安全，默认情况下，C# 不支持指针算法。不过，通过使用 unsafe 关键字，可以定义可使用指针的不安全上下文。有关指针的更多信息，请参见主题指针类型。

说明：

在公共语言运行时 (CLR) 中，不安全代码是指无法验证的代码。C# 中的不安全代码不一定是危险的；只是其安全性无法由 CLR 进行验证的代码。因此，CLR 只对在完全受信任的程序集中的不安全代码执行操作。如果使用不安全代码，由您负责确保您的代码不会引起安全风险或指针错误。有关更多信息，请参见安全性（C# 编程指南）。

不安全代码概述

不安全代码具有下列属性：

  • 方法、类型和可被定义为不安全的代码块。

  • 在某些情况下，通过移除数组界限检查，不安全代码可提高应用程序的性能。

  • 当调用需要指针的本机函数时，需要使用不安全代码。

  • 使用不安全代码将引起安全风险和稳定性风险。

  • 在 C# 中，为了编译不安全代码，必须用 /unsafe 编译应用程序。

相关章节

有关更多信息，请参见：

  • 指针类型（C# 编程指南）

  • 固定大小的缓冲区（C# 编程指南）

  • 如何：使用指针复制字节数组（C# 编程指南）

  • 如何：使用 Windows ReadFile 函数（C# 编程指南）

  • unsafe（C# 参考）

  • “不安全代码”示例

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 18 不安全代码

  • B 3 不安全代码的语法扩展

请参见

概念

C# 编程指南


固定大小的缓冲区（C# 编程指南）

[image]

C# 编程指南
固定大小的缓冲区（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 中，可以使用 fixed 语句在数据结构中创建带有固定大小数组的缓冲区。使用现有代码（如使用其他语言、预先存在的 DLL 或 COM 项目编写的代码）时，这种方法非常有用。固定数组可采用允许普通结构成员使用的任何属性或修饰符。唯一的限制是，数组类型必须是 bool、byte、 char、 short、int、long、sbyte、ushort、uint、ulong、float 或 double。

复制代码
private fixed char name[30];

备注

在早期版本的 C# 中，声明 C++ 样式的固定大小结构是很困难的，因为包含数组的 C# 结构不包含数组元素，而是包含对元素的引用。

C# 2.0 添加了在 struct（当用在 unsafe 代码块中时）中嵌入固定大小的数组的功能。

例如，在 C# 2.0 之前，下面的 struct 的大小为 8 字节，其中 pathName 数组是对堆分配的数组的引用：

C# 复制代码
    public struct MyArray
    {
        public char[] pathName;
        private int reserved;
    }

在 C# 2.0 中，struct 可使用嵌入数组进行声明：

C# 复制代码
public struct MyArray // This code must appear in an unsafe block
{
    public fixed char pathName[128];
}

在此结构中，pathName 数组具有固定的大小和位置，因此可用在其他不安全的代码中。

128 个元素的 char 数组的大小为 256 字节。在固定大小的 char 缓冲区中，每个字符始终占用两个字节，而与编码无关。即使将 char 缓冲区封送到具有 CharSet = CharSet.Auto 或 CharSet = CharSet.Ansi 的 API 方法或结构，也是如此。有关更多信息，请参见 CharSet。

另一种常见的固定大小的数组是 bool 数组。bool 数组中的元素的大小始终为一个字节。bool 数组不适合用于创建位数组或位缓冲区。

说明：

除了用 stackalloc 创建的内存之外，C# 编译器和公共语言运行时 (CLR) 不执行任何安全缓冲区溢出检查。与所有不安全代码一样，请谨慎使用。

不安全缓冲区与常规数组在以下方面不同：

  • 不安全缓冲区只能用在不安全上下文中。

  • 不安全缓冲区始终是向量（或一维数组）。

  • 数组的声明应包括计数，如 char id[8]。而不能使用 char id[]。

  • 不安全缓冲区只能是不安全上下文中的结构的实例字段。

请参见

概念

C# 编程指南

参考

不安全代码和指针（C# 编程指南）
fixed 语句（C# 参考）
互操作性（C# 编程指南）


如何：使用 Windows ReadFile 函数（C# 编程指南）

[image]

C# 编程指南
如何：使用 Windows ReadFile 函数（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的示例通过读取并显示一个文本文件来演示 Windows ReadFile 函数。ReadFile 函数需要使用 unsafe 代码，因为它需要一个作为参数的指针。

传递到 Read 函数的字节数组是托管类型。这意味着公共语言运行时 (CLR) 垃圾回收器可能会随意地对数组使用的内存进行重新定位。为了防止出现这种情况，使用 fixed 来获取指向内存的指针并对它进行标记，以便垃圾回收器不会移动它。在 fixed 块的末尾，内存将自动返回，以便能够通过垃圾回收移动。

此功能称为“声明式锁定”。使用锁定，系统开销会非常小，除非在 fixed 块中发生垃圾回收（但此情况不太可能发生）。但是，锁定会在全局垃圾回收运行过程中导致某些不利的副作用。垃圾回收器压缩内存的能力在很大程度上受到锁定缓冲区的限制。因此，应尽可能避免使用锁定。

示例

C# 复制代码
class FileReader
{
    const uint GENERIC_READ = 0x80000000;
    const uint OPEN_EXISTING = 3;
    System.IntPtr handle;

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
    static extern unsafe System.IntPtr CreateFile
    (
        string FileName,          // file name
        uint DesiredAccess,       // access mode
        uint ShareMode,           // share mode
        uint SecurityAttributes,  // Security Attributes
        uint CreationDisposition, // how to create
        uint FlagsAndAttributes,  // file attributes
        int hTemplateFile         // handle to template file
    );

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true)]
    static extern unsafe bool ReadFile
    (
        System.IntPtr hFile,      // handle to file
        void* pBuffer,            // data buffer
        int NumberOfBytesToRead,  // number of bytes to read
        int* pNumberOfBytesRead,  // number of bytes read
        int Overlapped            // overlapped buffer
    );

    [System.Runtime.InteropServices.DllImport("kernel32", SetLastError = true)]
    static extern unsafe bool CloseHandle
    (
        System.IntPtr hObject // handle to object
    );

    public bool Open(string FileName)
    {
        // open the existing file for reading
        handle = CreateFile
        (
            FileName,
            GENERIC_READ,
            0,
            0,
            OPEN_EXISTING,
            0,
            0
        );

        if (handle != System.IntPtr.Zero)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public unsafe int Read(byte[] buffer, int index, int count)
    {
        int n = 0;
        fixed (byte* p = buffer)
        {
            if (!ReadFile(handle, p + index, count, &n, 0))
            {
                return 0;
            }
        }
        return n;
    }

    public bool Close()
    {
        return CloseHandle(handle);
    }
}

class Test
{
    static int Main(string[] args)
    {
        if (args.Length != 1)
        {
            System.Console.WriteLine("Usage : ReadFile <FileName>");
            return 1;
        }

        if (!System.IO.File.Exists(args[0]))
        {
            System.Console.WriteLine("File " + args[0] + " not found.");
            return 1;
        }

        byte[] buffer = new byte[128];
        FileReader fr = new FileReader();

        if (fr.Open(args[0]))
        {
            // Assume that an ASCII file is being read.
            System.Text.ASCIIEncoding Encoding = new System.Text.ASCIIEncoding();

            int bytesRead;
            do
            {
                bytesRead = fr.Read(buffer, 0, buffer.Length);
                string content = Encoding.GetString(buffer, 0, bytesRead);
                System.Console.Write("{0}", content);
            }
            while (bytesRead > 0);

            fr.Close();
            return 0;
        }
        else
        {
            System.Console.WriteLine("Failed to open requested file");
            return 1;
        }
    }
}

请参见

概念

C# 编程指南

参考

不安全代码和指针（C# 编程指南）
指针类型（C# 编程指南）

其他资源

C# 参考
垃圾回收


指针类型（C# 编程指南）

[image]

C# 编程指南
指针类型（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在不安全的上下文中，类型可以是指针类型、值类型或引用类型。指针类型声明具有下列形式之一：

复制代码
type* identifier;
void* identifier; //allowed but not recommended

下列类型都可以是指针类型：

  • sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal 或 bool。

  • 任何枚举类型。

  • 任何指针类型。

  • 仅包含非托管类型的字段的任何用户定义的结构类型。

指针类型不继承 object，并且指针类型与 object 之间不存在转换。此外，装箱和取消装箱不支持指针。但是，允许在不同指针类型之间以及指针类型与整型之间进行转换。

当在同一个声明中声明多个指针时，* 仅与基础类型一起使用，而不是作为每个指针名称的前缀。例如：

复制代码
int* p1, p2, p3;   // Ok
int *p1, *p2, *p3;   // Invalid in C#

指针不能指向引用或包含引用的结构，因为即使有指针指向对象引用，该对象引用也无法执行垃圾回收。GC 并不注意是否有任何类型的指针指向对象。

myType* 类型的指针变量的值是 myType 类型的变量的地址。下面是指针类型声明的示例：

示例 | 说明
int* p | p 是指向整数的指针
int** p | p 是指向整数的指针的指针
int*[] p | p 是指向整数的指针的一维数组
char* p | p 是指向字符的指针
void* p | p 是指向未知类型的指针

指针间接寻址运算符 * 可用于访问位于指针变量所指向的位置的内容。例如，请考虑下面的声明：

复制代码
int* myVariable;

表达式 *myVariable 表示在 myVariable 中包含的地址处找到的 int 变量。

不能对 void* 类型的指针应用间接寻址运算符。但是，可以使用强制转换将 void 指针转换为其他指针类型，反之亦然。

指针可以为 null。如果将间接寻址运算符应用于 null 指针，则会导致由实现定义的行为。

注意，在方法之间传递指针会导致未定义的行为。示例通过 Out 或 Ref 参数或作为函数结果返回一个指向局部变量的指针。如果将指针设置在固定的块中，它所指向的变量可能不再是固定的。

下表列出可在不安全的上下文中针对指针执行的运算符和语句：

运算符/语句 | 用途
* | 执行指针间接寻址。
-> | 通过指针访问结构的成员。
[] | 为指针建立索引。
& | 获取变量的地址。
++ 和 -- | 递增和递减指针。
加、减 | 执行指针算法。
==、!=、<、>、<= 和 >= | 比较指针。
stackalloc | 在堆栈上分配内存。
fixed 语句 | 临时固定变量以便可以找到其地址。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 18 不安全代码

请参见

概念

C# 编程指南

参考

不安全代码和指针（C# 编程指南）
指针转换（C# 编程指南）
指针表达式（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）
装箱和取消装箱（C# 编程指南）

其他资源

类型（C# 参考）


指针转换（C# 编程指南）

[image]

C# 编程指南
指针转换（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下表显示了预定义的隐式指针转换。隐式转换可能在多种情形下发生，包括调用方法时和在赋值语句中。

隐式指针转换

发件人 | 到
任何指针类型 | void*
null | 任何指针类型

显式指针转换用于在不存在隐式转换时通过使用强制转换表达式来执行转换。下表显示了这些转换。

显式指针转换

发件人 | 到
任何指针类型 | 所有其他指针类型
sbyte、byte、short、ushort、int、uint、long 或 ulong | 任何指针类型
任何指针类型 | sbyte、byte、short、ushort、int、uint、long 或 ulong

示例

在下面的示例中，一个指向 int 的指针被转换为指向 byte 的指针。注意，该指针指向变量的最低地址字节。连续递增该结果直到达到 int 的大小（4 字节），即可显示变量的剩余字节。

C# 复制代码
// compile with: /unsafe
C# 复制代码
class ClassConvert
{
    static void Main()
    {
        int number = 1024;

        unsafe
        {
            // Convert to byte:
            byte* p = (byte*)&number;

            System.Console.Write("The 4 bytes of the integer:");

            // Display the 4 bytes of the int variable:
            for (int i = 0 ; i < sizeof(int) ; ++i)
            {
                System.Console.Write(" {0:X2}", *p);
                // Increment the pointer:
                p++;
            }
            System.Console.WriteLine();
            System.Console.WriteLine("The value of the integer: {0}", number);

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }
}
    /* Output:
        The 4 bytes of the integer: 00 04 00 00
        The value of the integer: 1024
    */

请参见

概念

C# 编程指南

参考

指针表达式（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


指针表达式（C# 编程指南）

[image]

C# 编程指南
指针表达式（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

本节讨论下列指针表达式：

获取变量的值

获取变量的地址

如何：通过指针访问成员（C# 编程指南）

如何：通过指针访问数组元素（C# 编程指南）

操作指针

请参见

概念

C# 编程指南

参考

指针转换（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


如何：获取指针变量的值（C# 编程指南）

[image]

C# 编程指南
如何：获取指针变量的值（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

使用指针间接运算符可获取位于指针所指向的位置的变量。表达式采用下面的形式，其中， p 是指针类型：

复制代码
*p;

不能对除指针类型以外的任何类型的表达式使用一元间接寻址运算符。此外，不能将它应用于 void 指针。

当向 null 指针应用间接寻址运算符时，结果将取决于具体的实现。

示例

下面的示例使用不同类型的指针访问 char 类型的变量。注意，theChar 的地址在不同的运行中是不同的，因为分配给变量的物理地址可能会更改。

C# 复制代码
// compile with: /unsafe
C# 复制代码
unsafe class TestClass
{
    static void Main()
    {
        char theChar = 'Z';
        char* pChar = &theChar;
        void* pVoid = pChar;
        int* pInt = (int*)pVoid;

        System.Console.WriteLine("Value of theChar = {0}", theChar);
        System.Console.WriteLine("Address of theChar = {0:X2}",(int)pChar);
        System.Console.WriteLine("Value of pChar = {0}", *pChar);
        System.Console.WriteLine("Value of pInt = {0}", *pInt);
    }
}
复制代码
Value of theChar = Z
Address of theChar = 12F718
Value of pChar = Z
Value of pInt = 90

请参见

概念

C# 编程指南

参考

指针表达式（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


如何：获取变量的地址（C# 编程指南）

[image]

C# 编程指南
如何：获取变量的地址（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

要获取计算结果为固定变量的一元表达式的地址，请使用 address-of 运算符：

复制代码
int number;
int* p = &number; //address-of operator &

address-of 运算符仅适用于变量。如果该变量是可移动变量，则在获取其地址之前，可以使用 fixed 语句暂时固定此变量。

确保初始化该变量是程序员的责任。如果变量未初始化，编译器不会发出错误消息。

不能获取常数或值的地址。

示例

此示例声明一个指向 int 的指针 p，并将整数变量 number 的地址赋值给该指针。给 *p 赋值的结果是初始化变量 number。如果对此赋值语句进行注释，则将取消对变量 number 的初始化，但是不会发出编译时错误。注意该示例如何使用成员访问运算符 -> 来获取和显示存储在指针中的地址。

C# 复制代码
// compile with: /unsafe
C# 复制代码
class AddressOfOperator
{
    static void Main()
    {
        int number;

        unsafe
        {
            // Assign the address of number to a pointer:
            int* p = &number;

            // Commenting the following statement will remove the
            // initialization of number.
            *p = 0xffff;

            // Print the value of *p:
            System.Console.WriteLine("Value at the location pointed to by p: {0:X}", *p);

            // Print the address stored in p:
            System.Console.WriteLine("The address stored in p: {0}", (int)p);
        }

        // Print the value of the variable number:
        System.Console.WriteLine("Value of the variable number: {0:X}", number);

        System.Console.ReadKey();
    }
}
/* Output:
        Value at the location pointed to by p: FFFF
        The address stored in p: 2420904
        Value of the variable number: FFFF
*/

请参见

概念

C# 编程指南

参考

指针表达式（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


如何：通过指针访问成员（C# 编程指南）

[image]

C# 编程指南
如何：通过指针访问成员（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

要访问在不安全的上下文中声明的结构的成员，您可以使用以下示例中所示的成员访问运算符，其中，p 是指向包含成员 x 的结构的指针。

复制代码
CoOrds* p = &home;
p -> x = 25; //member access operator ->

示例

此示例声明并实例化了包含两个坐标（x 和 y）的结构 CoOrds。此示例通过使用成员访问运算符 -> 和一个指向实例 home 的指针为 home 和 home 赋值。

说明：

请注意，表达式 p->x 等效于表达式 p->x，使用这两个表达式可获得相同的结果。

C# 复制代码
// compile with: /unsafe
C# 复制代码
struct CoOrds
{
    public int x;
    public int y;
}

class AccessMembers
{
    static void Main()
    {
        CoOrds home;

        unsafe
        {
            CoOrds* p = &home;
            p->x = 25;
            p->y = 12;

            System.Console.WriteLine("The coordinates are: x={0}, y={1}", p->x, p->y );
        }
    }
}

请参见

概念

C# 编程指南

参考

指针表达式（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


如何：通过指针访问数组元素（C# 编程指南）

[image]

C# 编程指南
如何：通过指针访问数组元素（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在不安全的上下文中，可通过使用指针元素访问来访问内存中的元素，如下面的示例所示：

复制代码
       char* charPointer = stackalloc char[123];
      for (int i = 65; i < 123; i++)
      {
          charPointer[i] = (char)i; //access array elements
      }

方括号中的表达式必须能够隐式转换为 int、uint、long 或 ulong。操作 p[e] 等效于 *(p+e)。与 C 和 C++ 一样，指针元素访问不检查越界错误。

示例

在此示例中，123 内存位置被分配给字符数组 charPointer。该数组用于在两个 for 循环中显示小写字母和大写字母。

请注意，表达式 charPointer[i] 等效于表达式 *(charPointer + i)，使用这两个表达式可获得相同的结果。

C# 复制代码
// compile with: /unsafe
C# 复制代码
class Pointers
{
    unsafe static void Main()
    {
        char* charPointer = stackalloc char[123];

        for (int i = 65; i < 123; i++)
        {
            charPointer[i] = (char)i;
        }

        // Print uppercase letters:
        System.Console.WriteLine("Uppercase letters:");
        for (int i = 65; i < 91; i++)
        {
            System.Console.Write(charPointer[i]);
        }
        System.Console.WriteLine();

        // Print lowercase letters:
        System.Console.WriteLine("Lowercase letters:");
        for (int i = 97; i < 123; i++)
        {
            System.Console.Write(charPointer[i]);
        }
    }
}
复制代码
Uppercase letters:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Lowercase letters:
abcdefghijklmnopqrstuvwxyz

请参见

概念

C# 编程指南

参考

指针表达式（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


操作指针（C# 编程指南）

[image]

C# 编程指南
操作指针（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

本章节包括下列指针操作：

增加和减少

算术运算

比较

请参见

概念

C# 编程指南

参考

指针表达式（C# 编程指南）
C# 运算符
指针类型（C# 编程指南）
/unsafe（启用不安全模式)（C# 编译器选项）


如何：递增和递减指针（C# 编程指南）

[image]

C# 编程指南
如何：递增和递减指针（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

使用增量和减量运算符 ++ 和 -- 可以将 pointer-type* 类型的指针的位置改变 sizeof (pointer-type)。增量和减量表达式的形式如下：

复制代码
++p;
P++;
--p;
p--;

增量和减量运算符可应用于除 void* 类型以外的任何类型的指针。

对 pointer-type 类型的指针应用增量运算符的效果是将指针变量中包含的地址增加 sizeof (pointer-type)。

对 pointer-type 类型的指针应用减量运算符的效果是从指针变量中包含的地址减去 sizeof (pointer-type)。

当运算溢出指针范围时，不会产生异常，实际结果取决于具体实现。

示例

此示例通过将指针增加 int 的大小来遍历一个数组。对于每一步，此示例都显示数组元素的地址和内容。

C# 复制代码
// compile with: /unsafe
C# 复制代码
class IncrDecr
{
    unsafe static void Main()
    {
        int[] numbers = {0,1,2,3,4};

        // Assign the array address to the pointer:
        fixed (int* p1 = numbers)
        {
            // Step through the array elements:
            for(int* p2=p1; p2<p1+numbers.Length; p2++)
            {
                System.Console.WriteLine("Value:{0} @ Address:{1}", *p2, (long)p2);
            }
        }
    }
}
复制代码
Value:0 @ Address:12860272
Value:1 @ Address:12860276
Value:2 @ Address:12860280
Value:3 @ Address:12860284
Value:4 @ Address:12860288

请参见

概念

C# 编程指南

参考

指针表达式（C# 编程指南）
C# 运算符
操作指针（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


指针的算术运算（C# 编程指南）

[image]

C# 编程指南
指针的算术运算（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本主题讨论使用算术运算符 + 和 - 来操作指针。

说明：

不能对 void 指针执行任何算术运算。

对指针执行加减数值操作

可以将类型为 int、uint、long 或 ulong 的值 n 与 void* 以外任何类型的指针 p 相加。结果 p+n 是加上 n * sizeof(p) to the address of p 得到的指针。同样，p-n 是从 p 的地址中减去 n * sizeof(p) 得到的指针。

指针相减

也可以对相同类型的指针进行减法运算。计算结果的类型始终为 long。例如，如果 p1 和 p2 都是类型为 pointer-type* 的指针，则表达式 p1-p2 的计算结果为：

((long)p1 - (long)p2)/sizeof(pointer_type)

当算术运算溢出指针范围时，不会产生异常，并且结果取决于具体实现。

示例

C# 复制代码
// compile with: /unsafe
C# 复制代码
class PointerArithmetic
{
    unsafe static void Main()
    {
        int* memory = stackalloc int[30];
        long* difference;
        int* p1 = &memory[4];
        int* p2 = &memory[10];

        difference = (long*)(p2 - p1);

        System.Console.WriteLine("The difference is: {0}", (long)difference);
    }
}
// Output:  The difference is: 6

C# 语言规范

有关更多信息，请参见 C# 语言规范 中的以下各章节：

  • 18.5.6 指针算法

请参见

概念

C# 编程指南

参考

不安全代码和指针（C# 编程指南）
指针表达式（C# 编程指南）
C# 运算符
操作指针（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


指针比较（C# 编程指南）

[image]

C# 编程指南
指针比较（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可应用下面的运算符比较任意类型的指针：

== != < > <= >=

比较运算符比较两个操作数的地址，就像他们是无符号整数一样。

示例

C# 复制代码
// compile with: /unsafe
C# 复制代码
class CompareOperators
{
    unsafe static void Main()
    {
        int x = 234;
        int y = 236;
        int* p1 = &x;
        int* p2 = &y;

        System.Console.WriteLine(p1 < p2);
        System.Console.WriteLine(p2 < p1);
    }
}

示例输出

True

False

请参见

概念

C# 编程指南

参考

指针表达式（C# 编程指南）
C# 运算符
操作指针（C# 编程指南）
指针类型（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）
stackalloc（C# 参考）

其他资源

类型（C# 参考）


如何：使用指针复制字节数组（C# 编程指南）

[image]

C# 编程指南
如何：使用指针复制字节数组（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的示例使用指针将字节从一个数组复制到另一个使用指针的数组。

此示例使用 unsafe 关键字，它允许在 Copy 方法内使用指针。fixed 语句用于声明指向源数组和目标数组的指针。这将锁定源数组和目标数组在内存中的位置，使其不会因为垃圾回收操作而移动。这些内存块将在 fixed 块结束时取消锁定。因为本示例中 Copy 函数使用了 unsafe 关键字，它必须使用 /unsafe 编译器选项进行编译。

示例

C# 复制代码
// compile with: /unsafe
C# 复制代码
class TestCopy
{
    // The unsafe keyword allows pointers to be used within the following method:
    static unsafe void Copy(byte[] src, int srcIndex, byte[] dst, int dstIndex, int count)
    {
        if (src == null || srcIndex < 0 ||
            dst == null || dstIndex < 0 || count < 0)
        {
            throw new System.ArgumentException();
        }

        int srcLen = src.Length;
        int dstLen = dst.Length;
        if (srcLen - srcIndex < count || dstLen - dstIndex < count)
        {
            throw new System.ArgumentException();
        }

        // The following fixed statement pins the location of the src and dst objects
        // in memory so that they will not be moved by garbage collection.
        fixed (byte* pSrc = src, pDst = dst)
        {
            byte* ps = pSrc;
            byte* pd = pDst;

            // Loop over the count in blocks of 4 bytes, copying an integer (4 bytes) at a time:
            for (int i = 0 ; i < count / 4 ; i++)
            {
                *((int*)pd) = *((int*)ps);
                pd += 4;
                ps += 4;
            }

            // Complete the copy by moving any bytes that weren't moved in blocks of 4:
            for (int i = 0; i < count % 4 ; i++)
            {
                *pd = *ps;
                pd++;
                ps++;
            }
        }
    }

    static void Main()
    {
        byte[] a = new byte[100];
        byte[] b = new byte[100];

        for (int i = 0; i < 100; ++i)
        {
            a[i] = (byte)i;
        }

        Copy(a, 0, b, 0, 100);
        System.Console.WriteLine("The first 10 elements are:");

        for (int i = 0; i < 10; ++i)
        {
            System.Console.Write(b[i] + " ");
        }
        System.Console.WriteLine("\n");
    }
}
/* Output:
    The first 10 elements are:
    0 1 2 3 4 5 6 7 8 9
*/

请参见

任务

“不安全代码”示例

概念

C# 编程指南

参考

不安全代码和指针（C# 编程指南）
/unsafe（启用不安全模式)（C# 编译器选项）

其他资源

垃圾回收


XML 文档注释（C# 编程指南）

[image]

C# 编程指南
XML 文档注释（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在 Visual C# 中，可以通过以下方法为代码创建文档：紧接在源代码中 XML 标记引用的代码块的前面，使用特殊的注释字段包括 XML 标记。例如：

复制代码
/// <summary>
///  This class performs an important function.
/// </summary>
public class MyClass{}

使用 /doc 进行编译时，编译器将在源代码中搜索所有的 XML 标记，并创建一个 XML 文档文件。

说明：

XML doc 注释不是元数据；它们不包括在编译的程序集中，因此无法通过反射对其进行访问。

本节内容

  • 建议的文档注释标记

  • 处理 XML 文件

  • 文档标记分隔符

  • 如何：使用 XML 文档功能

相关章节

有关更多信息，请参见：

  • /doc（处理文档注释）

  • “XML 文档”示例

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 附录 A：文档注释

请参见

概念

C# 编程指南


建议的文档注释标记（C# 编程指南）

[image]

C# 编程指南
建议的文档注释标记（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 编译器将代码中的文档注释处理到 XML 文件中。需要在您的站点实现的一项具体内容是处理 XML 文件以创建文档。

在代码构造（如类型和类型成员）上处理标记。

说明：

文档注释不能应用于命名空间。

编译器将处理任何为有效 XML 的标记。下列标记提供了用户文档中常用的功能。

标记

<c> | <para> | <see>*
<code> | <param>* | <seealso>*
<example> | <paramref> | <summary>
<exception>* | <permission>* | <typeparam>*
<include>* | <remarks> | <typeparamref>
<list> | <returns> | <value>

（* 表示编译器验证语法。）

如果希望尖括号显示在文档注释的文本中，请使用 < 和 >。例如，<尖括号中的文本>。

请参见

任务

“XML 文档”示例

概念

C# 编程指南

参考

/doc（处理文档注释）（C# 编译器选项）
XML 文档注释（C# 编程指南）


<c>（C# 编程指南）

[image]

C# 编程指南
<c>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<c>text</c>

参数

text

希望将其指示为代码的文本。

备注

<c> 标记为您提供了一种将说明中的文本标记为代码的方法。使用 <code> 将多行指示为代码。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// text for class TestClass
public class TestClass
{
    /// <summary><c>DoWork</c> is a method in the <c>TestClass</c> class.
    /// </summary>
    public static void DoWork(int Int1)
    {
    }

    /// text for Main
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<code>（C# 编程指南）

[image]

C# 编程指南
<code>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<code>content</code>

参数

content

希望将其标记为代码的文本。

备注

<code> 标记为您提供了一种将多行指示为代码的方法。使用 <c> 指示应将说明中的文本标记为代码。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

有关如何使用 <code> 标记的示例，请参见 <example> 主题。

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<example>（C# 编程指南）

[image]

C# 编程指南
<example>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<example>description</example>

参数

description

代码示例的说明。

备注

使用 <example> 标记可以指定使用方法或其他库成员的示例。这通常涉及使用 <code> 标记。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// text for class TestClass
public class TestClass
{
    /// <summary>
    /// The GetZero method.
    /// </summary>
    /// <example> This sample shows how to call the GetZero method.
    /// <code>
    /// class TestClass
    /// {
    ///     static int Main()
    ///     {
    ///         return GetZero();
    ///     }
    /// }
    /// </code>
    /// </example>
    public static int GetZero()
    {
        return 0;
    }

    /// text for Main
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<exception>（C# 编程指南）

[image]

C# 编程指南
<exception>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<exception cref="member">description</exception>

参数

cref = "member"

对可从当前编译环境中获取的异常的引用。编译器检查到给定异常存在后，将 member 转换为输出 XML 中的规范化元素名。必须将 member 括在双引号 (" ") 中。

有关如何创建对泛型类型的 cref 引用的更多信息，请参见 <see>（C# 编程指南）。

description

异常的说明。

备注

<exception> 标记使您可以指定哪些异常可被引发。此标记可用在方法、属性、事件和索引器的定义中。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// comment for class
public class EClass : System.Exception
{
    // class definition...
}

/// comment for class
class TestClass
{
    /// <exception cref="System.Exception">Thrown when...</exception>
    public void DoSomething()
    {
        try
        {
        }
        catch (EClass)
        {
        }
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<include>（C# 编程指南）

[image]

C# 编程指南
<include>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<include file='filename' path='tagpath[@name="id"]' />

参数

filename

包含文档的文件名。该文件名可用路径加以限定。将 filename 括在单引号 (' ') 中。

tagpath

filename 中指向标记 name 的标记路径。将此路径括在单引号中 (' ')。

name

注释前边的标记中的名称说明符；name 具有一个 id。

id

位于注释之前的标记的 ID。将此 ID 括在双引号中 (" ")。

备注

<include> 标记使您得以引用描述源代码中类型和成员的另一文件中的注释。这是除了将文档注释直接置于源代码文件中之外的另一种可选方法。

<include> 标记使用 XML XPath 语法。有关自定义 <include> 使用的方法，请参见 XPath 文档。

示例

以下是一个多文件示例。第一个文件使用 <include> ，如下所列：

C# 复制代码
// compile with: /doc:DocFileName.xml

/// <include file='xml_include_tag.doc' path='MyDocs/MyMembers[@name="test"]/*' />
class Test
{
    static void Main()
    {
    }
}

/// <include file='xml_include_tag.doc' path='MyDocs/MyMembers[@name="test2"]/*' />
class Test2
{
    public void Test()
    {
    }
}

第二个文件 xml_include_tag.doc 包含下列文档注释：

复制代码
<MyDocs>

<MyMembers name="test">
<summary>
The summary for this type.
</summary>
</MyMembers>

<MyMembers name="test2">
<summary>
The summary for this other type.
</summary>
</MyMembers>

</MyDocs>

程序输出

<?xml version="1.0"?>

<doc>

<assembly>

<name>xml_include_tag</name>

</assembly>

<members>

<member name="T:Test">

<summary>

The summary for this type.

</summary>

</member>

<member name="T:Test2">

<summary>

The summary for this other type.

</summary>

</member>

</members>

</doc>

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<list>（C# 编程指南）

[image]

C# 编程指南
<list>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<list type="bullet" | "number" | "table">
    <listheader>
        <term>term</term>
        <description>description</description>
    </listheader>
    <item>
        <term>term</term>
        <description>description</description>
    </item>
</list>

参数

term

要定义的项，该项将在 description 中定义。

description

项目符号列表或编号列表中的项或者 term 的定义。

备注

<listheader> 块用于定义表或定义列表中的标题行。定义表时，只需为标题中的项提供一个项。

列表中的每一项都用一个 <item> 块来描述。创建定义列表时，既需要指定 term 也需要指定 description。但是，对于表、项目符号列表或编号列表，只需为 description 提供一个项。

列表或表所拥有的 <item> 块数可以根据需要而定。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// text for class TestClass
public class TestClass
{
    /// <summary>Here is an example of a bulleted list:
    /// <list type="bullet">
    /// <item>
    /// <description>Item 1.</description>
    /// </item>
    /// <item>
    /// <description>Item 2.</description>
    /// </item>
    /// </list>
    /// </summary>
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<para>（C# 编程指南）

[image]

C# 编程指南
<para>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<para>content</para>

参数

content

段落文本。

备注

<para> 标记用于诸如 <summary>、<remarks> 或 <returns> 等标记内，使您得以将结构添加到文本中。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

有关使用 <para> 的示例，请参见 <summary>。

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<param>（C# 编程指南）

[image]

C# 编程指南
<param>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<param name='name'>description</param>

参数

name

方法参数名。将此名称用双引号括起来 (" ")。

description

参数说明。

备注

<param> 标记应当用于方法声明的注释中，以描述方法的一个参数。

有关 <param> 标记的文本将显示在 IntelliSense、对象浏览器和代码注释 Web 报表中。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// text for class TestClass
public class TestClass
{
    /// <param name="Int1">Used to indicate status.</param>
    public static void DoWork(int Int1)
    {
    }
    /// text for Main
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<paramref>（C# 编程指南）

[image]

C# 编程指南
<paramref>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<paramref name="name"/>

参数

name

要引用的参数名。将此名称用双引号括起来 (" ")。

备注

<paramref> 标记提供了指示代码注释中的某个单词（例如在 <summary> 或 <remarks> 块中）引用某个参数的方式。可以处理 XML 文件来以不同的方式格式化此单词，比如将其设置为粗体或斜体。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// text for class TestClass
public class TestClass
{
    /// <summary>DoWork is a method in the TestClass class.
    /// The <paramref name="Int1"/> parameter takes a number.
    /// </summary>
    public static void DoWork(int Int1)
    {
    }

    /// text for Main
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<permission>（C# 编程指南）

[image]

C# 编程指南
<permission>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<permission cref="member">description</permission>

参数

cref = "member"

对可以通过当前编译环境进行调用的成员或字段的引用。编译器检查到给定代码元素存在后，将 member 转换为输出 XML 中的规范化元素名。必须将 member 括在双引号 (" ") 中。

有关如何创建对泛型类型的 cref 引用的信息，请参见 <see>（C# 编程指南）。

description

对成员的访问的说明。

备注

<permission> 标记使您得以将成员的访问记入文档。使用 PermissionSet 类可以指定对成员的访问。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

class TestClass
{
    /// <permission cref="System.Security.PermissionSet">Everyone can access this method.</permission>
    public static void Test()
    {
    }

    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<remarks>（C# 编程指南）

[image]

C# 编程指南
<remarks>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<remarks>description</remarks>

参数

Description

成员的说明。

备注

<remarks> 标记用于添加有关某个类型的信息，从而补充由 <summary> 所指定的信息。此信息显示在 对象浏览器 中。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// <summary>
/// You may have some primary information about this class.
/// </summary>
/// <remarks>
/// You may have some additional information about this class.
/// </remarks>
public class TestClass
{
    /// text for Main
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<returns>（C# 编程指南）

[image]

C# 编程指南
<returns>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<returns>description</returns>

参数

description

返回值的说明。

备注

<returns> 标记应当用于方法声明的注释，以描述返回值。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// text for class TestClass
public class TestClass
{
    /// <returns>Returns zero.</returns>
    public static int GetZero()
    {
        return 0;
    }

    /// text for Main
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<see>（C# 编程指南）

[image]

C# 编程指南
<see>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<see cref="member"/>

参数

cref = "member"

对可以通过当前编译环境进行调用的成员或字段的引用。编译器检查给定的代码元素是否存在，并将 member 传递给输出 XML 中的元素名称。应将 member 放在双引号 (" ") 中。

备注

<see> 标记使您得以从文本内指定链接。使用 <seealso> 指示文本应该放在“另请参见”节中。

使用 /doc 进行编译可以将文档注释处理到文件中。

有关使用 <see> 的示例，请参见 <summary>。

示例

下面的示例演示如何对泛型类型进行 cref 引用。

C# 复制代码
// compile with: /doc:DocFileName.xml

// the following cref shows how to specify the reference, such that,
// the compiler will resolve the reference
/// <summary cref="C{T}">
/// </summary>
class A { }

// the following cref shows another way to specify the reference,
// such that, the compiler will resolve the reference
// <summary cref="C &lt; T &gt;">

// the following cref shows how to hard-code the reference
/// <summary cref="T:C`1">
/// </summary>
class B { }

/// <summary cref="A">
/// </summary>
/// <typeparam name="T"></typeparam>
class C<T> { }

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<seealso>（C# 编程指南）

[image]

C# 编程指南
<seealso>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<seealso cref="member"/>

参数

cref = "member"

对可以通过当前编译环境进行调用的成员或字段的引用。编译器检查到给定代码元素存在后，将 member 传递给输出 XML 中的元素名。必须将 member 括在双引号 (" ") 中。

有关如何创建对泛型类型的 cref 引用的信息，请参见 <see>（C# 编程指南）。

备注

<seealso> 标记使您得以指定希望在“请参见”一节中出现的文本。使用 <see> 从文本内指定链接。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

有关使用 <seealso> 的示例，请参见 <summary>。

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<summary>（C# 编程指南）

[image]

C# 编程指南
<summary>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<summary>description</summary>

参数

description

对象的摘要。

备注

</summary> 标记应当用于描述类型或类型成员。使用 <remarks> 添加针对某个类型说明的补充信息。

</summary> 标记的文本是唯一有关 IntelliSense 中的类型的信息源，它也显示在 对象浏览器 中。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// text for class TestClass
public class TestClass
{
    /// <summary>DoWork is a method in the TestClass class.
    /// <para>Here's how you could make a second paragraph in a description. <see cref="System.Console.WriteLine(System.String)"/> for information about output statements.</para>
    /// <seealso cref="TestClass.Main"/>
    /// </summary>
    public static void DoWork(int Int1)
    {
    }

    /// text for Main
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<typeparam>（C# 编程指南）

[image]

C# 编程指南
<typeparam>（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

<typeparam name="name">description</typeparam>

参数

name

类型参数的名称。将此名称用双引号括起来 (" ")。

description

类型参数的说明。

备注

在泛型类型或方法声明的注释中应该使用 <typeparam> 标记描述类型参数。为泛型类型或方法的每个类型参数添加标记。

有关更多信息，请参见 泛型（C# 编程指南）。

<typeparam> 标记的文本将显示在 对象浏览器代码注释 Web 报表的 IntelliSense 中。

使用 /doc 进行编译可以将文档注释处理到文件中。

有关显示如何使用 <typeparam> 的代码示例，请参见 <typeparamref>（C# 编程指南）。

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）

其他资源

C# 参考


<typeparamref>（C# 编程指南）

[image]

C# 编程指南
<typeparamref>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<typeparamref name="name"/>

参数

name

类型参数的名称。将此名称用双引号括起来 (" ")。

备注

有关泛型类型和方法中的类型参数的更多信息，请参见泛型。

使用此标记，文档文件的使用者能够以某种独特的方式设置单词的格式，例如以斜体显示。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// comment for class
public class TestClass
{
    /// <summary>
    /// Creates a new array of arbitrary type <typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">The element type of the array</typeparam>
    public static T[] mkArray<T>(int n)
    {
        return new T[n];
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


<value>（C# 编程指南）

[image]

C# 编程指南
<value>（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

<value>property-description</value>

参数

property-description

属性的说明。

备注

<value> 标记使您得以描述属性所代表的值。请注意，当在 Visual Studio .NET 开发环境中通过代码向导添加属性时，它将会为新属性添加 <summary> 标记。然后，应该手动添加 <value> 标记以描述该属性所表示的值。

使用 /doc 进行编译可以将文档注释处理到文件中。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// text for class Employee
public class Employee
{
    private string _name;

    /// <summary>The Name property represents the employee's name.</summary>
    /// <value>The Name property gets/sets the _name data member.</value>
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
        }
    }
}

/// text for class MainClass
public class MainClass
{
    /// text for Main
    static void Main()
    {
    }
}

请参见

概念

C# 编程指南

参考

建议的文档注释标记（C# 编程指南）


处理 XML 文件（C# 编程指南）

[image]

C# 编程指南
处理 XML 文件（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

编译器为代码中被标记为生成文档的每一个构造生成一个 ID 字符串。（有关如何标记代码的信息，请参见建议的文档注释标记。）ID 字符串唯一地标识构造。处理 XML 文件的程序可以使用 ID 字符串标识文档应用于的相应 .NET Framework 元数据/反射项。

XML 文件不是代码的分层表示形式，它是一个平面列表，其中的每一个元素都有一个生成的 ID。

编译器在生成 ID 字符串时遵循下列规则：

  • 字符串中没有空白。

  • ID 字符串的第一部分通过单个字符后跟一个冒号来标识所标识成员的种类。使用下列成员类型：

    字符

    说明

    N

    命名空间

    不可将文档注释添加到命名空间中，但是可以对它们进行 cref 引用（在受支持的位置）。

    T

    类型：类、接口、结构、枚举、委托

    F

    字段

    P

    属性（包括索引程序或其他索引属性）

    M

    方法（包括一些特殊方法，例如构造函数、运算符等）

    E

    事件

    !

    错误字符串

    字符串的其余部分会提供有关此错误的信息。C# 编译器为无法解析的链接生成错误信息。

  • 字符串的第二部分是项的完全限定名，从命名空间的根开始。项的名称、其封闭类型和命名空间以句号分隔。如果项的名称本身包含句号，则用哈希符号 ('#') 替换这些句号。假定任何项的名称中都不直接存在哈希符号。例如，String 构造函数的完全限定名是“System.String.#ctor”。

  • 对于属性和方法，如果该方法带参数，则将其后的参数列表括在括号中。如果没有参数，则没有括号。多个参数以逗号分隔。每个参数的编码都直接遵循它在 .NET Framework 签名中的编码方法：

    • 基类型。常规类型（ELEMENT_TYPE_CLASS 或 ELEMENT_TYPE_VALUETYPE）表示为类型的完全限定名。

    • 内部类型（例如 ELEMENT_TYPE_I4、ELEMENT_TYPE_OBJECT、ELEMENT_TYPE_STRING、ELEMENT_TYPE_TYPEDBYREF 和 ELEMENT_TYPE_VOID）表示为相应完全类型的完全限定名。例如，System.Int32 或 System.TypedReference。

    • ELEMENT_TYPE_PTR 表示为“*”，在修改的类型之后。

    • ELEMENT_TYPE_BYREF 表示为“@”，在修改的类型之后。

    • ELEMENT_TYPE_PINNED 表示为“^”，在修改的类型之后。C# 编译器永远不会生成此结果。

    • ELEMENT_TYPE_CMOD_REQ 表示为“|”和修饰符类的完全限定名，在修改的类型之后。C# 编译器永远不会生成此结果。

    • ELEMENT_TYPE_CMOD_OPT 表示为“!”和修饰符类的完全限定名，在修改的类型之后。

    • ELEMENT_TYPE_SZARRAY 表示为“[]”，在数组的元素类型之后。

    • ELEMENT_TYPE_GENERICARRAY 表示为“[?]”，在数组的元素类型之后。C# 编译器永远不会生成此结果。

    • ELEMENT_TYPE_ARRAY 表示为 [lowerbound:size,lowerbound:size]，其中逗号数为秩 - 1，每一维的下限和大小（如果已知）用十进制表示。如果未指定下限及大小，它将完全被省略。如果省略了特定维的下限及大小，则“:”也将被省略。例如，以 1 作为下限并且未指定大小的二维数组是 [1:,1:]。

    • ELEMENT_TYPE_FNPTR 表示为“=FUNC:type(signature)”，其中 type 是返回类型，signature 是方法的参数。如果没有参数，则将省略括号。C# 编译器永远不会生成此结果。

    不表示下列签名组件，因为从不使用它们来区分重载方法：

    • 调用约定

    • 返回类型

    • ELEMENT_TYPE_SENTINEL

  • 仅对于转换运算符（op_Implicit 和 op_Explicit），方法的返回值才被编码为“~”，后跟返回类型，如上述编码所示。

  • 对于泛型类型，类型名称后跟反勾号，再跟一个数字，指示泛型类型参数的个数。例如，

    <member name="T:SampleClass`2"> 是定义为 <member name="T:SampleClass`2"> 的类型的标记。

    对于接受泛型类型作为参数的方法，泛型类型参数被指定为数字前面加上反勾号（如 `0、`1）。表示类型泛型参数的数组表示法（从零开始）的每个数字。

示例

下列示例显示了如何生成类及其成员的 ID 字符串：

C# 复制代码
///
///
namespace N  // "N:N"
{
    ///
    ///
    public unsafe class X    // "T:N.X"
    {
        public X(){}
        //----------------------------
        // The result of the above is:
        // "M:N.X.#ctor"

        /// <param name="i"></param>
        public X(int i){}
        //----------------------------
        // The result of the above is:
        // "M:N.X.#ctor(System.Int32)"

        ~X(){}
        //----------------------------
        // The result of the above is:
        // "M:N.X.Finalize", destructor's representation in metadata

        public string q;
        //----------------------------
        // The result of the above is:
        // "F:N.X.q"

        /// <returns></returns>
        public const double PI = 3.14;
        //----------------------------
        // The result of the above is:
        // "F:N.X.PI"

        /// <param name="s"></param>
        /// <param name="y"></param>
        /// <param name="z"></param>
        /// <returns></returns>
        public int f(){return 1;}
        //----------------------------
        // The result of the above is:
        // "M:N.X.f"

        /// <param name="array1"></param>
        /// <param name="array"></param>
        /// <returns></returns>
        public int bb(string s, ref int y, void * z){return 1;}
        //----------------------------
        // The result of the above is:
        // "M:N.X.bb(System.String,System.Int32@,=System.Void*)"

        /// <param name="x"></param>
        /// <param name="xx"></param>
        /// <returns></returns>
        public int gg(short[] array1, int[,] array){return 0;}
        //----------------------------
        // The result of the above is:
        // "M:N.X.gg(System.Int16[], System.Int32[0:,0:])"

        public static X operator+(X x, X xx){return x;}
        //----------------------------
        // The result of the above is:
        // "M:N.X.op_Addition(N.X,N.X)"

        public int prop {get{return 1;} set{}}
        //----------------------------
        // The result of the above is:
        // "P:N.X.prop"

        public event D d;
        //----------------------------
        // The result of the above is:
        // "E:N.X.d"

        public int this[string s]{get{return 1;}}
        //----------------------------
        // The result of the above is:
        // "P:N.X.Item(System.String)"

        public class Nested{}
        //----------------------------
        // The result of the above is:
        // "T:N.X.Nested"

        public delegate void D(int i);
        //----------------------------
        // The result of the above is:
        // "T:N.X.D"

        /// <param name="x"></param>
        /// <returns></returns>
        public static explicit operator int(X x){return 1;}
        //----------------------------
        // The result of the above is:
        // "M:N.X.op_Explicit(N.X)~System.Int32"
    }
}

请参见

任务

“XML 文档”示例

概念

C# 编程指南

参考

/doc（处理文档注释）（C# 编译器选项）
XML 文档注释（C# 编程指南）


文档标记的分隔符（C# 编程指南）

[image]

C# 编程指南
文档标记的分隔符（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

使用 XML doc 注释需要分隔符，分隔符为编译器指示文档注释的起止位置。XML 文档标记可以与以下几种分隔符一起使用：

///

这是显示在文档示例中并由 Visual C# 项目模板使用的形式。

说明：

Visual Studio IDE 具有一项称为“智能注释编辑”的功能，它自动插入

和
标记，并且当您在代码编辑器中键入 /// 分隔符后将光标移动到这些标记内。从项目属性页的 “选项”对话框 ->“文本编辑器”->“C#”->“格式设置” 可以访问此功能。

/** */

多行分隔符。

使用 /** */ 分隔符时有一些格式设置规则：

  • 对于包含 /** 分隔符的行，如果该行的其余部分为空白，则不将该行处理为注释。如果第一个字符为空白，则忽略此空白字符并处理该行的剩余部分。否则，将 /** 分隔符后的整行文本处理为注释的一部分。

  • 对于包含 */ 分隔符的行，如果到 */ 分隔符为止仅有空白，则忽略该行。否则，将到 */ 分隔符为止的那行文本处理为注释的一部分，同时必须遵循以下描述的模式匹配规则。

  • 对于以 /** 分隔符开头的行后面的那些行，编译器会在每个行的开头寻找一个公共模式，这些行由可选的空白和星号 (*) 以及后面更多可选的空白组成。如果编译器在每行的开头均找到了一组公共的字符，则它将对 /** 分隔符到包含 */ 分隔符的行（可能包括此行）之间的所有行忽略该模式。

示例：

  • 以下注释中将被处理的唯一部分是以 <summary> 开头的行。下列两个标记格式将产生相同的注释：

    /**

    <summary>text</summary>

    */

    /** <summary>text</summary> */

  • 编译器在第二行和第三行的开头应用“*”模式进行忽略。

    /**

    * <summary>

    * text </summary>*/

  • 编译器在此注释中找不到任何模式，因为第二行没有星号。因而，第二行和第三行直到 */ 的所有文本都将被处理为注释的一部分。

    /**

    * <summary>

    text </summary>*/

  • 编译器之所以在此注释中没找到任何模式有两个原因。首先，行首星号前的空格数目不一致。其次，第五行是以 Tab 开头的，它并不等效于空格。因而，从第二行到 */ 的所有文本都将被处理为注释的一部分。

    /**

    * <summary>

    * text

    * text2

    * </summary>

    */

请参见

任务

“XML 文档”示例

概念

C# 编程指南

参考

XML 文档注释（C# 编程指南）
/doc（处理文档注释）（C# 编译器选项）
XML 文档注释（C# 编程指南）


如何：使用 XML 文档功能（C# 编程指南）

[image]

C# 编程指南
如何：使用 XML 文档功能（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的示例提供对某个已存档的类型的基本概述。

示例

C# 复制代码
// compile with: /doc:DocFileName.xml

/// <summary>
/// Class level summary documentation goes here.</summary>
/// <remarks>
/// Longer comments can be associated with a type or member
/// through the remarks tag</remarks>
public class TestClass
{
    /// <summary>
    /// Store for the name property</summary>
    private string _name = null;

    /// <summary>
    /// The class constructor. </summary>
    public TestClass()
    {
        // TODO: Add Constructor Logic here
    }

    /// <summary>
    /// Name property </summary>
    /// <value>
    /// A value tag is used to describe the property value</value>
    public string Name
    {
        get
        {
            if (_name == null)
            {
                throw new System.Exception("Name is null");
            }
            return _name;
        }
    }

    /// <summary>
    /// Description for SomeMethod.</summary>
    /// <param name="s"> Parameter description for s goes here</param>
    /// <seealso cref="System.String">
    /// You can use the cref attribute on any tag to reference a type or member
    /// and the compiler will check that the reference exists. </seealso>
    public void SomeMethod(string s)
    {
    }

    /// <summary>
    /// Some other method. </summary>
    /// <returns>
    /// Return results are described through the returns tag.</returns>
    /// <seealso cref="SomeMethod(string)">
    /// Notice the use of the cref attribute to reference a specific method </seealso>
    public int SomeOtherMethod()
    {
        return 0;
    }

    /// <summary>
    /// The entry point for the application.
    /// </summary>
    /// <param name="args"> A list of command line arguments</param>
    static int Main(System.String[] args)
    {
        // TODO: Add code to start application here
        return 0;
    }
}
复制代码
// This .xml file was generated with the previous code sample.
<?xml version="1.0"?>
<doc>
    <assembly>
        <name>xmlsample</name>
    </assembly>
    <members>
        <member name="T:SomeClass">
            <summary>
            Class level summary documentation goes here.</summary>
            <remarks>
            Longer comments can be associated with a type or member
            through the remarks tag</remarks>
        </member>
        <member name="F:SomeClass.m_Name">
            <summary>
            Store for the name property</summary>
        </member>
        <member name="M:SomeClass.#ctor">
            <summary>The class constructor.</summary>
        </member>
        <member name="M:SomeClass.SomeMethod(System.String)">
            <summary>
            Description for SomeMethod.</summary>
            <param name="s"> Parameter description for s goes here</param>
            <seealso cref="T:System.String">
            You can use the cref attribute on any tag to reference a type or member
            and the compiler will check that the reference exists. </seealso>
        </member>
        <member name="M:SomeClass.SomeOtherMethod">
            <summary>
            Some other method. </summary>
            <returns>
            Return results are described through the returns tag.</returns>
            <seealso cref="M:SomeClass.SomeMethod(System.String)">
            Notice the use of the cref attribute to reference a specific method </seealso>
        </member>
        <member name="M:SomeClass.Main(System.String[])">
            <summary>
            The entry point for the application.
            </summary>
            <param name="args"> A list of command line arguments</param>
        </member>
        <member name="P:SomeClass.Name">
            <summary>
            Name property </summary>
            <value>
            A value tag is used to describe the property value</value>
        </member>
    </members>
</doc>

编译代码

若要编译该示例，请键入以下命令行：

csc XMLsample.cs /doc:XMLsample.xml

这将创建 XML 文件 XMLsample.xml，您可以在浏览器中或使用 TYPE 命令查看该文件。

可靠编程

XML 文档以 /// 开头。创建新项目时，向导将为您放入一些起始 /// 行。对这些注释的处理有一些限制：

  • 文档必须是格式良好的 XML。如果 XML 格式不良，将生成警告，并且文档文件将包含一条注释，指出遇到错误。

  • 开发人员可自由创建自己的标记集。有一套建议的标记（请参见“其他阅读材料”部分）。某些建议的标记具有特殊含义：

    • <param> 标记用于描述参数。如果使用，编译器将验证参数是否存在，以及文档中是否描述了所有参数。如果验证失败，则编译器发出警告。

    • cref 属性可以附加到任意标记，以提供对代码元素的引用。编译器将验证该代码元素是否存在。如果验证失败，则编译器发出警告。编译器在查找 cref 属性中描述的类型时，会考虑所有的 using 语句。

    • <summary> 标记由 Visual Studio 内的“Intellisense”使用，用来显示类型或成员的其他相关信息。

      说明：

      XML 文件并不提供有关类型和成员的完整信息（例如，它不包含任何类型信息）。若要获得有关类型或成员的完整信息，文档文件必须与实际类型或成员上的反射一起使用。

请参见

任务

“XML 文档”示例

概念

C# 编程指南

参考

/doc（处理文档注释）（C# 编译器选项）
XML 文档注释（C# 编程指南）


应用程序域（C# 编程指南）

[image]

C# 编程指南
应用程序域（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

应用程序域为隔离正在运行的应用程序提供了一种灵活而安全的方法。

应用程序域通常由运行时宿主创建和操作。有时，您可能希望应用程序以编程方式与应用程序域交互，例如想在不停止应用程序运行的情况下卸载某个组件时。

应用程序域使应用程序以及应用程序的数据彼此分离，有助于提高安全性。单个进程可以运行多个应用程序域，并具有在单独进程中所存在的隔离级别。在单个进程中运行多个应用程序提高了服务器伸缩性。

下面的代码示例创建一个新的应用程序域，然后加载并执行以前生成的程序集 HelloWorld.exe，该程序集存储在驱动器 C 上。

C# 复制代码
static void Main()
{
    // Create an Application Domain:
    System.AppDomain newDomain = System.AppDomain.CreateDomain("NewApplicationDomain");

    // Load and execute an assembly:
    newDomain.ExecuteAssembly(@"c:\HelloWorld.exe");

    // Unload the application domain:
    System.AppDomain.Unload(newDomain);
}

应用程序域概述

应用程序域具有以下特点：

  • 必须先将程序集加载到应用程序域中，然后才能执行该程序集。有关更多信息，请参见 程序集和全局程序集缓存（C# 编程指南）。

  • 一个应用程序域中的错误不会影响在另一个应用程序域中运行的其他代码。

  • 能够在不停止整个进程的情况下停止单个应用程序并卸载代码。不能卸载单独的程序集或类型，只能卸载整个应用程序域。

相关章节

  • 应用程序域概述

  • 应用程序域

  • 应用程序域和程序集

  • 对应用程序域进行编程

  • 使用应用程序域和程序集编程

  • 在另一个应用程序域中执行代码（C# 编程指南）

  • 如何：创建和使用应用程序域（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范 中的以下各章节：

  • 3.1 应用程序启动

请参见

概念

C# 编程指南

参考

程序集和全局程序集缓存（C# 编程指南）


在另一个应用程序域中执行代码（C# 编程指南）

[image]

C# 编程指南
在另一个应用程序域中执行代码（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

一旦将程序集加载到应用程序域中，就可以执行该程序集所包含的代码。最简单的加载方法是使用 AssemblyLoad，它会将程序集加载到当前应用程序域中，并从程序集的默认入口点开始运行代码。

如果希望将该程序集加载到另外一个应用程序域中，可以使用 ExecuteAssembly 或 ExecuteAssemblyByName，或者使用这些方法的其他重载版本之一。

如果想从默认入口点以外的位置执行另一个程序集，可在远程程序集中定义一个从 MarshalByRefObject 派生的新类型。然后在应用程序中使用 CreateInstance 创建该类型的一个实例。

请考虑下面的文件，它创建一个包含一个命名空间和两个类的程序集。假设此程序集已经生成，并以 HelloWorldRemote.exe 为名存储在驱动器 C 上。

C# 复制代码
// This namespace contains code to be called.
namespace HelloWorldRemote
{
    public class RemoteObject : System.MarshalByRefObject
    {
        public RemoteObject()
        {
            System.Console.WriteLine("Hello, World! (RemoteObject Constructor)");
        }
    }
    class Program
    {
        static void Main()
        {
            System.Console.WriteLine("Hello, World! (Main method)");
        }
    }
}

为了从其他应用程序访问该代码，可以将该程序集加载到当前应用程序域中，或创建新的应用程序域并将该程序集加载到其中。如果使用 Assembly.LoadFrom 将程序集加载到当前应用程序域中，您可以使用 Assembly.CreateInstance 来实例化 RemoteObject 类的实例，这样将导致执行对象构造函数。

C# 复制代码
static void Main()
{
    // Load the assembly into the current appdomain:
    System.Reflection.Assembly newAssembly = System.Reflection.Assembly.LoadFrom(@"c:\HelloWorldRemote.exe");

    // Instantiate RemoteObject:
    newAssembly.CreateInstance("HelloWorldRemote.RemoteObject");
}

将程序集加载到一个单独的应用程序域时，应使用 AppDomain.ExecuteAssembly 来访问默认入口点，或使用 AppDomain.CreateInstance 创建 RemoteObject 类的实例。创建该实例将导致执行构造函数。

C# 复制代码
static void Main()
{
    System.AppDomain NewAppDomain = System.AppDomain.CreateDomain("NewApplicationDomain");

    // Load the assembly and call the default entry point:
    NewAppDomain.ExecuteAssembly(@"c:\HelloWorldRemote.exe");

    // Create an instance of RemoteObject:
    NewAppDomain.CreateInstanceFrom(@"c:\HelloWorldRemote.exe", "HelloWorldRemote.RemoteObject");
}

如果不想以编程方式加载程序集，可以从“解决方案资源管理器”中使用“添加引用”来指定程序集 HelloWorldRemote.exe。然后向应用程序的 using 块中添加一个 using HelloWorldRemote; 指令，并在程序中使用 RemoteObject 类型来声明 RemoteObject 对象的一个实例，如下所示：

C# 复制代码
static void Main()
{
    // This code creates an instance of RemoteObject, assuming HelloWorldRemote has been added as a reference:
    HelloWorldRemote.RemoteObject o = new HelloWorldRemote.RemoteObject();
}

请参见

概念

C# 编程指南
应用程序域概述
应用程序域和程序集
对应用程序域进行编程

参考

应用程序域（C# 编程指南）

其他资源

应用程序域
使用应用程序域和程序集编程


如何：创建和使用应用程序域（C# 编程指南）

[image]

C# 编程指南
如何：创建和使用应用程序域（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

出于安全和性能目的，应用程序域提供了隔离代码的方法。有关更多信息，请参见 应用程序域（C# 编程指南）。

使用应用程序域

  1. 创建应用程序域。公共语言运行时宿主自动为应用程序创建一个应用程序域。有关信息，请参见主题 如何：创建应用程序域。

  2. 配置应用程序域。有关更多信息，请参见 如何：配置应用程序域。

  3. 将程序集加载到应用程序域中。有关更多信息，请参见 如何：将程序集加载到应用程序域中。

  4. 访问其他程序集的内容。有关更多信息，请参见 在另一个应用程序域中执行代码（C# 编程指南）。

  5. 卸载应用程序域。有关更多信息，请参见 如何：卸载应用程序域。

请参见

概念

C# 编程指南
应用程序域概述
应用程序域和程序集
对应用程序域进行编程

参考

应用程序域（C# 编程指南）

其他资源

应用程序域
使用应用程序域和程序集编程


程序集和全局程序集缓存（C# 编程指南）

[image]

C# 编程指南
程序集和全局程序集缓存（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

程序集是任何 .NET Framework 应用程序的基本构造块。例如，在生成简单的 C# 应用程序时，Visual Studio 创建一个单个可移植可执行 (PE) 文件形式的程序集，明确地说就是一个 EXE 或 DLL。

程序集包含描述它们自己的内部版本号和它们包含的所有数据和对象类型的详细信息的元数据。有关更多信息，请参见程序集清单。

程序集仅在需要时才加载。如果不使用程序集，则不会加载。这意味着程序集可能是在大型项目中管理资源的有效途径。

程序集可以包含一个或多个模块。例如，计划较大的项目时，可以让几个各个开发人员负责单独的模块，并通过组合所有这些模块来创建单个程序集。有关模块的更多信息，请参见主题如何：生成多文件程序集。

程序集概述

程序集具有以下特点：

  • 程序集作为 .exe 或 .dll 文件实现。

  • 通过将程序集放在全局程序集缓存中，可在多个应用程序之间共享程序集。

  • 要将程序集包含在全局程序集缓存中，必须对程序集进行强命名。有关更多信息，请参见具有强名称的程序集。

  • 程序集仅在需要时才加载到内存中。

  • 可以使用反射以编程方式获取关于程序集的信息。有关更多信息，请参见主题反射。

  • 如果加载程序集的目的只是对其进行检查，应使用诸如 ReflectionOnlyLoadFrom 的方法。

  • 可以在单个应用程序中使用相同程序集的两个版本。有关更多信息，请参见 extern 别名。

相关章节

更多信息：

  • 友元程序集（C# 编程指南）

  • 如何：与其他应用程序共享程序集（C# 编程指南）

  • 如何：加载和卸载程序集（C# 编程指南）

  • 如何：确定文件是否为程序集（C# 编程指南）

  • extern（C# 参考）

  • 公共语言运行库中的程序集

  • 程序集概述

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.2 程序结构

  • 9.1 编译单元

请参见

概念

C# 编程指南
元数据和 PE 文件结构

参考

应用程序域（C# 编程指南）


友元程序集（C# 编程指南）

[image]

C# 编程指南
友元程序集（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可以从一个程序集访问另一个程序集中的内部类型或内部成员。

备注

友元程序集功能用于访问内部成员；私有类型和私有成员仍然不可访问。

若要使程序集（程序集 B）能够访问另一个程序集（程序集 A）的内部类型和成员，应使用程序集 A 中的 InternalsVisibleToAttribute 属性。

说明：

在对将要访问另一个程序集（程序集 A）的内部类型或内部成员的程序集（程序集 B）进行编译时，必须用 /out 编译器选项显式指定输出文件的名称（.exe 或 .dll）（有关更多信息，请参见 /out）。这是必需的，因为当编译器将生成的程序集绑定到外部引用时，尚未为该程序集生成名称。

StrongNameIdentityPermission 类还提供共享类型的功能，其与友元程序集的区别如下：

  • StrongNameIdentityPermission 应用于单个类型，而友元程序集应用于整个程序集。

  • 如果希望程序集 B 能够共享程序集 A 中的数百个类型，则必须使用 StrongNameIdentityPermission 修饰所有这些类型；而使用友元程序集时只需声明一次友元关系。

  • 使用 StrongNameIdentityPermission 时，想要共享的类型必须声明为公共类型。使用友元程序集时，会将共享类型声明为内部类型。

  • 有关如何生成可访问程序集中的非公共类型的 .netmodule 的信息，请参见 /moduleassemblyname。

示例

在此示例中，程序集使内部类型和内部成员对名为 called cs_friend_assemblies_2 的程序集可见。

复制代码
// cs_friend_assemblies.cs
// compile with: /target:library
using System.Runtime.CompilerServices;
using System;

[assembly:InternalsVisibleTo("cs_friend_assemblies_2")]

// internal by default
class Class1
{
    public void Test()
    {
        Console.WriteLine("Class1.Test");
    }
}

// public type with internal member
public class Class2
{
    internal void Test()
    {
        Console.WriteLine("Class2.Test");
    }
}

在此示例中，程序集使用程序集 cs_friend_assemblies.dll 中的内部类型和内部成员。

注意，必须显式指定输出文件的名称 (/out:cs_friend_assemblies_2.exe)。

如果此程序集允许另一个程序集（程序集 C）访问它的内部类型和成员，则程序集 C 不会自动变成程序集 cs_friend_assemblies.dll 的友元。

复制代码
// cs_friend_assemblies_2.cs
// compile with: /reference:cs_friend_assemblies.dll /out:cs_friend_assemblies_2.exe
public class M
{
    static void Main()
    {
        // access an internal type
        Class1 a = new Class1();
        a.Test();

        Class2 b = new Class2();
        // access an internal member of a public type
        b.Test();
    }
}
复制代码
Class1.Test
Class2.Test

此示例演示了如何使内部类型和成员对具有强名称的程序集可用。

若要生成 keyfile 并显示公钥，请使用下面的 sn.exe 命令序列（有关更多信息，请参见强名称工具 (Sn.exe)）：

  • sn -k friend_assemblies.snk // 生成强名称密钥

  • sn -p friend_assemblies.snk key.publickey // 将公钥从 key.snk 提取到 key.publickey 中

  • sn -tp key.publickey // 显示存储在文件 key.publickey 中的公钥

用 /keyfile 将 keyfile 传递到编译器。

复制代码
// cs_friend_assemblies_3.cs
// compile with: /target:library /keyfile:friend_assemblies.snk
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("cs_friend_assemblies_4, PublicKey=0024000004800000940000000602000000240000525341310004000001000100031d7b6f3abc16c7de526fd67ec2926fe68ed2f9901afbc5f1b6b428bf6cd9086021a0b38b76bc340dc6ab27b65e4a593fa0e60689ac98dd71a12248ca025751d135df7b98c5f9d09172f7b62dabdd302b2a1ae688731ff3fc7a6ab9e8cf39fb73c60667e1b071ef7da5838dc009ae0119a9cbff2c581fc0f2d966b77114b2c4")]
class Class1
{
    public void Test()
    {
        System.Console.WriteLine("Class1.Test");
    }
}

此示例演示如何使用对具有强名称的程序集可用的内部类型和成员。

复制代码
// cs_friend_assemblies_4.cs
// compile with: /keyfile:friend_assemblies.snk /reference:cs_friend_assemblies_3.dll /out:cs_friend_assemblies_4.exe
public class M
{
    static void Main()
    {
        Class1 a = new Class1();
        a.Test();
    }
}
复制代码
Class1.Test

请参见

概念

C# 编程指南

参考

程序集和全局程序集缓存（C# 编程指南）


如何：确定文件是否为程序集（C# 编程指南）

[image]

C# 编程指南
如何：确定文件是否为程序集（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

当且仅当一个文件是托管文件并且在其元数据中包含程序集入口时，该文件才是一个程序集。有关程序集和元数据的更多信息，请参见主题 程序集清单。

如何手动确定一个文件是否为程序集

  1. 启动 MSIL 反汇编程序 (Ildasm.exe)。

  2. 加载希望测试的文件。

  3. 如果 ILDASM 报告该文件不是可移植的可执行 (PE) 文件，则它不是程序集。有关更多信息，请参见主题 如何：查看程序集内容。

如何以编程方式确定一个文件是否为程序集

  1. 调用 GetAssemblyName 方法，并向其传递正在测试的文件的完整文件路径和名称。

  2. 如果引发 BadImageFormatException 异常，则该文件不是程序集。

示例

此示例测试一个 DLL 以确定它是否为程序集。

C# 复制代码
class TestAssembly
{
    static void Main()
    {

        try
        {
            System.Reflection.AssemblyName testAssembly =
                System.Reflection.AssemblyName.GetAssemblyName(@"C:\Windows\Microsoft.NET\Framework\v3.5\System.Net.dll");

            System.Console.WriteLine("Yes, the file is an Assembly.");
        }

        catch (System.IO.FileNotFoundException)
        {
            System.Console.WriteLine("The file cannot be found.");
        }

        catch (System.BadImageFormatException)
        {
            System.Console.WriteLine("The file is not an Assembly.");
        }

        catch (System.IO.FileLoadException)
        {
            System.Console.WriteLine("The Assembly has already been loaded.");
        }
    }
}
/* Output (with .NET Framework 3.5 installed):
    Yes, the file is an Assembly.
*/

GetAssemblyName 方法加载测试文件，然后在读取信息之后释放它。

请参见

任务

关于异常的疑难解答：System.BadImageFormatException

概念

C# 编程指南

参考

程序集和全局程序集缓存（C# 编程指南）
AssemblyName


如何：加载和卸载程序集（C# 编程指南）

[image]

C# 编程指南
如何：加载和卸载程序集（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

程序引用的程序集将在生成时自动加载，不过也可以在运行时将特定的程序集加载到当前应用程序域中。有关更多信息，请参见应用程序域。

没有办法卸载单独的程序集而不卸载包含它的所有应用程序域。即使程序集已在范围之外，实际的程序集文件仍然保持被加载，直至包含它的所有应用程序域都被卸载。

如果想要卸载某些程序集而不卸载其他程序集，可考虑创建新的应用程序域，在该域中执行代码，然后卸载该应用程序域。有关更多信息，请参见如何：在另一个应用程序域中执行代码。

将程序集加载到应用程序域中

  • 使用 AppDomain 和 System.Reflection 类中包含的几个加载方法之一。有关更多信息，请参见将程序集加载到应用程序域中。

卸载应用程序域

  • 没有办法卸载单独的程序集而不卸载包含它的所有应用程序域。使用 AppDomain 中的 Unload 方法可卸载应用程序域。有关更多信息，请参见卸载应用程序域。

请参见

任务

如何：将程序集加载到应用程序域中

概念

C# 编程指南

参考

程序集和全局程序集缓存（C# 编程指南）
应用程序域（C# 编程指南）


如何：与其他应用程序共享程序集（C# 编程指南）

[image]

C# 编程指南
如何：与其他应用程序共享程序集（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

程序集可以是私有的也可以是共享的：默认情况下，大多数简单的 C# 程序都包含一个私有程序集，原因是不打算将该程序集供其他应用程序使用。

为了与其他应用程序共享程序集，必须将该程序集置于 全局程序集缓存 (GAC) 中。

共享程序集

  1. 创建程序集。有关更多信息，请参见 创建程序集。

  2. 为程序集指定一个强名称。有关更多信息，请参见如何：使用强名称为程序集签名。

  3. 为程序集指定版本信息。有关更多信息，请参见程序集版本控制。

  4. 将您的程序集添加到全局程序集缓存中。有关更多信息，请参见 如何：将程序集安装到全局程序集缓存。

  5. 从其他应用程序中访问该程序集包含的类型。有关更多信息，请参见 如何：引用具有强名称的程序集。

请参见

概念

C# 编程指南
具有强名称的程序集

其他资源

使用程序集编程
使用程序集和全局程序集缓存
创建和使用具有强名称的程序集


属性（C# 编程指南）

[image]

C# 编程指南
属性（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

属性提供功能强大的方法以将声明信息与 C# 代码（类型、方法、属性等）相关联。属性与程序实体关联后，即可在运行时使用名为“反射”的技术查询属性。有关更多信息，请参见反射（C# 编程指南）。

属性以两种形式出现：

  • 一种是在公共语言运行时 (CLR) 中定义的属性。

  • 另一种是可以创建的用于向代码中添加附加信息的自定义属性。此信息可在以后以编程方式检索。

在本例中，属性 TypeAttributes..::.Serializable 用来将特定特性应用于类：

C# 复制代码
[System.Serializable]
public class SampleClass
{
    // Objects of this type can be serialized.
}

属性概述

属性具有以下特点：

  • 属性可向程序中添加元数据。元数据是有关在程序中定义的类型的信息。所有的 .NET 程序集都包含指定的一组元数据，这些元数据描述在程序集中定义的类型和类型成员。可以添加自定义属性，以指定所需的任何附加信息。

  • 程序可以使用反射检查自己的元数据或其他程序内的元数据。有关更多信息，请参见使用反射访问属性（C# 编程指南）。

  • MarshallAsare 等属性在 COM 互操作方案中得到了广泛使用。有关更多信息，请参见 System.Runtime.InteropServices..::.MarshalAsAttribute 和 System.Runtime.InteropServices..::.StructLayoutAttribute。

相关章节

有关更多信息，请参见：

  • 使用属性（C# 编程指南）

  • 创建自定义属性（C# 编程指南）

  • 消除属性目标的歧义性（C# 编程指南）

  • 使用反射访问属性（C# 编程指南）

  • 如何：使用属性创建 C/C++ 联合（C# 编程指南）

  • 通用属性（C# 编程指南）

  • “属性”示例

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各节：

  • 10.2.1 属性

  • 17 属性

请参见

概念

C# 编程指南
属性 (Attribute) 概述
特性的常见用途

参考

反射（C# 编程指南）

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 更新了概述部分。 | 内容 Bug 修复


使用属性（C# 编程指南）

[image]

C# 编程指南
使用属性（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

属性可以放置在几乎所有的声明中（但特定的属性可能限制在其上有效的声明类型）。在语法上，属性的指定方法为：将括在方括号中的属性名置于其适用的实体声明之前。例如，具有 DllImport 属性的方法将声明如下：

C# 复制代码
[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();

有关更多信息，请参见 DllImportAttribute 类。

许多属性都有参数，而这些参数可以是定位（未命名）参数也可以是命名参数。任何定位参数都必须按特定顺序指定并且不能省略，而命名参数是可选的且可以按任意顺序指定。首先指定定位参数。例如，这三个属性是等效的：

复制代码
[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]

第一个参数（DLL 名称）是定位参数并且总是第一个出现，其他参数为命名参数。在这种情况下，两个命名参数均默认为 false，因此可将其省略。有关默认参数值的信息，请参考各个属性的文档。

在一个声明中可以放置多个属性，可分开放置，也可放在同一组括号中：

C# 复制代码
void MethodA([In][Out] ref double x) { }
void MethodB([Out][In] ref double x) { }
void MethodC([In, Out] ref double x) { }

某些属性对于给定实体可以指定多次。例如，Conditional 就是一个可多次使用的属性：

C# 复制代码
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
    // ...
}
说明：

根据约定，所有属性名称都以单词“Attribute”结束，以便将它们与“.NET Framework”中的其他项区分。但是，在代码中使用属性时不需要指定属性后缀。例如，[DllImport] 虽等效于 [DllImportAttribute]，但 DllImportAttribute 才是该属性在 .NET Framework 中的实际名称。

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
属性（C# 编程指南）
消除属性目标的歧义性（C# 编程指南）
创建自定义属性（C# 编程指南）
使用反射访问属性（C# 编程指南）
Attribute
System.Reflection


创建自定义属性（C# 编程指南）

[image]

C# 编程指南
创建自定义属性（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

通过定义一个属性类，可以创建您自己的自定义属性。该属性类直接或间接地从 Attribute 派生，有助于方便快捷地在元数据中标识属性定义。假设您要用编写类或结构的程序员的名字标记类和结构。可以定义一个自定义 Author 属性类：

C# 复制代码
[System.AttributeUsage(System.AttributeTargets.Class |
                       System.AttributeTargets.Struct)
]
public class Author : System.Attribute
{
    private string name;
    public double version;

    public Author(string name)
    {
        this.name = name;
        version = 1.0;
    }
}

类名是属性名 Author。它由 System.Attribute 派生而来，因此是自定义属性类。构造函数的参数是自定义属性的定位参数（在本例中为 name），任何公共读写字段或属性都是命名参数（在本例中，version 是唯一的命名参数）。请注意 AttributeUsage 属性的用法，它使得 Author 属性仅在 class 和 struct 声明中有效。

可以按如下所示使用此新属性：

C# 复制代码
[Author("H. Ackerman", version = 1.1)]
class SampleClass
{
    // H. Ackerman's code goes here...
}

AttributeUsage 有一个命名参数 AllowMultiple，使用它可以使自定义属性成为一次性使用或可以使用多次的属性。

C# 复制代码
[System.AttributeUsage(System.AttributeTargets.Class |
                       System.AttributeTargets.Struct,
                       AllowMultiple = true)  // multiuse attribute
]
public class Author : System.Attribute
C# 复制代码
[Author("H. Ackerman", version = 1.1)]
[Author("M. Knott", version = 1.2)]
class SampleClass
{
    // H. Ackerman's code goes here...
    // M. Knott's code goes here...
}
说明：

如果属性 (Attribute) 类包含一个属性 (Property)，则该属性 (Property) 必须为读写属性。在 C# 的属性 (Attribute) 类中只写属性 (Property) 不受支持。

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
属性（C# 编程指南）
使用属性（C# 编程指南）
消除属性目标的歧义性（C# 编程指南）
使用反射访问属性（C# 编程指南）
System.Reflection

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 添加了有关属性支持的说明。 | 内容 Bug 修复


消除属性目标的歧义性（C# 编程指南）

[image]

C# 编程指南
消除属性目标的歧义性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在某些情况下，属性的目标（即属性适用于的实体）显得不明确。例如，在以下方法声明中，SomeAttr 属性可以适用于方法或方法的返回值：

C# 复制代码
public class SomeAttr : System.Attribute { }

[SomeAttr]
int Method()
{
    return 0;
}

这种情况在封送处理时经常出现。为解析多义性，C# 对于每种声明都有一组默认目标，而通过显式指定属性目标可重写这些目标。

C# 复制代码
// default: applies to method
[SomeAttr]
int Method1() { return 0; }

// applies to method
[method: SomeAttr]
int Method2() { return 0; }

// applies to return value
[return: SomeAttr]
int Method3() { return 0; }

请注意，这与 SomeAttr 被定义为有效的目标无关；也就是说，即使 SomeAttr 被定义为只适用于返回值，也必须指定 return 目标。换言之，编译器将不使用 AttributeUsage 信息解析不明确的属性目标。有关更多信息，请参见 AttributeUsage（C# 编程指南）。

属性目标的语法如下：

[target : attribute-list]

参数

target

以下之一：程序集、字段、事件、方法、模块、参数、属性、返回值、类型。

attribute-list

适用属性的列表。

下表列出了允许属性的所有声明。对于每个声明，声明中属性的可能目标在第二列中列出。以粗体显示的目标是默认值。

声明 | 可能的目标
程序集 | assembly
模块 | module
类 | type
结构 | type
接口 | type
枚举 | type
委托 | type、return
方法 | method、return
参数 | param
字段 | field
属性 — 索引器 | property
属性 — get 访问器 | method、return
属性 — set 访问器 | method、param、return
事件 — 字段 | event、field、method
事件 — 属性 | event、property
事件 — 添加 | method、param
事件 — 移除 | method、param

程序集级属性和模块级属性没有默认目标。有关更多信息，请参见全局属性。

示例

C# 复制代码
using System.Runtime.InteropServices;
C# 复制代码
[Guid("12345678-1234-1234-1234-123456789abc"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ISampleInterface
{
    [DispId(17)]  // set the DISPID of the method
    [return: MarshalAs(UnmanagedType.Interface)]  // set the marshaling on the return type
    object DoWork();
}

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
属性（C# 编程指南）
使用属性（C# 编程指南）
创建自定义属性（C# 编程指南）
使用反射访问属性（C# 编程指南）
System.Reflection
Attribute


使用反射访问属性（C# 编程指南）

[image]

C# 编程指南
使用反射访问属性（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

如果没有检索自定义属性的信息和对其进行操作的方法，则定义自定义属性并将其放置在源代码中就没有意义。C# 具有一个反射系统，可用来检索用自定义属性定义的信息。主要方法是 GetCustomAttributes，它返回对象数组，这些对象在运行时等效于源代码属性。此方法具有多个重载版本。有关更多信息，请参见 Attribute。

属性规范，如：

C# 复制代码
[Author("H. Ackerman", version = 1.1)]
class SampleClass

在概念上等效于：

C# 复制代码
Author anonymousAuthorObject = new Author("H. Ackerman");
anonymousAuthorObject.version = 1.1;

但是，直到查询 SampleClass 以获取属性时才会执行此代码。对 SampleClass 调用 GetCustomAttributes 会导致按上述方式构造并初始化一个 Author 对象。如果类还有其他属性，则其他属性对象的以类似方式构造。然后 GetCustomAttributes 返回 Author 对象和数组中的任何其他属性对象。之后就可以对此数组进行迭代，确定根据每个数组元素的类型所应用的属性，并从属性对象中提取信息。

示例

下面是一个完整的示例。定义一个自定义属性，将其应用于若干实体并通过反射进行检索。

C# 复制代码
[System.AttributeUsage(System.AttributeTargets.Class |
                       System.AttributeTargets.Struct,
                       AllowMultiple = true)  // multiuse attribute
]
public class Author : System.Attribute
{
    string name;
    public double version;

    public Author(string name)
    {
        this.name = name;
        version = 1.0;  // Default value
    }

    public string GetName()
    {
        return name;
    }
}

[Author("H. Ackerman")]
private class FirstClass
{
    // ...
}

// No Author attribute
private class SecondClass
{
    // ...
}

[Author("H. Ackerman"), Author("M. Knott", version = 2.0)]
private class ThirdClass
{
    // ...
}

class TestAuthorAttribute
{
    static void Main()
    {
        PrintAuthorInfo(typeof(FirstClass));
        PrintAuthorInfo(typeof(SecondClass));
        PrintAuthorInfo(typeof(ThirdClass));
    }

    private static void PrintAuthorInfo(System.Type t)
    {
        System.Console.WriteLine("Author information for {0}", t);
        System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t);  // reflection

        foreach (System.Attribute attr in attrs)
        {
            if (attr is Author)
            {
                Author a = (Author)attr;
                System.Console.WriteLine("   {0}, version {1:f}", a.GetName(), a.version);
            }
        }
    }
}
/* Output:
    Author information for FirstClass
       H. Ackerman, version 1.00
    Author information for SecondClass
    Author information for ThirdClass
       M. Knott, version 2.00
       H. Ackerman, version 1.00
*/

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
属性（C# 编程指南）
使用属性（C# 编程指南）
消除属性目标的歧义性（C# 编程指南）
创建自定义属性（C# 编程指南）
System.Reflection
Attribute


如何：使用属性创建 C/C++ 联合（C# 编程指南）

[image]

C# 编程指南
如何：使用属性创建 C/C++ 联合（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

通过使用属性可以自定义结构在内存中的布局方式。例如，可以使用 StructLayout(LayoutKind.Explicit) 和 FieldOffset 属性创建在 C/C++ 中称为联合的布局。

示例

在上一个代码段中，TestUnion 的所有字段都从内存中的同一位置开始。

C# 复制代码
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestUnion
{
    [System.Runtime.InteropServices.FieldOffset(0)]
    public int i;

    [System.Runtime.InteropServices.FieldOffset(0)]
    public double d;

    [System.Runtime.InteropServices.FieldOffset(0)]
    public char c;

    [System.Runtime.InteropServices.FieldOffset(0)]
    public byte b;
}

以下是字段从其他显式设置的位置开始的另一个示例。

C# 复制代码
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
    [System.Runtime.InteropServices.FieldOffset(0)]
    public long lg;

    [System.Runtime.InteropServices.FieldOffset(0)]
    public int i1;

    [System.Runtime.InteropServices.FieldOffset(4)]
    public int i2;

    [System.Runtime.InteropServices.FieldOffset(8)]
    public double d;

    [System.Runtime.InteropServices.FieldOffset(12)]
    public char c;

    [System.Runtime.InteropServices.FieldOffset(14)]
    public byte b;
}

i1 和 i2 这两个 int 字段共享与 lg 相同的内存位置。使用平台调用时，这种结构布局控制很有用。

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
属性（C# 编程指南）
使用属性（C# 编程指南）
消除属性目标的歧义性（C# 编程指南）
创建自定义属性（C# 编程指南）
使用反射访问属性（C# 编程指南）
System.Reflection
Attribute


通用属性（C# 编程指南）

[image]

C# 编程指南
通用属性（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

本节描述 C# 程序中最常用的属性。

本节内容

  • Conditional（C# 编程指南）

  • Obsolete（C# 编程指南）

  • 全局属性（C# 编程指南）

  • AttributeUsage（C# 编程指南）

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
使用属性（C# 编程指南）
使用反射访问属性（C# 编程指南）
System.Reflection
Attribute


Conditional（C# 编程指南）

[image]

C# 编程指南
Conditional（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

根据预处理标识符执行方法。Conditional 属性是 ConditionalAttribute 的别名，可应用于方法或属性类。

在本示例中，Conditional 应用于方法以启用或禁用程序特定的诊断信息的显示：

复制代码
#define TRACE_ON
using System;
using System.Diagnostics;

public class Trace
{
    [Conditional("TRACE_ON")]
    public static void Msg(string msg)
    {
        Console.WriteLine(msg);
    }
}

public class ProgramClass
{
    static void Main()
    {
        Trace.Msg("Now in Main...");
        Console.WriteLine("Done.");
    }
}

如果未定义 TRACE_ON 标识符，则将不会显示跟踪输出。

Conditional 属性经常与 DEBUG 标识符一起使用以启用调试版本的跟踪和日志记录功能（在发行版本中没有这两种功能），如下例所示：

复制代码
[Conditional("DEBUG")]
static void DebugMethod()
{
}

备注

当调用标记为条件的方法时，指定的预处理符号的存在或不存在决定是否包含或省略此调用。如果定义了该符号，则包含调用；否则省略调用。使用 Conditional 是封闭 #if 和 #endif 内部方法的替代方法，它更整洁、更别致、减少了出错的机会，如下例所示：

复制代码
#if DEBUG
void ConditionalMethod()
{
}
#endif

条件方法必须是类或结构声明中的方法，而且必须具有 void 返回类型。

使用多个标识符

如果某个方法具有多个 Conditional 属性，且至少定义了多个条件符号（换言之，这些符号彼此之间存在逻辑“或”关系）中的一个，则将包含对该方法的调用。在本例中，A 或 B 的存在将导致方法调用：

C# 复制代码
[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
    // ...
}

若要获得对符号进行逻辑“与”运算的效果，可以定义序列条件方法。例如，仅当 A 和 B 均已定义时，才能执行下面的第二种方法：

C# 复制代码
[Conditional("A")]
static void DoIfA()
{
    DoIfAandB();
}

[Conditional("B")]
static void DoIfAandB()
{
    // Code to execute when both A and B are defined...
}

使用具有属性类的条件

Conditional 属性还可被应用于属性类定义。在本例中，仅当定义了 DEBUG 时，自定义属性 Documentation 才向元数据添加信息。

C# 复制代码
[Conditional("DEBUG")]
public class Documentation : System.Attribute
{
    string text;

    public Documentation(string text)
    {
        this.text = text;
    }
}

class SampleClass
{
    // This attribute will only be included if DEBUG is defined.
    [Documentation("This method displays an integer.")]
    static void DoWork(int i)
    {
        System.Console.WriteLine(i.ToString());
    }
}

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
属性（C# 编程指南）
消除属性目标的歧义性（C# 编程指南）
创建自定义属性（C# 编程指南）
使用反射访问属性（C# 编程指南）
System.Reflection
Attribute


Obsolete（C# 编程指南）

[image]

C# 编程指南
Obsolete（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

Obsolete 属性将某个程序实体标记为一个建议不再使用的实体。每次使用被标记为已过时的实体时，随后将生成警告或错误，这取决于属性是如何配置的。例如：

复制代码
[System.Obsolete("use class B")]
class A
{
    public void Method() { }
}
class B
{
    [System.Obsolete("use NewMethod", true)]
    public void OldMethod()  { }
    public void NewMethod()  { }
}

在此例中，Obsolete 属性应用于类 A 和方法 B.OldMethod。因为应用于 B.OldMethod 的属性构造函数的第二个参数设置为 true，所以此方法将导致编译器错误，而使用类 A 只会产生警告。但是，调用 B.NewMethod 既不产生警告也不产生错误。

向属性构造函数提供的作为第一个参数的字符串将显示为警告或错误的一部分。例如，当将它与前面的定义一起使用时，下面的代码将生成两个警告和一个错误：

复制代码
// Generates 2 warnings:
A a = new A();
// Generate no errors or warnings:
B b = new B();
b.NewMethod();
// Generates an error, terminating compilation:
b.OldMethod();

为类 A 产生两个警告：一个用于声明类引用，一个用于类构造函数。

可在不使用参数的情况下使用 Obsolete 属性，但要包括此项已过时的原因及改用什么项的建议。

Obsolete 属性是一个单用途属性，并且可应用于允许属性的任何实体。Obsolete 是 ObsoleteAttribute 的别名。

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
属性（C# 编程指南）
消除属性目标的歧义性（C# 编程指南）
创建自定义属性（C# 编程指南）
使用反射访问属性（C# 编程指南）
Attribute
System.Reflection


全局属性（C# 编程指南）

[image]

C# 编程指南
全局属性（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

大多数属性适用于特定的语言元素，如类或方法；但是有些属性是全局的，它们适用于整个程序集或模块。例如，AssemblyVersionAttribute 属性可用于向程序集中嵌入版本信息，如下例所示：

复制代码
[assembly: AssemblyVersion("1.0.0.0")]

全局属性在源代码中出现在任何顶级 using 指令之后以及任何类型或命名空间声明之前。全局属性可显示在多个源文件中，但这些文件必须在单一编译传递中编译。

以下是一些常用的 .NET Framework 程序集级的属性：

AssemblyCompanyAttribute

AssemblyConfigurationAttribute

AssemblyCopyrightAttribute

AssemblyCultureAttribute

AssemblyDescriptionAttribute

AssemblyProductAttribute

AssemblyTitleAttribute

AssemblyTrademarkAttribute

这些属性用于基于 Visual Studio Windows 窗体应用程序模板 的项目中。此模板包含一个名为 AssemblyInfo.cs 的文件，该文件包括这些属性实例化：

复制代码
[assembly: AssemblyTitle("WindowsApplication1")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("WindowsApplication1")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2005")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
说明：

如果不是正在创建程序集，则将忽略程序集级属性。

程序集签名属性

在 Visual Studio 的早期版本中，使用这些程序集级属性执行具有强名称的签名程序集

  • AssemblyKeyFileAttribute

  • AssemblyKeyNameAttribute

  • AssemblyDelaySignAttribute

现在仍可支持这样做，但是签名程序集的首选方法是使用项目设计器中的签名页。有关更多信息，请参见 “项目设计器”->“签名”页 和 如何：对程序集进行签名 (Visual Studio)。

请参见

概念

C# 编程指南

参考

System.Reflection
属性（C# 编程指南）
创建自定义属性（C# 编程指南）
使用反射访问属性（C# 编程指南）

其他资源

通用属性（C# 编程指南）


AttributeUsage（C# 编程指南）

[image]

C# 编程指南
AttributeUsage（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

确定可以如何使用自定义属性类。AttributeUsage 是一个可应用于自定义属性定义的属性，自定义属性定义来控制如何应用新属性。显式应用的默认设置与此类似：

复制代码
[System.AttributeUsage(System.AttributeTargets.All,
                   AllowMultiple=false,
                   Inherited=true)]
class NewAttribute : System.Attribute { }

在本例中，NewAttribute 类可应用于任何支持属性的代码实体，但是对每个实体只可应用一次。当应用于基类时，它可由派生类继承。

AllowMultiple 和 Inherited 参数是可选的，所以此代码具有相同的效果：

复制代码
[System.AttributeUsage(System.AttributeTargets.All)]
class NewAttribute : System.Attribute { }

第一个 AttributeUsage 参数必须是 AttributeTargets 枚举的一个或多个元素。多个目标类型可同时进行“或”运算，如下例所示：

复制代码
using System;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }

如果 AllowMultiple 参数设置为 true，则返回属性可对单个实体应用多次，如下例所示：

复制代码
using System;
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]
class MultiUseAttr : Attribute { }

[MultiUseAttr][MultiUseAttr]
class Class1 { }

[MultiUseAttr, MultiUseAttr]
class Class2 { }

在这种情况下，由于 AllowMultiple 设置为 true，MultiUseAttr 可反复应用。所示的应用多个属性的这两种格式均有效。

如果 Inherited 设置为 false，则该属性不由从属性化的类派生的类继承。例如：

复制代码
using System;
[AttributeUsage(AttributeTargets.Class, Inherited=false)]
class Attr1 : Attribute { }

[Attr1]
class BClass { }

class DClass : BClass { }

在这种情况下，Attr1 不通过继承应用于 DClass。

备注

AttributeUsage 属性是一个单用途属性 — 它无法对相同的类应用多次。AttributeUsage 是 AttributeUsageAttribute 的别名。

有关更多信息，请参见 使用反射访问属性（C# 编程指南）。

示例

下面的示例将阐释 Inherited 参数和 AllowMultiple 参数对 AttributeUsage 属性的效果，以及如何才能枚举应用于类的自定义属性。

复制代码
using System;

// Create some custom attributes:
[AttributeUsage(System.AttributeTargets.Class, Inherited=false)]
class A1 : System.Attribute { }

[AttributeUsage(System.AttributeTargets.Class)]
class A2 : System.Attribute { }

[AttributeUsage(System.AttributeTargets.Class, AllowMultiple=true)]
class A3 : System.Attribute { }

// Apply custom attributes to classes:
[A1,A2]
class BaseClass { }

[A3,A3]
class DerivedClass : BaseClass { }

public class TestAttributeUsage
{
    static void Main()
    {
        BaseClass b = new BaseClass();
        DerivedClass d = new DerivedClass();

        // Display custom attributes for each class.
        Console.WriteLine("Attributes on Base Class:");
        object[] attrs = b.GetType().GetCustomAttributes(true);
        foreach (Attribute attr in attrs)
        {
            Console.WriteLine(attr);
        }

        Console.WriteLine("Attributes on Derived Class:");
        attrs = d.GetType().GetCustomAttributes(true);
        foreach (Attribute attr in attrs)
        {
            Console.WriteLine(attr);
        }
    }
}

示例输出

复制代码
Attributes on Base Class:
A1
A2
Attributes on Derived Class:
A3
A3
A2

请参见

概念

C# 编程指南

参考

反射（C# 编程指南）
属性（C# 编程指南）
使用属性（C# 编程指南）
消除属性目标的歧义性（C# 编程指南）
创建自定义属性（C# 编程指南）
使用反射访问属性（C# 编程指南）
Attribute
System.Reflection


集合类（C# 编程指南）

[image]

C# 编程指南
集合类（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

.NET Framework 提供了用于数据存储和检索的专用类。这些类提供对堆栈、队列、列表和哈希表的支持。大多数集合类实现相同的接口，可继承这些接口来创建适应更为专业的数据存储需要的新集合类。

说明：

针对 .NET Framework 的 2.0 版和更高版本的应用程序应当使用 System.Collections.Generic 命名空间中的泛型集合类，与对应的非泛型类相比，这些类提供了更高的类型安全性和效率。

C# 复制代码
ArrayList list = new ArrayList();
list.Add(10);
list.Add(20);

集合类概述

集合类具有以下特点：

  • 集合类定义为 System.Collections 或 System.Collections.Generic 命名空间的一部分。

  • 大多数集合类都派生自 ICollection、IComparer、IEnumerable、IList、IDictionary 和 IDictionaryEnumerator 接口以及它们的等效泛型接口。

  • 使用泛型集合类可以提供更高的类型安全性，在某些情况下还可以提供更好的性能，尤其是在存储值类型时，这些优势会体现得更明显。有关更多信息，请参见泛型的优点（C# 编程指南）。

相关章节

  • 何时使用泛型集合

  • 集合和数据结构

  • 选择集合类

  • 集合内的比较和排序

  • 创建和操作集合

  • 如何：使用 foreach 访问集合类（C# 编程指南）

  • “集合类”示例

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）

其他资源

Collections Best Practices（有关集合的最佳做法）


如何：使用 foreach 访问集合类（C# 编程指南）

[image]

C# 编程指南
如何：使用 foreach 访问集合类（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的代码示例阐释如何编写可与 foreach 一起使用的非泛型集合类。该类是字符串标记化拆分器，类似于 C 运行时函数 strtok_s。

说明：

此示例描述的是仅当您无法使用泛型集合类时才采用的推荐做法。C# 语言和 .NET Framework 的 2.0 版和更高版本支持泛型。有关如何实现支持 IEnumerable<(Of <(T>)>) 的类型安全泛型集合类并因而避免本主题中稍后讨论的问题的示例，请参见如何：为泛型列表创建迭代器块（C# 编程指南）。

在下面的示例中，Tokens 使用“ ”和“-”作为分隔符将句子“This is a sample sentence.”拆分为标记，并使用 foreach 语句枚举这些标记：

C# 复制代码
Tokens f = new Tokens("This is a sample sentence.", new char[] {' ','-'});

foreach (string item in f)
{
    System.Console.WriteLine(item);
}

Tokens 在内部使用一个数组，该数组自行实现 IEnumerator 和 IEnumerable。该代码示例本可以利用数组本身的枚举方法，但那样会使本示例的用意无法体现出来。

在 C# 中，集合类不一定要从 IEnumerable 和 IEnumerator 继承以便与 foreach 兼容。只要此类具有必需的 GetEnumerator、MoveNext、Reset 和 Current 成员，就可 foreach 与一起使用。省略接口有一个好处：您可以将 Current 的返回类型定义得比 Object 更为明确，从而提供类型安全。

例如，从本主题中先前的示例代码开始，更改以下几行：

复制代码
// No longer inherits from IEnumerable:
public class Tokens
// Doesn't return an IEnumerator:
public TokenEnumerator GetEnumerator()
// No longer inherits from IEnumerator:
public class TokenEnumerator
// Type-safe: returns string, not object:
public string Current

现在，由于 Current 返回字符串，因此当 foreach 语句中使用了不兼容的类型时，编译器便能够检测到：

复制代码
// Error: cannot convert string to int:
foreach (int item in f)

省略 IEnumerable 和 IEnumerator 的缺点是，集合类不再能够与其他公共语言运行时兼容语言的 foreach 语句或等效项交互操作。

您可以同时拥有两者的优点，即 C# 中的类型安全以及与其他公共语言运行时兼容语言的互操作性，方法是从 IEnumerable 和 IEnumerator 继承并使用显式接口实现，如下面的示例所示。

示例

C# 复制代码
using System.Collections;

// Declare the Tokens class:
public class Tokens : IEnumerable
{
    private string[] elements;

    Tokens(string source, char[] delimiters)
    {
        // Parse the string into tokens:
        elements = source.Split(delimiters);
    }

    // IEnumerable Interface Implementation:
    //   Declaration of the GetEnumerator() method
    //   required by IEnumerable
    public IEnumerator GetEnumerator()
    {
        return new TokenEnumerator(this);
    }

    // Inner class implements IEnumerator interface:
    private class TokenEnumerator : IEnumerator
    {
        private int position = -1;
        private Tokens t;

        public TokenEnumerator(Tokens t)
        {
            this.t = t;
        }

        // Declare the MoveNext method required by IEnumerator:
        public bool MoveNext()
        {
            if (position < t.elements.Length - 1)
            {
                position++;
                return true;
            }
            else
            {
                return false;
            }
        }

        // Declare the Reset method required by IEnumerator:
        public void Reset()
        {
            position = -1;
        }

        // Declare the Current property required by IEnumerator:
        public object Current
        {
            get
            {
                return t.elements[position];
            }
        }
    }

    // Test Tokens, TokenEnumerator
    static void Main()
    {
        // Testing Tokens by breaking the string into tokens:
        Tokens f = new Tokens("This is a sample sentence.", new char[] {' ','-'});

        foreach (string item in f)
        {
            System.Console.WriteLine(item);
        }
    }
}
/* Output:
    This
    is
    a
    sample
    sentence.
*/

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）
集合类（C# 编程指南）
System.Collections.Generic

其他资源

C# 参考


异常和异常处理（C# 编程指南）

[image]

C# 编程指南
异常和异常处理（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

C# 语言的异常处理功能可帮助您处理程序运行时出现的任何意外或异常情况。异常处理使用 try、catch 和 finally 关键字尝试某些操作，以处理失败情况，尽管这些操作有可能失败，但如果您确定需要这样做，且希望在事后清理资源，就可以尝试这样做。公共语言运行时 (CLR)、.NET Framework 或任何第三方库或者应用程序代码都可以生成异常。异常是使用 throw 关键字创建的。

很多情况下，异常可能不是由代码直接调用的方法引发，而是由调用堆栈中位置更靠下的另一个方法所引发。在这种情况下，CLR 将展开堆栈，查找是否有方法包含针对该特定异常类型的 catch 块，如果找到这样的方法，就会执行找到的第一个这样的 catch 块。如果在调用堆栈中的任何位置都没有找到适当的 catch 块，就会终止该进程，并向用户显示一条消息。

此示例中使用一个方法检测是否有被零除的情况；如果有，则捕获该错误。如果没有异常处理，此程序将终止并产生“DivideByZeroException 未处理”错误。

C# 复制代码
class ExceptionTest
{
    static double SafeDivision(double x, double y)
    {
        if (y == 0)
            throw new System.DivideByZeroException();
        return x / y;
    }
    static void Main()
    {
        // Input for test purposes. Change values
        // to see exception handling behavior.
        double a = 98, b = 0;
        double result = 0;

        try
        {
            result = SafeDivision(a, b);
            Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
        }
        catch (DivideByZeroException e)
        {
            Console.WriteLine("Attempted divide by zero.");
        }
    }
}

异常概述

异常具有以下特点：

  • 各种类型的异常最终都是由 System.Exception 派生而来。

  • 在可能引发异常的语句周围使用 try 块。

  • 一旦 try 块中发生异常，控制流将跳转到第一个关联的异常处理程序（无论该处理程序存在于调用堆栈中的什么位置）。在 C# 中，catch 关键字用于定义异常处理程序。

  • 如果给定异常没有异常处理程序，则程序将停止执行，并显示一条错误消息。

  • 除非您可以处理某个异常并使应用程序处于已知状态，否则请不要捕捉该异常。如果捕捉 System.Exception，请在 catch 块的末尾使用 throw 关键字再次引发该异常。

  • 如果 catch 块定义了一个异常变量，则可以用它获取有关所发生异常类型的更多信息。

  • 程序可以使用 throw 关键字显式地引发异常。

  • 异常对象包含有关错误的详细信息，比如调用堆栈的状态以及有关错误的文本说明。

  • 即使发生异常也会执行 finally 块中的代码。使用 finally 块释放资源，例如，关闭在 try 块中打开的任何流或文件。

  • .NET Framework 中的托管异常是凭借 Win32 结构化异常处理机制实现的。有关更多信息，请参见Structured Exception Handling (C++) 和 A Crash Course on the Depths of Win32 Structured Exception Handling（有关深入探究 Win32 结构化异常处理的应急课程）。

相关章节

有关异常和异常处理的更多信息，请参见以下主题：

  • 使用异常（C# 编程指南）

  • 异常处理（C# 编程指南）

  • 创建和引发异常（C# 编程指南）

  • 编译器生成的异常（C# 编程指南）

  • 如何：使用 try/catch 处理异常（C# 编程指南）

  • 如何：使用 finally 执行清理代码（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各节：

  • 8.9.5 throw 语句

  • 8.10 try 语句

  • 16 异常

请参见

概念

C# 编程指南
异常概述

参考

C# 关键字
throw（C# 参考）
try-catch（C# 参考）
try-finally（C# 参考）
try-catch-finally（C# 参考）

其他资源

异常设计准则
处理和引发异常
编写可靠的 .NET 代码

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 修改了代码示例并添加了关于展开堆栈的文本。 | 内容 Bug 修复
2008 年 7 月 | 增加了解释 finally 块的示例。 | 信息补充。
2008 年 7 月 | 增加了有关 Win32 结构化异常处理机制的信息。 | 信息补充。


使用异常（C# 编程指南）

[image]

C# 编程指南
使用异常（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 中，程序中的运行时错误通过使用一种称为“异常”的机制在程序中传播。异常由遇到错误的代码引发，由能够更正错误的代码捕捉。异常可由 .NET Framework 公共语言运行时 (CLR) 或由程序中的代码引发。一旦引发了一个异常，这个异常就会在调用堆栈中往上传播，直到找到针对它的 catch 语句。未捕获的异常由系统提供的通用异常处理程序处理，该处理程序会显示一个对话框。

异常由从 Exception 派生的类表示。此类标识异常的类型，并包含详细描述异常的属性。引发异常涉及到创建一个异常派生类的实例，配置异常的属性（可选），然后使用 throw 关键字引发该对象。例如：

C# 复制代码
class CustomException : Exception
{
    public CustomException(string message)
    {

    }

}
private static void TestThrow()
{
    CustomException ex =
        new CustomException("Custom exception in TestThrow()");

    throw ex;
}

在引发异常之后，运行时检查当前语句以确定它是否在 try 块中。如果是，则检查与该 try 块关联的任何 catch 块，以确定它们是否能够捕获该异常。Catch 块通常会指定异常类型；如果该 catch 块的类型与异常或异常的基类的类型相同，则该 catch 块就能够处理该方法。例如：

C# 复制代码
static void TestCatch()
{
    try
    {
        TestThrow();
    }
    catch (CustomException ex)
    {
        System.Console.WriteLine(ex.ToString());
    }
}

如果引发异常的语句不在 try 块中，或者包含该语句的 try 块没有匹配的 catch 块，运行时将检查调用方法中是否有 try 语句和 catch 块。运行时将在调用堆栈中向上继续搜索兼容的 catch 块。在找到并执行 catch 块之后，控制权将传递给 catch 块之后的下一个语句。

一个 try 语句可能包含多个 catch 块。将执行第一个能够处理该异常的 catch 语句；任何后续的 catch 语句都将被忽略，即使它们是兼容的也如此。因此，在任何情况下都应该按照从最具体（或者派生程度最高）到最不具体这一顺序排列 catch 块。例如：

C# 复制代码
static void TestCatch2()
{
    System.IO.StreamWriter sw = null;
    try
    {
        sw = new System.IO.StreamWriter(@"C:\test\test.txt");
        sw.WriteLine("Hello");
    }

    catch (System.IO.FileNotFoundException ex)
    {
        System.Console.WriteLine(ex.ToString());  // put the more specific exception first
    }

    catch (System.IO.IOException ex)
    {
        System.Console.WriteLine(ex.ToString());  // put the less specific exceptions last
    }
    finally
    {
        sw.Close();
    }

    System.Console.WriteLine("Done");  // this statement is executed after the catch block
}

执行 catch 块之前，运行时会检查 finally 块。Finally 块使程序员可以清除中止的 try 块可能遗留下的任何模糊状态，或者释放任何外部资源（例如图形句柄、数据库连接或文件流），而无需等待垃圾回收器在运行时完成这些对象。例如：

C# 复制代码
static void TestFinally()
{
    System.IO.FileStream file = null;
    //Change the path to something that works on your machine
    System.IO.FileInfo fileInfo = new System.IO.FileInfo(@"C:\file.txt");

    try
    {
        file = fileInfo.OpenWrite();
        file.WriteByte(0xF);
    }
    finally
    {
        // Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
        if (file != null)
        {
            file.Close();
        }
    }

    try
    {
        file = fileInfo.OpenWrite();
        System.Console.WriteLine("OpenWrite() succeeded");
    }
    catch (System.IO.IOException)
    {
        System.Console.WriteLine("OpenWrite() failed");
    }
}

如果 WriteByte() 引发了异常，那么在没有调用 file.Close() 的情况下，第二个 try 块中尝试重新打开文件的代码就会失败，并且文件将保持锁定状态。由于要执行 finally 块（即使已引发异常），前一示例中的 finally 块使得可以正确地关闭文件，从而帮助避免错误。

如果在引发异常之后没有在调用堆栈上找到兼容的 catch 块，则会出现三种情况中的一种：

  • 如果异常出现在析构函数中，则中止该析构函数并调用基析构函数（如果有）。

  • 如果调用堆栈包含静态构造函数或静态字段初始值设定项，则引发一个 TypeInitializationException，并将原始异常分配给新异常的 InnerException 属性。

  • 如果到达线程的开头，则终止线程。

请参见

概念

C# 编程指南

参考

异常和异常处理（C# 编程指南）


异常处理（C# 编程指南）

[image]

C# 编程指南
异常处理（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 程序员使用 try 块来对可能受异常影响的代码进行分区，并使用 catch 块来处理所产生的任何异常。可以使用 finally 块来执行代码，而无论是否引发了异常。有时需要这样做，因为如果引发了异常，将不会执行 try/catch 构造后面的代码。try 块必须与 catch 或 finally 块一起使用，并且可以包括多个 catch 块。例如：

C# 复制代码
try
{
    // Code to try here.
}
catch (SomeSpecificException ex)
{
    // Code to handle exception here.
    // Only catch exceptions you know how to handle.
    // Never catch base class System.Exception without
    // rethrowing it at the end of the catch block.
}
C# 复制代码
try
{
    // Code to try here.
}
finally
{
    // Code to execute after try here.
}
C# 复制代码
try
{
    // Code to try here.
}
catch (SomeSpecificException ex)
{
    // Code to handle exception here.
}
finally
{
    // Code to execute after try (and possibly catch) here.
}

不带有 catch 或 finally 块的 try 语句将导致编译器错误。

Catch 块

catch 块可以指定要捕捉的异常类型。此类型称为“异常筛选器”，应该是从 Exception 派生的类型。一般来说，除非您确知如何处理 try 块中可能引发的所有异常，或者在 catch 块的末尾包括一条 throw 语句，否则，不要在 catch 块中指定 Exception。

具有不同异常筛选器的多个 catch 块可以串联在一起。多个 catch 块的计算顺序是从顶部到底部，但是，对于所引发的每个异常，都只执行一个 catch 块。与所引发异常的准确类型或其基类最为匹配的第一个 catch 块将被执行。如果没有任何 catch 块指定匹配的异常筛选器，则将执行不带筛选器的 catch 块（如果有的话）。需要将带有最具体的（即派生程度最高的）异常类的 catch 块放在最前面。

当下列条件为真时，应该捕捉异常：

  • 对引发异常的原因有具体的了解，并可实现特定的恢复，例如，捕捉 FileNotFoundException 对象并提示用户输入新的文件名。

  • 可以新建一个更具体的异常并引发该异常。例如：

    C# 复制代码
    int GetInt(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch(System.IndexOutOfRangeException e)
    {
        throw new System.ArgumentOutOfRangeException(
            "Parameter index is out of range.");
    }
}

  • 部分处理异常。例如，可以使用 catch 块向错误日志中添加项，但随后重新引发该异常，以便对该异常进行后续处理。例如：

    C# 复制代码
    try
{
    // try to access a resource
}
catch (System.UnauthorizedAccessException e)
{
    LogError(e);  // call a custom error logging procedure
    throw e;      // re-throw the error
}

Finally 块

可以使用 finally 块清理在 try 块中执行的操作。如果存在 finally 块，它将在执行完 finally 和 finally 块之后执行。finally 块始终会执行，而与是否引发异常或者是否找到与异常类型匹配的 catch 块无关。

可以使用 finally 块释放资源（如文件流、数据库连接和图形句柄），而不用等待由运行时中的垃圾回收器来完成对象。有关更多信息，请参见using 语句（C# 参考）。

在此示例中，finally 块用来关闭在 try 块中打开的文件。注意，在关闭文件句柄之前要检查它的状态。如果 try 块没有打开该文件，则文件句柄仍然设置为 null。或者，如果成功打开文件并且没有引发异常，则 finally 块仍将执行，并且将关闭打开的文件。

C# 复制代码
System.IO.FileStream file = null;
System.IO.FileInfo fileinfo = new System.IO.FileInfo("C:\\file.txt");
try
{
    file = fileinfo.OpenWrite();
    file.WriteByte(0xF);
}
finally
{
    // check for null because OpenWrite
    // might have failed
    if (file != null)
    {
        file.Close();
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 16 异常

  • 8.9.5 throw 语句

  • 8.10 try 语句

请参见

概念

C# 编程指南

参考

异常和异常处理（C# 编程指南）
try-catch（C# 参考）
try-finally（C# 参考）
try-catch-finally（C# 参考）

其他资源

C# 参考


创建和引发异常（C# 编程指南）

[image]

C# 编程指南
创建和引发异常（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

异常用于指示在运行程序时发生了错误。此时将创建一个描述错误的异常对象，然后使用 throw 关键字“引发”该对象。然后运行时搜索最兼容的异常处理程序。

当存在下列一种或多种情况时，程序员应引发异常：

  • 方法无法完成其中定义的功能。

    例如，如果方法的参数具有无效值：

    C# 复制代码
    static void CopyObject(SampleClass original)
{
    if (original == null)
    {
        throw new System.ArgumentException("Parameter cannot be null", "original");
    }

}

  • 根据对象的状态，对某个对象进行不适当的调用。

    一个示例可能尝试对只读文件执行写操作。在对象状态不允许某项操作的情况下，引发 InvalidOperationException 的一个实例或基于此类的派生类的对象。以下为引发 InvalidOperationException 对象的方法的示例：

    C# 复制代码
    class ProgramLog
{
    System.IO.FileStream logFile = null;
    void OpenLog(System.IO.FileInfo fileName, System.IO.FileMode mode) {}

    void WriteLog()
    {
        if (!this.logFile.CanWrite)
        {
            throw new System.InvalidOperationException("Logfile cannot be read-only");
        }
        // Else write data to the log and return.
    }
}

  • 方法的参数导致了异常。

    在此情况下，应捕获原始异常并创建一个 ArgumentException 实例。原始异常应作为 InnerException 参数传递给 ArgumentException 的构造函数：

    C# 复制代码
    static int GetValueFromArray(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch (System.IndexOutOfRangeException ex)
    {
        System.ArgumentException argEx = new System.ArgumentException("Index is out of range", "index", ex);
        throw argEx;
    }
}

异常包含一个名为 StackTrace 的属性。此字符串包含当前调用堆栈上的方法的名称，以及为每个方法引发异常的位置（文件名和行号）。StackTrace 对象由公共语言运行时 (CLR) 从 throw 语句点开始自动创建，因此必须从堆栈跟踪的开始点引发异常。

所有异常都包含一个名为 Message 的属性。应该设置此字符串来解释发生异常的原因。注意，不应将安全敏感信息放在消息文本中。除 Message 之外，ArgumentException 还包含一个名为 ParamName 的属性，应将该属性设置为导致引发异常的参数的名称。对于属性设置器，ParamName 应设置为 value。

公共的受保护方法应在其无法完成预期功能时引发异常。引发的异常类应该是符合错误条件的最确切的可用异常。这些异常应编写为类功能的一部分，派生类或对原始类的更新应保留相同的行为，以实现向后兼容性。

引发异常时要避免的情况

下表确定了在引发异常时要避免的做法：

  • 不应使用异常来更改正常执行过程中的程序流程。异常只能用于报告和处理错误条件。

  • 只能引发异常，而不能作为返回值或参数返回异常。

  • 不要从自己的源代码中有意引发 System..::.Exception、System..::.SystemException、System..::.NullReferenceException 或 System..::.IndexOutOfRangeException。

  • 不要创建可在调试模式下引发但不会在发布模式下引发的异常。若要在开发阶段确定运行时错误，请改用调试断言。

定义异常类

程序可以引发 System 命名空间中的预定义异常类（前面注明的情况除外），或通过从 ApplicationException 派生来创建它们自己的异常类。派生类至少应定义四个构造函数：一个是默认构造函数，一个用来设置消息属性，一个用来设置 Message 属性和 InnerException 属性。第四个构造函数用于序列化异常。新异常类应该可序列化。例如：

C# 复制代码
[Serializable()]
public class InvalidDepartmentException : System.Exception
{
    public InvalidDepartmentException() { }
    public InvalidDepartmentException(string message) { }
    public InvalidDepartmentException(string message, System.Exception inner) { }

    // Constructor needed for serialization
    // when exception propagates from a remoting server to the client.
    protected InvalidDepartmentException(System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) { }
}

仅当新属性提供的数据有助于解决异常时，才应将其添加到异常类。如果向派生的异常类添加了新属性，则应重写 ToString() 以返回添加的信息。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 8.9.5 throw 语句

  • 8.10 try 语句

  • 16 异常

请参见

概念

C# 编程指南

参考

异常和异常处理（C# 编程指南）
异常处理（C# 编程指南）


编译器生成的异常（C# 编程指南）

[image]

C# 编程指南
编译器生成的异常（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

有些异常在基本操作失败时由 .NET Framework 的公共语言运行时 (CLR) 自动引发。下表列出了这些异常和它们的错误条件。

异常 | 说明
ArithmeticException | 在算术运算期间发生的异常（如 DivideByZeroException 和 OverflowException）的基类。
ArrayTypeMismatchException | 当数组存储给定的元素时，如果由于该元素的实际类型与数组的实际类型不兼容而导致存储失败，就会引发此异常。
DivideByZeroException | 在试图用零除整数值时引发。
IndexOutOfRangeException | 在试图为数组设置小于零或超出数组界限的索引时引发。
InvalidCastException | 当从基类型到接口或派生类型的显式转换在运行时失败时，就会引发此异常。
NullReferenceException | 在尝试引用值为 null 的对象时引发。
OutOfMemoryException | 在使用 new 运算符分配内存的尝试失败时引发。这表明可用于公共语言运行时的内存已耗尽。
OverflowException | 在 checked 上下文中的算术运算溢出时引发。
StackOverflowException | 当执行堆栈由于具有太多的挂起方法调用而耗尽时，就会引发此异常；这通常表明存在非常深的递归或无限递归。
TypeInitializationException | 在静态构造函数引发异常并且不存在可以捕捉到它的兼容 catch 子句时引发。

请参见

概念

C# 编程指南

参考

异常和异常处理（C# 编程指南）
异常处理（C# 编程指南）
try-catch（C# 参考）
try-finally（C# 参考）
try-catch-finally（C# 参考）


如何：使用 try/catch 处理异常（C# 编程指南）

[image]

C# 编程指南
如何：使用 try/catch 处理异常（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

try-catch 块的用途是捕捉和处理工作代码所生成的异常。有些异常可以在 catch 块中处理，解决问题后不会再次引发异常；但更多情况下，您唯一能做的是确保引发适当的异常。

示例

在此示例中，IndexOutOfRangeException 不是最适当的异常：对本方法而言 ArgumentOutOfRangeException 更恰当些，因为错误是由调用方传入的 index 参数导致的。

C# 复制代码
class TestTryCatch
{
    static int GetInt(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (System.IndexOutOfRangeException e)  // CS0168
        {
            System.Console.WriteLine(e.Message);
            //set IndexOutOfRangeException to the new exception's InnerException
            throw new System.ArgumentOutOfRangeException("index parameter is out of range.", e);
        }
    }
}

注释

导致异常的代码被括在 try 块中。在其后面紧接着添加一个 catch 语句，以便在 IndexOutOfRangeException 发生时对其进行处理。catch 块处理 IndexOutOfRangeException，并引发更适当的 ArgumentOutOfRangeException 异常。为给调用方提供尽可能多的信息，应考虑将原始异常指定为新异常的 InnerException。因为 InnerException 属性是只读，所以必须在新异常的构造函数中为其赋值。

请参见

概念

C# 编程指南

参考

异常和异常处理（C# 编程指南）
异常处理（C# 编程指南）


如何：使用 finally 执行清理代码（C# 编程指南）

[image]

C# 编程指南
如何：使用 finally 执行清理代码（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

finally 语句的目的是确保即使在引发异常的情况下也能立即进行必要的对象（通常是保存外部资源的对象）清理。此类清理功能的一个示例是在使用后立即对 FileStream 调用 Close，而不是等待公共语言运行时对该对象进行垃圾回收，如下所示：

C# 复制代码
static void CodeWithoutCleanup()
{
    System.IO.FileStream file = null;
    System.IO.FileInfo fileInfo = new System.IO.FileInfo("C:\\file.txt");

    file = fileInfo.OpenWrite();
    file.WriteByte(0xF);

    file.Close();
}

示例

为了将上面的代码转换为 try-catch-finally 语句，需要将清理代码与工作代码分开，如下所示。

C# 复制代码
static void CodeWithCleanup()
{
    System.IO.FileStream file = null;
    System.IO.FileInfo fileInfo = null;

    try
    {
        fileInfo = new System.IO.FileInfo("C:\\file.txt");

        file = fileInfo.OpenWrite();
        file.WriteByte(0xF);
    }
    catch(System.UnauthorizedAccessException e)
    {
        System.Console.WriteLine(e.Message);
    }
    finally
    {
        if (file != null)
        {
            file.Close();
        }
    }
}

因为在 OpenWrite() 调用前，try 块内随时都有可能发生异常，OpenWrite() 调用本身也有可能失败，所以我们无法保证该文件在我们试图关闭它时处于打开状态。finally 块添加了一项检查，以确保在调用 Close 方法前，FileStream 对象不为 null。如果没有 null 检查，finally 块可能引发自身的 NullReferenceException，但是应当尽可能避免在 finally 块中引发异常。

在 finally 块中关闭数据库连接是另一个不错的选择。因为有时候数据库服务器允许的连接数是有限的，所以应尽快关闭数据库连接。在由于引发了异常而无法关闭连接的情况下，使用 finally 块也是比等待垃圾回收更好的一种选择。

请参见

概念

C# 编程指南

参考

异常和异常处理（C# 编程指南）
异常处理（C# 编程指南）
using 语句（C# 参考）
try-catch（C# 参考）
try-finally（C# 参考）
try-catch-finally（C# 参考）


如何：捕捉非 CLS 异常

[image]

C# 编程指南
如何：捕捉非 CLS 异常
示例 另请参见 发送反馈意见

更新：2007 年 11 月

包括 C++/CLI 在内的某些 .NET 语言允许对象引发不是由 Exception 派生的异常。这类异常称为“非 CLS 异常”或“非异常”。在 Visual C# 中，您不能引发非 CLS 异常，但是可以采用两种方法捕捉这类异常：

  • 在 catch (Exception e) 块中作为 RuntimeWrappedException 捕捉。

    默认情况下，Visual C# 程序集将非 CLS 异常作为包装异常来捕捉。如果需要访问可通过 WrappedException 属性访问的原始异常，请使用此方法。本主题后面的过程将解释如何使用此方式捕捉异常。

  • 在位于 catch (Exception) 或 catch (Exception e) 块之后的常规 catch 块（没有指定异常类型的 catch 块）中。

    如果您要执行某个操作（如写入日志文件）以响应非 CLS 异常，并且不需要访问异常信息，请使用此方法。默认情况下，公共语言运行时会包装所有异常。要禁用此行为，请将程序集级别属性添加到您的代码中，该属性通常在 AssemblyInfo.cs 文件中。[assembly: RuntimeCompatibilityAttribute(WrapNonExceptionThrows = false)]

捕捉非 CLS 异常

  1. 在 catch(Exception e) block 中，使用 as 关键字来测试 e 能否强制转换为 RuntimeWrappedException。

  2. 通过 WrappedException 属性访问原始异常。

示例

下面的示例演示如何捕捉由使用 C++/CLR 编写的类库引发的非 CLS 异常。请注意，在此示例中，Visual C# 客户端代码事先已经知道要引发的异常类型为 System..::.String。只要 WrappedException 属性的原始类型可通过您的代码来访问，就可以将该属性强制转换回其原始类型。

复制代码
// Class library written in C++/CLR.
   ThrowNonCLS.Class1 myClass = new ThrowNonCLS.Class1();

   try
   {
    // throws gcnew System::String(
    // "I do not derive from System.Exception!");
    myClass.TestThrow();
   }

   catch (Exception e)
   {
    RuntimeWrappedException rwe = e as RuntimeWrappedException;
    if (rwe != null)
    {
      String s = rwe.WrappedException as String;
      if (s != null)
      {
        Console.WriteLine(s);
      }
    }
    else
    {
       // Handle other System.Exception types.
    }
   }

请参见

参考

异常和异常处理（C# 编程指南）
RuntimeWrappedException


文件系统和注册表（C# 编程指南）

[image]

C# 编程指南
文件系统和注册表（C# 编程指南）
发送反馈意见

更新：2007 年 11 月

以下主题说明如何使用 C# 和 .NET Framework 对文件、文件夹和注册表执行各种基本操作。

本节内容

标题 | 说明
如何：循环访问目录树（C# 编程指南） | 说明如何手动循环访问目录树。
如何：获取有关文件、文件夹和驱动器的信息（C# 编程指南） | 说明如何检索有关文件、文件夹和驱动器的信息，例如创建时间和大小。
如何：创建文件或文件夹（C# 编程指南） | 说明如何创建新文件或文件夹。
如何：复制、删除和移动文件和文件夹（C# 编程指南） | 说明如何复制、删除和移动文件和文件夹。
如何：提供文件操作进度对话框（C# 编程指南） | 说明如何为某些文件操作显示标准的 Windows 进度对话框。
如何：写入文本文件（C# 编程指南） | 说明如何写入文本文件。
如何：读取文本文件（C# 编程指南） | 说明如何读取文本文件中的内容。
如何：一次读取一行文本文件 (Visual C#) | 说明如何一次从文件中检索一行文本。
如何：在注册表中创建注册表项 (Visual C#) | 说明如何向系统注册表中写入项。
如何：写入 Application 事件日志 (Visual C#) | 说明如何写入应用程序事件日志。

相关章节

文件和流 I/O

如何：复制、删除和移动文件和文件夹（C# 编程指南）

C# 编程指南

文件系统和注册表（C# 编程指南）

System.IO


如何：循环访问目录树（C# 编程指南）

[image]

C# 编程指南
如何：循环访问目录树（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

词组“循环访问目录树”的意思是在指定的根文件夹下，访问每个嵌套子目录中任意深度的所有文件。您不必打开每一个文件。可以只检索 string 形式的文件名或子目录名，或者可以检索 System.IO..::.FileInfo 或 System.IO..::.DirectoryInfo 对象形式的其他信息。

说明：

在 Windows 中，可交换使用术语“目录”和“文件夹”。大多数文档和用户界面文本使用术语“文件夹”，但 .NET Framework 类库使用术语“目录”。

最简单的例子，如果您确信您拥有指定根目录下所有目录的访问权限，则您可以使用 System.IO.SearchOption.AllDirectories 标志。该标志返回与指定模式相匹配的所有嵌套子目录。下面的示例演示如何使用该标志。

C# 复制代码
root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);

此方法的缺点是，如果指定根目录下任何一个子目录引发了 DirectoryNotFoundException 或 UnauthorizedAccessException，则整个方法将会失败并且不返回任何目录。使用 GetFiles 方法时也是如此。如果一定要处理特定子文件夹中的这些异常，则必须手动遍历该目录树，如下面的示例所示。

手动遍历目录树时，可以先处理子目录（前序遍历），或者可以先处理文件（后序遍历）。如果执行前序遍历，则在循环访问直接位于当前文件夹本身中的文件之前，请先遍历当前文件夹下的整个树。本文档后面的示例执行的是后序遍历，但您可以轻松地将它们修改为执行前序遍历。

另外还可以选择是使用递归遍历，还是使用基于堆栈的遍历。本文档后面的示例对这两种方法进行了演示。

如果必须对文件和文件夹上执行各种操作，则可以通过下面的方法将这些示例模块化：将操作重构到单独的函数，这样您就可以通过单个委托来调用这些函数。

说明：

NTFS 文件系统可以包含交接点、符号链接和硬链接形式的重新分析点。.NET Framework 方法（如 GetFiles 和 GetDirectories）将不返回重新分析点下的任何子目录。此行为可防止两个重新分析点相互引用时陷入无限循环。通常，为了确保您不会无意修改或删除文件，在使用重新分析点时应特别小心。如果需要精确控制重新分析点，请使用平台调用或本机代码直接调用相应的 Win32 文件系统方法。

示例

下面的示例演示如何使用递归遍历目录树。递归方法很简洁，但如果目录树很大且嵌套很深，则有可能会引起堆栈溢出异常。

对于所处理的特定异常以及在每个文件和文件夹上执行的特定操作，都只是作为示例提供。您应该修改此代码来满足自己特定的需要。有关更多信息，请参见代码中的注释。

C# 复制代码
public class RecursiveFileSearch
{
    static System.Collections.Specialized.StringCollection log = new System.Collections.Specialized.StringCollection();

    static void Main()
    {
        // Start with drives if you have to search the entire computer.
        string[] drives = System.Environment.GetLogicalDrives();

        foreach (string dr in drives)
        {
            System.IO.DriveInfo di = new System.IO.DriveInfo(dr);

            // Here we skip the drive if it is not ready to be read. This
            // is not necessarily the appropriate action in all scenarios.
            if (!di.IsReady)
            {
                Console.WriteLine("The drive {0} could not be read", di.Name);
                continue;
            }
            System.IO.DirectoryInfo rootDir = di.RootDirectory;
            WalkDirectoryTree(rootDir);
        }

        // Write out all the files that could not be processed.
        Console.WriteLine("Files with restricted access:");
        foreach (string s in log)
        {
            Console.WriteLine(s);
        }
        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key");
        Console.ReadKey();
    }

    static void WalkDirectoryTree(System.IO.DirectoryInfo root)
    {
        System.IO.FileInfo[] files = null;
        System.IO.DirectoryInfo[] subDirs = null;

        // First, process all the files directly under this folder
        try
        {
            files = root.GetFiles("*.*");
        }
        // This is thrown if even one of the files requires permissions greater
        // than the application provides.
        catch (UnauthorizedAccessException e)
        {
            // This code just writes out the message and continues to recurse.
            // You may decide to do something different here. For example, you
            // can try to elevate your privileges and access the file again.
            log.Add(e.Message);
        }

        catch (System.IO.DirectoryNotFoundException e)
        {
            Console.WriteLine(e.Message);
        }

        if (files != null)
        {
            foreach (System.IO.FileInfo fi in files)
            {
                // In this example, we only access the existing FileInfo object. If we
                // want to open, delete or modify the file, then
                // a try-catch block is required here to handle the case
                // where the file has been deleted since the call to TraverseTree().
                Console.WriteLine(fi.FullName);
            }

            // Now find all the subdirectories under this directory.
            subDirs = root.GetDirectories();

            foreach (System.IO.DirectoryInfo dirInfo in subDirs)
            {
                // Resursive call for each subdirectory.
                WalkDirectoryTree(dirInfo);
            }
        }
    }
}

下面的示例演示在不使用递归的情况下如何循环访问目录树中的文件和文件夹。该技术使用泛型 Stack<(Of <(T>)>) 集合类型，该类型是一个后进先出 (LIFO) 堆栈。

对于所处理的特定异常以及在每个文件和文件夹上执行的特定操作，都只是作为示例提供。您应该修改此代码来满足自己特定的需要。有关更多信息，请参见代码中的注释。

C# 复制代码
public class StackBasedIteration
{
    static void Main(string[] args)
    {
        // Specify the starting folder on the command line, or in
        // Visual Studio in the Project > Properties > Debug pane.
        TraverseTree(args[0]);

        Console.WriteLine("Press any key");
        Console.ReadKey();
    }

    public static void TraverseTree(string root)
    {
        // Data structure to hold names of subfolders to be
        // examined for files.
        Stack<string> dirs = new Stack<string>(20);

        if (!System.IO.Directory.Exists(root))
        {
            throw new ArgumentException();
        }
        dirs.Push(root);

        while (dirs.Count > 0)
        {
            string currentDir = dirs.Pop();
            string[] subDirs;
            try
            {
                subDirs = System.IO.Directory.GetDirectories(currentDir);
            }
            // An UnauthorizedAccessException exception will be thrown if we do not have
            // discovery permission on a folder or file. It may or may not be acceptable
            // to ignore the exception and continue enumerating the remaining files and
            // folders. It is also possible (but unlikely) that a DirectoryNotFound exception
            // will be raised. This will happen if currentDir has been deleted by
            // another application or thread after our call to Directory.Exists. The
            // choice of which exceptions to catch depends entirely on the specific task
            // you are intending to perform and also on how much you know with certainty
            // about the systems on which this code will run.
            catch (UnauthorizedAccessException e)
            {
                Console.WriteLine(e.Message);
                continue;
            }
            catch (System.IO.DirectoryNotFoundException e)
            {
                Console.WriteLine(e.Message);
                continue;
            }

            string[] files = null;
            try
            {
                files = System.IO.Directory.GetFiles(currentDir);
            }

            catch (UnauthorizedAccessException e)
            {

                Console.WriteLine(e.Message);
                continue;
            }

            catch (System.IO.DirectoryNotFoundException e)
            {
                Console.WriteLine(e.Message);
                continue;
            }
            // Perform the required action on each file here.
            // Modify this block to perform your required task.
            foreach (string file in files)
            {
                try
                {
                    // Perform whatever action is required in your scenario.
                    System.IO.FileInfo fi = new System.IO.FileInfo(file);
                    Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
                }
                catch (System.IO.FileNotFoundException e)
                {
                    // If file was deleted by a separate application
                    //  or thread since the call to TraverseTree()
                    // then just continue.
                    Console.WriteLine(e.Message);
                    continue;
                }
            }

            // Push the subdirectories onto the stack for traversal.
            // This could also be done before handing the files.
            foreach (string str in subDirs)
                dirs.Push(str);
        }
    }
}

通常，测试每个文件夹以确定您的应用程序是否有权限打开它是非常耗时的。因此，代码示例只是将该部分操作放到了一个 try/catch 块内。您可以修改该 catch 块，以便在访问某个文件夹遭受拒绝时，您可以提升自己的权限，然后再次访问它。通常，只捕捉那些无需使应用程序停留在一个未知状态就可以处理的异常。

如果必须将目录树的内容存储到内存或磁盘中，则最好只存储每个文件的 FullName 属性（string 类型）。然后，可以根据需要使用该字符串创建一个新的 FileInfo 或 DirectoryInfo 对象，或者打开任何需要进行其他处理的文件。

可靠编程

可靠文件迭代代码必须考虑文件系统的诸多复杂性。有关更多信息，请参见 NTFS Technical Reference（NTFS 技术参考）。

请参见

概念

LINQ 和文件目录

参考

System.IO

其他资源

文件系统和注册表（C# 编程指南）


如何：获取有关文件、文件夹和驱动器的信息（C# 编程指南）

[image]

C# 编程指南
如何：获取有关文件、文件夹和驱动器的信息（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在 .NET Framework 中，可以使用以下类来访问文件系统信息：

  • System.IO..::.FileInfo

  • System.IO..::.DirectoryInfo

  • System.IO..::.DriveInfo

  • System.IO..::.Directory

  • System.IO..::.File

FileInfo 和 DirectoryInfo 类表示文件或目录，包含公开 NTFS 文件系统所支持的很多文件属性 (Attribute) 的属性 (Property)，同时还包含用于打开、关闭、移动和删除文件和文件夹的方法。可以通过将表示文件、文件夹或驱动器名称的字符串传递到下面的构造函数来创建这些类的实例：

C# 复制代码
System.IO.DriveInfo di = new System.IO.DriveInfo(@"C:\");

此外，还可以通过调用 DirectoryInfo..::.GetDirectories、DirectoryInfo..::.GetFiles 和 DriveInfo..::.RootDirectory 来获取文件、文件夹或驱动器的名称。

System.IO..::.Directory 和 System.IO..::.File 类提供用于检索有关目录和文件的信息的静态方法。

示例

下面的示例演示访问有关文件和文件夹的信息的各种方法。

C# 复制代码
class FileSysInfo
{
    static void Main()
    {
        // You can also use System.Environment.GetLogicalDrives to
        // obtain names of all logical drives on the computer.
        System.IO.DriveInfo di = new System.IO.DriveInfo(@"C:\");
        Console.WriteLine(di.TotalFreeSpace);
        Console.WriteLine(di.VolumeLabel);

        // Get the root directory and print out some information about it.
        System.IO.DirectoryInfo dirInfo = di.RootDirectory;
        Console.WriteLine(dirInfo.Attributes.ToString());

        // Get the files in the directory and print out some information about them.
        System.IO.FileInfo[] fileNames = dirInfo.GetFiles("*.*");

        foreach (System.IO.FileInfo fi in fileNames)
        {
            Console.WriteLine("{0}: {1}: {2}", fi.Name, fi.LastAccessTime, fi.Length);
        }

        // Get the subdirectories directly that is under the root.
        // See "How to: Iterate Through a Directory Tree" for an example of how to
        // iterate through an entire tree.
        System.IO.DirectoryInfo[] dirInfos = dirInfo.GetDirectories("*.*");

        foreach (System.IO.DirectoryInfo d in dirInfos)
        {
            Console.WriteLine(d.Name);
        }

        // The Directory and File classes provide several static methods
        // for accessing files and directories.

        // Get the current application directory.
        string currentDirName = System.IO.Directory.GetCurrentDirectory();
        Console.WriteLine(currentDirName);

        // Get an array of file names as strings rather than FileInfo objects.
        // Use this method when storage space is an issue, and when you might
        // hold on to the file name reference for a while before you try to access
        // the file.
        string[] files = System.IO.Directory.GetFiles(currentDirName, "*.txt");

        foreach (string s in files)
        {
            // Create the FileInfo object only when needed to ensure
            // the information is as current as possible.
            System.IO.FileInfo fi = null;
            try
            {
                 fi = new System.IO.FileInfo(s);
            }
            catch (System.IO.FileNotFoundException e)
            {
                // To inform the user and continue is
                // sufficient for this demonstration.
                // Your application may require different behavior.
                Console.WriteLine(e.Message);
                continue;
            }
            Console.WriteLine("{0} : {1}",fi.Name, fi.Directory);
        }

        // Change the directory. In this case, first check to see
        // whether it already exists, and create it if it does not.
        // If this is not appropriate for your application, you can
        // handle the System.IO.IOException that will be raised if the
        // directory cannot be found.
        if (!System.IO.Directory.Exists(@"C:\Users\Public\TestFolder\"))
        {
            System.IO.Directory.CreateDirectory(@"C:\Users\Public\TestFolder\");
        }

        System.IO.Directory.SetCurrentDirectory(@"C:\Users\Public\TestFolder\");

        currentDirName = System.IO.Directory.GetCurrentDirectory();
        Console.WriteLine(currentDirName);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

可靠编程

处理用户指定的路径字符串时，还应处理在以下情况下引发的异常：

  • 文件名的格式不正确。例如，包含无效字符或仅包含空白。

  • 文件名为空。

  • 文件名长于系统定义的最大长度。

  • 文件名包含冒号 (:)。

如果应用程序不具有读取指定文件所需的足够权限，则无论路径是否存在，Exists 方法都将返回 false；该方法不引发异常。

请参见

概念

C# 编程指南

参考

System.IO

其他资源

文件系统和注册表（C# 编程指南）

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


如何：创建文件或文件夹（C# 编程指南）

[image]

C# 编程指南
如何：创建文件或文件夹（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例在计算机上创建一个文件夹和一个子文件夹，然后在该子文件夹中创建一个新文件并将一些数据写入该文件。

示例

C# 复制代码
public class CreateFileOrFolder
{
    static void Main()
    {
        // Specify a "currently active folder"
        string activeDir = @"c:\testdir2";

        //Create a new subfolder under the current active folder
        string newPath = System.IO.Path.Combine(activeDir, "mySubDir");

        // Create the subfolder
        System.IO.Directory.CreateDirectory(newPath);

        // Create a new file name. This example generates
        // a random string.
        string newFileName = System.IO.Path.GetRandomFileName();

        // Combine the new file name with the path
        newPath = System.IO.Path.Combine(newPath, newFileName);

        // Create the file and write to it.
        // DANGER: System.IO.File.Create will overwrite the file
        // if it already exists. This can occur even with
        // random file names.
        if (!System.IO.File.Exists(newPath))
        {
            using (System.IO.FileStream fs = System.IO.File.Create(newPath))
            {
                for (byte i = 0; i < 100; i++)
                {
                    fs.WriteByte(i);
                }
            }
        }

        // Read data back from the file to prove
        // that the previous code worked.
        try
        {
            byte[] readBuffer = System.IO.File.ReadAllBytes(newPath);
            foreach (byte b in readBuffer)
            {
                Console.WriteLine(b);
            }
        }
        catch (System.IO.IOException e)
        {
            Console.WriteLine(e.Message);
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}

如果该文件夹已存在，则 CreateDirectory 不执行任何操作，且不会引发异常。而 File..::.Create 则会覆盖任何现有文件。若要避免覆盖现有文件，可以使用 OpenWrite()()() 方法并指定将使文件被追加而不是被覆盖的 FileMode.OpenOrCreate 选项。

以下情况可能会导致异常：

  • 文件夹名称格式不正确。例如，它包含非法字符或仅仅是空白（ArgumentException 类）。使用 Path 类创建有效路径名。

  • 要创建的文件夹的父文件夹是只读的（IOException 类）。

  • 文件夹名称是 null（ArgumentNullException 类）。

  • 文件夹名称太长（PathTooLongException 类）。

  • 文件夹名称只是冒号“:”（PathTooLongException 类）。

安全性

在部分信任的情况下可能会引发 SecurityException 类的实例。

如果用户不具有创建文件夹的权限，则该示例引发 UnauthorizedAccessException 类的实例。

请参见

概念

C# 编程指南

参考

System.IO

其他资源

文件系统和注册表（C# 编程指南）


如何：复制、删除和移动文件和文件夹（C# 编程指南）

[image]

C# 编程指南
如何：复制、删除和移动文件和文件夹（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

以下示例说明如何使用 System.IO 命名空间中的 System.IO..::.File、System.IO..::.Directory、System.IO..::.FileInfo 和 System.IO..::.DirectoryInfo 类以同步方式复制、移动和删除文件和文件夹。这些示例没有提供进度栏或其他任何用户界面。如果您想提供一个标准进度对话框，请参见如何：提供文件操作进度对话框（C# 编程指南）。

在操作多个文件时，请使用 System.IO..::.FileSystemWatcher 提供一些事件，以便可以利用这些事件计算进度。另一种方法是使用平台调用来调用 Windows Shell 中相应的文件相关方法。有关如何异步执行这些文件操作的信息，请参见异步文件 I/O。

示例

下面的示例演示如何复制文件和目录。

C# 复制代码
// Simple synchronous file copy operations with no user interface.
// To run this sample, first create the following directories and files:
// C:\Users\Public\TestFolder
// C:\Users\Public\TestFolder\test.txt
// C:\Users\Public\TestFolder\SubDir\test.txt
public class SimpleFileCopy
{
    static void Main()
    {
        string fileName = "test.txt";
        string sourcePath = @"C:\Users\Public\TestFolder";
        string targetPath =  @"C:\Users\Public\TestFolder\SubDir";

        // Use Path class to manipulate file and directory paths.
        string sourceFile = System.IO.Path.Combine(sourcePath, fileName);
        string destFile = System.IO.Path.Combine(targetPath, fileName);

        // To copy a folder's contents to a new location:
        // Create a new target folder, if necessary.
        if (!System.IO.Directory.Exists(targetPath))
        {
            System.IO.Directory.CreateDirectory(targetPath);
        }

        // To copy a file to another location and
        // overwrite the destination file if it already exists.
        System.IO.File.Copy(sourceFile, destFile, true);

        // To copy all the files in one directory to another directory.
        // Get the files in the source folder. (To recursively iterate through
        // all subfolders under the current directory, see
        // "How to: Iterate Through a Directory Tree.")
        // Note: Check for target path was performed previously
        //       in this code example.
        if (System.IO.Directory.Exists(sourcePath))
        {
            string[] files = System.IO.Directory.GetFiles(sourcePath);

            // Copy the files and overwrite destination files if they already exist.
            foreach (string s in files)
            {
                // Use static Path methods to extract only the file name from the path.
                fileName = System.IO.Path.GetFileName(s);
                destFile = System.IO.Path.Combine(targetPath, fileName);
                System.IO.File.Copy(s, destFile, true);
            }
        }
        else
        {
            Console.WriteLine("Source path does not exist!");
        }

        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

下面的示例演示如何移动文件和目录。

C# 复制代码
// Simple synchronous file move operations with no user interface.
public class SimpleFileMove
{
    static void Main()
    {
        string sourceFile = @"C:\Users\Public\public\test.txt";
        string destinationFile = @"C:\Users\Public\private\test.txt";

        // To move a file or folder to a new location:
        System.IO.File.Move(sourceFile, destinationFile);

        // To move an entire directory. To programmatically modify or combine
        // path strings, use the System.IO.Path class.
        System.IO.Directory.Move(@"C:\Users\Public\public\test\", @"C:\Users\Public\private");
    }
}

下面的示例演示如何删除文件和目录。

C# 复制代码
// Simple synchronous file deletion operations with no user interface.
// To run this sample, create the following files on your drive:
// C:\Users\Public\DeleteTest\test1.txt
// C:\Users\Public\DeleteTest\test2.txt
// C:\Users\Public\DeleteTest\SubDir\test2.txt

public class SimpleFileDelete
{
    static void Main()
    {
        // Delete a file by using File class static method...
        if(System.IO.File.Exists(@"C:\Users\Public\DeleteTest\test.txt"))
        {
            // Use a try block to catch IOExceptions, to
            // handle the case of the file already being
            // opened by another process.
            try
            {
                System.IO.File.Delete(@"C:\Users\Public\DeleteTest\test.txt");
            }
            catch (System.IO.IOException e)
            {
                Console.WriteLine(e.Message);
                return;
            }
        }

        // ...or by using FileInfo instance method.
        System.IO.FileInfo fi = new System.IO.FileInfo(@"C:\Users\Public\DeleteTest\test2.txt");
        try
        {
            fi.Delete();
        }
        catch (System.IO.IOException e)
        {
            Console.WriteLine(e.Message);
        }

        // Delete a directory. Must be writable or empty.
        try
        {
            System.IO.Directory.Delete(@"C:\Users\Public\DeleteTest");
        }
        catch (System.IO.IOException e)
        {
            Console.WriteLine(e.Message);
        }
        // Delete a directory and all subdirectories with Directory static method...
        if(System.IO.Directory.Exists(@"C:\Users\Public\DeleteTest"))
        {
            try
            {
                System.IO.Directory.Delete(@"C:\Users\Public\DeleteTest", true);
            }

            catch (System.IO.IOException e)
            {
                Console.WriteLine(e.Message);
            }
        }

        // ...or with DirectoryInfo instance method.
        System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(@"C:\Users\Public\public");
        // Delete this dir and all subdirs.
        try
        {
            di.Delete(true);
        }
        catch (System.IO.IOException e)
        {
            Console.WriteLine(e.Message);
        }

    }
}

请参见

任务

如何：提供文件操作进度对话框（C# 编程指南）

概念

C# 编程指南
通用 I/O 任务

参考

System.IO

其他资源

文件系统和注册表（C# 编程指南）
文件和流 I/O

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


如何：提供文件操作进度对话框（C# 编程指南）

[image]

C# 编程指南
如何：提供文件操作进度对话框（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

为所执行的文件操作提供标准 Windows 进度对话框的最简单的方法是使用 Microsoft.VisualBasic 命名空间中提供的 FileSystem 对象。

说明：

对于在以下说明中使用的某些 Visual Studio 用户界面元素，您的计算机可能会显示不同的名称或位置。这些元素取决于您使用的 Visual Studio 版本及设置。有关更多信息，请参见Visual Studio 设置。

添加对 Microsoft.VisualBasic.dll 的引用

  • 在 Visual Studio 中，打开或创建一个项目，然后单击“项目”菜单上的“添加引用”。在“.NET”选项卡上的“组件名称”栏中选择“Microsoft.VisualBasic”。

添加 using 指令

  • 在您的 C# 文件中添加下面这行代码：

    C# 复制代码
    using Microsoft.VisualBasic.FileIO;

示例

下面的代码将 sourcePath 指定的目录复制到destinationPath 指定的目录中。它还提供一个标准 Windows 进度对话框，该对话框显示预计完成操作还需要的时间。

C# 复制代码
// Requires project reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.FileIO;
class FileProgress
{
    static void Main()
    {
        string sourcePath = @"C:\Users\public\documents\";
        string destinationPath = @"C:\testFolder";
        FileSystem.CopyDirectory(sourcePath, destinationPath,
            UIOption.AllDialogs);
    }
}

请参见

其他资源

文件系统和注册表（C# 编程指南）

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 新增主题。 | 内容 Bug 修复


如何：写入文本文件（C# 编程指南）

[image]

C# 编程指南
如何：写入文本文件（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

以下示例给出了将文本写入文件的各种方法。前两个示例使用 System.IO..::.File 类中的静态方法将完整的字符串数组或完整字符串写入文本文件。示例 #3 说明如果在将文本写入文件之前必须分别处理文本的每一行，应该如何将这些文本添加到文件中。示例 1-3 均覆盖文件中的所有现有内容。示例 #4 说明如何将文本追加到现有文件的末尾。

示例

C# 复制代码
class WriteTextFile
{
    static void Main()
    {

        // These examples assume a "C:\Users\Public\TestFolder" folder on your machine.
        // You can modify the path if necessary.

        // Example #1: Write an array of strings to a file.
        // Create a string array that consists of three lines.
        string[] lines = {"First line", "Second line", "Third line"};
        System.IO.File.WriteAllLines(@"C:\Users\Public\TestFolder\WriteLines.txt", lines);

        // Example #2: Write one string to a text file.
        string text = "A class is the most powerful data type in C#. Like structures, " +
                       "a class defines the data and behavior of the data type. ";
        System.IO.File.WriteAllText(@"C:\Users\Public\TestFolder\WriteText.txt", text);

        // Example #3: Write only some strings in an array to a file.
        using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\Users\Public\TestFolder\WriteLines2.txt"))
        {
            foreach (string line in lines)
            {
                if (line.Contains("Second") == false)
                {
                    file.WriteLine(line);
                }
            }
        }

        // Example #4: Append new text to an existing file
        using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\Users\Public\TestFolder\WriteLines2.txt", true))
        {
            file.WriteLine("Fourth line");
        }
    }
}
/* Output (to WriteLines.txt):
    First line
    Second line
    Third line

 Output (to WriteText.txt):
    A class is the most powerful data type in C#. Like structures, a class defines the data and behavior of the data type.

 Output to WriteLines2.txt after Example #3:
    First line
    Third line

 Output to WriteLines2.txt after Example #4:
    First line
    Third line
    Fourth line
 */

编译代码

将代码复制到控制台应用程序中。

将 "c:\testdir" 替换为您计算机上的实际文件夹名称，或者创建一个此名称的文件夹。

可靠编程

以下情况可能会导致异常：

  • 文件已存在并且为只读。

  • 路径名可能太长。

  • 磁盘可能已满。

请参见

概念

C# 编程指南

其他资源

文件系统和注册表（C# 编程指南）


如何：读取文本文件（C# 编程指南）

[image]

C# 编程指南
如何：读取文本文件（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例使用 System.IO..::.File 类中的静态方法 ReadAllText 和 ReadAllLines 读取文本文件的内容。

说明：

本示例使用的是在主题如何：写入文本文件（C# 编程指南）中创建的文件。

示例

C# 复制代码
class ReadFromFile
{
    static void Main()
    {
        // The files used here were created in the code example
        // in How to: Write to a Text File. You can of course substitute
        // other files of your own.

        // Example #1
        // Read the file as one string.
        string text = System.IO.File.ReadAllText(@"C:\Users\Public\TestFolder\WriteText.txt");

        // Display the file contents to the console.
        System.Console.WriteLine("Contents of writeText.txt = {0}", text);

        // Example #2
        // Read the file lines into a string array.
        string[] lines = System.IO.File.ReadAllLines(@"C:\Users\Public\TestFolder\WriteLines2.txt");

        System.Console.WriteLine("Contents of writeLines2.txt =:");
        foreach (string line in lines)
        {
            Console.WriteLine("\t" + line);
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}

编译代码

将代码复制并粘贴到控制台应用程序中。

将 "c:\testdir" 替换为实际的文件夹名。

可靠编程

以下情况可能会导致异常：

  • 该文件可能不存在。

安全性

不要依赖文件名来确定文件内容。例如，文件 myFile.cs 可能不是 C# 源文件。

请参见

概念

C# 编程指南

参考

System.IO

其他资源

文件系统和注册表（C# 编程指南）


如何：一次读取一行文本文件 (Visual C#)

[image]

C# 编程指南
如何：一次读取一行文本文件 (Visual C#)
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例使用 StreamReader 类的 ReadLine 方法将文本文件的内容读取（一次读取一行）到字符串中。所有文本行都保存在字符串 line 中并显示在屏幕上。

示例

复制代码
int counter = 0;
string line;

// Read the file and display it line by line.
System.IO.StreamReader file =
    new System.IO.StreamReader(@"c:\test.txt");
while((line = file.ReadLine()) != null)
{
    System.Console.WriteLine (line);
    counter++;
}

file.Close();
System.Console.WriteLine("There were {0} lines.", counter);
// Suspend the screen.
System.Console.ReadLine();

编译代码

复制该代码，并将其粘贴到控制台应用程序的 Main 方法中。

将 "c:\test.txt" 替换为实际的文件名。

可靠编程

以下情况可能会导致异常：

  • 该文件可能不存在。

安全性

不要根据文件的名称来判断文件的内容。例如，文件 myFile.cs 可能不是 C# 源文件。

请参见

概念

C# 编程指南

参考

System.IO

其他资源

文件系统和注册表（C# 编程指南）


如何：在注册表中创建注册表项 (Visual C#)

[image]

C# 编程指南
如何：在注册表中创建注册表项 (Visual C#)
示例 另请参见 发送反馈意见

更新：2007 年 11 月

本示例将值对“Name”和“Isabella”添加到当前用户的注册表中的注册表项“Names”之下。

示例

复制代码
Microsoft.Win32.RegistryKey key;
key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Names");
key.SetValue("Name", "Isabella");
key.Close();

编译代码

  • 复制该代码，并将其粘贴到控制台应用程序的 Main 方法中。

  • 将 Names 参数替换为直接存在于注册表 HKEY_CURRENT_USER 节点下的项的名称。

  • 将 Name 参数替换为直接存在于“Names”节点下的值的名称。

可靠编程

检查注册表结构以查找适合您的项的位置。例如，您可能需要打开当前用户的 Software 项，并且用您的公司的名称创建一项。然后将注册表值添加到您的公司的项上。

以下情况可能会导致异常：

  • 注册表项的名称为空。

  • 用户没有创建注册表项的权限。

  • 注册表项名称超过 255 个字符的限制。

  • 注册表项已关闭。

  • 注册表项是只读的。

安全性

与将数据写入本地计算机 (Microsoft.Win32.Registry.LocalMachine) 相比，将数据写入用户文件夹 (Microsoft.Win32.Registry.CurrentUser) 更安全。

在您创建注册表值时，需要确定如果该值已存在则应执行的操作。另一进程（可能是恶意进程）可能已创建了该值，并拥有对该值的访问权。将数据放入注册表值后，其他进程就可以使用这些数据了。若要防止出现这种情况，请使用 Overload:Microsoft.Win32.RegistryKey.GetValue 方法。如果注册表项不存在，该方法将返回 null。

即使注册表项受访问控制列表 (ACL) 的保护，在注册表中以纯文本形式存储机密信息（例如密码）也不安全。

请参见

概念

C# 编程指南

参考

System.IO

其他资源

文件系统和注册表（C# 编程指南）


如何：写入 Application 事件日志 (Visual C#)

[image]

C# 编程指南
如何：写入 Application 事件日志 (Visual C#)
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例将字符串写入本地计算机上的应用程序事件日志。

示例

复制代码
System.Diagnostics.EventLog appLog =
    new System.Diagnostics.EventLog() ;
appLog.Source = "This Application's Name";
appLog.WriteEntry("An entry to the Application event log.");

编译代码

复制该代码，并将其粘贴到控制台应用程序的 Main 方法中。

可靠编程

指定的源只能与一个事件日志 (ArgumentException) 关联。当调用 WriteEntry 方法时将引发异常。

安全性

在部分信任的情况下可能会引发 SecurityException 类。

请参见

概念

C# 编程指南

参考

System.IO

其他资源

文件系统和注册表（C# 编程指南）


互操作性（C# 编程指南）

[image]

C# 编程指南
互操作性（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

互操作性使您能够保留和利用在现有非托管代码中的投入。运行在公共语言运行库 (CLR) 的控制之下的代码称为“托管代码”，运行在 CLR 之外的代码称为“非托管代码”。COM、COM+、C++ 组件、ActiveX 组件和 Microsoft Win32 API 都是非托管代码的示例。

.NET Framework 通过平台调用服务、System.Runtime.InteropServices 命名空间、C++ 互操作性和 COM 互操作性（COM 互操作）来实现与非托管代码的互操作性。

本节内容

互操作性概述（C# 编程指南）

介绍在 C# 托管代码和非托管代码之间进行互操作的方法。

如何：使用平台调用播放波形文件（C# 编程指南）

介绍如何使用平台调用服务在 Windows 操作系统中播放 .wav 声音文件。

COM 类示例（C# 编程指南）

演示如何将 C# 类作为 COM 对象公开。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各节：

  • 10.6.7 外部方法

  • 17.5 互操作的属性

  • 18.9 动态内存分配

请参见

概念

C# 编程指南

其他资源

与非托管代码交互操作
.NET Compact Framework 中的互操作性


互操作性概述（C# 编程指南）

[image]

C# 编程指南
互操作性概述（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

本主题描述在 C# 托管和非托管代码之间实现互操作性的方法。

平台调用

平台调用是一种服务，它使托管代码可以调用在动态链接库 (DLL)（如 Microsoft Win32 API 中的那些 DLL）中实现的非托管函数。此服务将查找并调用导出的函数，然后根据需要跨越互用边界封送其参数（整数、字符串、数组、结构等）。

有关更多信息，请参见使用非托管 DLL 函数和如何：使用平台调用播放波形文件（C# 编程指南）。

说明：

公共语言运行库 (CLR) 管理对系统资源的访问。调用 CLR 外部的非托管代码会避开此安全机制，因此会带来安全风险。例如，非托管代码可能会直接调用非托管代码中的资源，从而避开 CLR 安全机制，。有关更多信息，请参见 .NET Framework Security（.NET Framework 安全性）。

C++ Interop

可以使用 C++ Interop（又称为 It Just Works (IJW)）包装本机 C++ 类，使得用 C# 或其他 .NET Framework 语言编写的代码可以使用它。为此，可以编写 C++ 代码来包装本机 DLL 或 COM 组件。与其他 .NET Framework 语言不同，Visual C++ 支持互操作性，允许托管代码和非托管代码存在于同一个应用程序中，甚至存在于同一个文件中。然后，可以使用 /clr 编译器开关生成 C++ 代码，从而生成托管程序集。最后，在 C# 项目中添加一个对该程序集的引用，并像使用其他托管类那样使用被包装对象。

有关更多信息，请参见如何：包装本机类以供 C# 使用和使用 C++ Interop（隐式 PInvoke）。

向 C# 公开 COM 组件

可以使用 C# 项目中的 COM 组件。一般步骤如下所示：

  1. 找到要使用的 COM 组件并注册它。使用 regsvr32.exe 注册或注销 COM DLL。

  2. 在项目中添加对 COM 组件或类型库的引用。

    添加引用时，Visual Studio 会用到类型库导入程序 (Tlbimp.exe)，后者将类型库作为输入并输出一个 .NET Framework 互操作程序集。该程序集又称为运行时可调用包装 (RCW)，其中包含了包装类型库中的 COM 类和接口的托管类和接口。Visual Studio 在项目中添加对已生成程序集的引用。

  3. 创建在 RCW 中定义的类的实例。而这样会创建 COM 对象的实例。

  4. 像使用其他托管对象那样使用该对象。当垃圾回收对该对象进行回收后，COM 对象的实例也会从内存中释放出来。

有关更多信息，请参见向 .NET Framework 公开 COM 组件。

向 COM 公开 C#

COM 客户端可以使用已经正确公开的 C# 类型。公开 C# 类型的基本步骤如下所示：

  1. 在 C# 项目中添加互操作属性。

    可以通过修改 Visual C# 项目属性使程序集 COM 可见。有关更多信息，请参见“程序集信息”对话框。

  2. 生成 COM 类型库并对它进行注册以供 COM 使用。

    可以修改 Visual C# 项目属性，以便自动为 COM 互操作注册 C# 程序集。Visual Studio 使用程序集注册工具 (Regasm.exe) 以及 /tlb 命令行开关，后者将托管程序集作为输入以生成类型库。此类型库描述程序集中的 public 类型并添加注册表项，以便 COM 客户端可以创建托管类。有关更多信息，请参见如何：为 COM Interop 注册组件。

有关更多信息，请参见向 COM 公开 .NET Framework 组件和COM 类示例（C# 编程指南）。

请参见

概念

C# 编程指南

其他资源

与非托管代码交互操作
高级 COM 互操作性
.NET Compact Framework 中的互操作性
Improving Interop Performance（提高互操作性能）
COM 互操作介绍
托管代码与非托管代码之间的封送处理

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 文档更新以包含更多互操作性的类型。 | 信息补充。


如何：使用平台调用播放波形文件（C# 编程指南）

[image]

C# 编程指南
如何：使用平台调用播放波形文件（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的 C# 代码示例演示如何在 Windows 操作系统上使用平台调用服务来播放波形声音文件。

示例

此代码示例使用 DllImport 将 winmm.dll 的 PlaySound 方法入口点作为 Form1 PlaySound() 导入。该示例具有一个带有一个按钮的简单 Windows 窗体。单击该按钮可打开标准 Windows OpenFileDialog 对话框，以便打开要播放的文件。选择波形文件后，将使用 winmm.DLL 程序集方法的 PlaySound() 方法来播放此文件。有关 winmm.dll 的 PlaySound 方法的更多信息，请参见使用 PlaySound 函数播放波形音频文件。浏览并选择一个带 .wav 扩展名的文件，然后单击“打开”以使用平台调用播放该波形文件。将在文本框中显示选择的文件的完整路径。

“打开文件”对话框将使用以下筛选器设置只显示具有 .wav 扩展名的文件：

C# 复制代码
dialog1.Filter = "Wav Files (*.wav)|*.wav";
C# 复制代码
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WinSound
{
    public partial class Form1 : Form
    {
        private TextBox textBox1;
        private Button button1;

        public Form1()  //constructor
        {
            InitializeComponent();
        }

        [System.Runtime.InteropServices.DllImport("winmm.DLL", EntryPoint = "PlaySound", SetLastError = true, CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true)]
        private static extern bool PlaySound(string szSound, System.IntPtr hMod, PlaySoundFlags flags);

        [System.Flags]
        public enum PlaySoundFlags : int
        {
            SND_SYNC = 0x0000,
            SND_ASYNC = 0x0001,
            SND_NODEFAULT = 0x0002,
            SND_LOOP = 0x0008,
            SND_NOSTOP = 0x0010,
            SND_NOWAIT = 0x00002000,
            SND_FILENAME = 0x00020000,
            SND_RESOURCE = 0x00040004
        }

        private void button1_Click (object sender, System.EventArgs e)
        {
            OpenFileDialog dialog1 = new OpenFileDialog();

            dialog1.Title = "Browse to find sound file to play";
            dialog1.InitialDirectory = @"c:\";
            dialog1.Filter = "Wav Files (*.wav)|*.wav";
            dialog1.FilterIndex = 2;
            dialog1.RestoreDirectory = true;

            if(dialog1.ShowDialog() == DialogResult.OK)
            {
                textBox1.Text = dialog1.FileName;
                PlaySound (dialog1.FileName, new System.IntPtr(), PlaySoundFlags.SND_SYNC);
            }
        }
    }
}

编译代码

编译代码

  1. 在 Visual Studio 中创建一个新的 C# Windows 应用程序项目，并命名为 WinSound。

  2. 复制上面的代码并将其粘贴到 Form1.cs 文件中以覆盖原来的内容。

  3. 复制下面的代码，并将它粘贴到 Form1.Designer.cs 文件的 InitializeComponent() 方法中现有代码的后面。

    C# 复制代码
    this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(192, 40);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(88, 24);
this.button1.TabIndex = 0;
this.button1.Text = "Browse";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(8, 40);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(168, 20);
this.textBox1.TabIndex = 1;
this.textBox1.Text = "FIle path";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Platform Invoke WinSound C#";
this.ResumeLayout(false);
this.PerformLayout();

  4. 编译并运行代码。

安全性

有关更多信息，请参见 .NET Framework Security（.NET Framework 安全性）。

请参见

任务

平台调用技术示例

概念

C# 编程指南

参考

互操作性概述（C# 编程指南）

其他资源

用平台调用封送数据


COM 类示例（C# 编程指南）

[image]

C# 编程指南
COM 类示例（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面是一个公开为 COM 对象的类的示例。将这些代码放置到 .cs 文件中并添加到您的项目中后，请将“Register for COM Interop”属性设置为“True”。有关更多信息，请参见 如何：为 COM Interop 注册组件。

向 COM 公开 Visual C# 对象要求声明一个类接口、一个事件接口（如果需要）和类本身。类成员必须遵循下列规则才能对 COM 可见：

  • 类必须是公共的。

  • 属性、方法和事件必须是公共的。

  • 属性和方法必须在类接口上声明。

  • 事件必须在事件接口中声明。

其他没有在这些接口中声明的类的公共成员对于 COM 是不可见的，但它们对于其他 .NET Framework 对象将是可见的。

若要向 COM 公开属性 (Property) 和方法，必须在类接口上声明这些属性 (Property) 和方法，并用 DispId 属性 (Attribute) 予以标记，然后在类中实现它们。成员在接口中声明的顺序即是用于 COM vtable 的顺序。

若要从类中公开事件，必须在事件接口上声明这些事件，并用 DispId 属性予以标记。该类不应实现此接口。

类实现类接口；它可以实现多个接口，但第一个实现将作为默认类接口。在此处实现向 COM 公开的方法和属性。它们必须标记为是公共的，并且必须与类接口中的声明匹配。同时，在此处声明由类引发的事件。它们必须标记为是公共的，并且必须与事件接口中的声明匹配。

有关更多信息，请参见 “COM Interop 第一部分”示例、“COM Interop 第二部分”示例 和 COM 互操作性示例。

示例

C# 复制代码
using System.Runtime.InteropServices;

namespace project_name
{
    [Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
    public interface ComClass1Interface
    {
    }

    [Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
        InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface ComClass1Events
    {
    }

    [Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
        ClassInterface(ClassInterfaceType.None),
        ComSourceInterfaces(typeof(ComClass1Events))]
    public class ComClass1 : ComClass1Interface
    {
    }
}

请参见

概念

C# 编程指南

参考

互操作性（C# 编程指南）
“项目设计器”->“生成”页 (C#)


线程处理（C# 编程指南）

[image]

C# 编程指南
线程处理（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

线程处理使 C# 程序能够执行并发处理，以便您可以同时执行多个操作。例如，您可以使用线程处理来监视用户输入，执行后台任务，以及处理并发输入流。System.Threading 命名空间提供支持多线程编程的类和接口，使您可以轻松地执行创建和启动新线程，同步多个线程，挂起线程以及中止线程等任务。

若要在 C# 代码中合并线程处理，请创建一个将在主线程外执行的函数，并让一个新的 Thread 对象指向该函数。下面的代码示例在 C# 应用程序中创建一个新线程：

C# 复制代码
System.Threading.Thread newThread;
newThread = new System.Threading.Thread(anObject.AMethod);

下面的代码示例在 C# 应用程序中启动一个新线程：

C# 复制代码
newThread.Start();

多线程处理可解决响应性和多任务的问题，但同时引入了资源共享和同步问题，因为根据对机制进行调度的中央线程，线程将在没有警告的情况下中断和继续。有关更多信息，请参见线程同步（C# 编程指南）。有关概述信息，请参见使用线程和线程处理。

概述

线程具有以下特点：

  • 线程使 C# 程序能够执行并发处理。

  • .NET Framework 的 System.Threading 命名空间使线程更易于使用。

  • 线程共享应用程序的资源。有关更多信息，请参见 使用线程和线程处理。

相关章节

有关更多信息，请参见下列主题：

  • 使用线程处理（C# 编程指南）

  • 如何：创建和终止线程（C# 编程指南）

  • 如何：使用线程池（C# 编程指南）

  • 如何：对制造者线程和使用者线程进行同步（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.10 执行顺序

  • 8.12 lock 语句

  • 10.5.3 可变字段

  • 10.8.1 类似字段的事件

请参见

任务

监视器同步技术示例
等待同步技术示例

概念

C# 编程指南
Mutex
监视器
互锁操作
AutoResetEvent

参考

Thread
Mutex
委托（C# 编程指南）

其他资源

如何：通过使用 Visual C# .NET 在多线程处理环境中同步对共享资源的访问


使用线程处理（C# 编程指南）

[image]

C# 编程指南
使用线程处理（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

默认情况下，C# 程序具有一个线程。此线程执行程序中以 Main 方法开始和结束的代码。Main 直接或间接执行的每一个命令都由默认线程（或主线程）执行，当 Main 返回时此线程也将终止。不过，可以创建辅助线程，以便与主线程一起并行执行代码。这些线程通常称为“辅助线程”。

辅助线程可以用于执行耗时较多的任务或时间要求紧迫的任务，而不必占用主线程。例如，辅助线程经常用在服务器应用程序中，以便无需等待前面的请求完成即可响应传入的请求。辅助线程还可用于在桌面应用程序中执行“后台”任务，以便使主线程（用于驱动用户界面元素）保持对用户操作的响应。

多线程处理解决了吞吐量和响应性的问题，但同时也带来了资源共享问题，如死锁和争用状态。多线程特别适用于需要不同资源（如文件句柄和网络连接）的任务。为单个资源分配多个线程可能会导致同步问题，线程会被频繁阻止以等待其他线程，从而与使用多线程的初衷背道而驰。

常见的策略是使用辅助线程执行不需要大量占用其他线程所使用的资源的、耗时较多的任务或时间要求紧迫的任务。实际上，程序中的某些资源必须由多个线程访问。考虑到这些情况，System.Threading 命名空间提供了用于同步线程的类。这些类包括 Mutex、Monitor、Interlocked、AutoResetEvent 和 ManualResetEvent。

您可以使用这些类中的部分或所有类来同步多个线程的活动，但是某些多线程处理支持由 C# 语言提供。例如，C# 中的 Lock 语句通过隐式使用 Monitor 来提供同步功能。

下面的主题演示了常用的多线程处理技术：

  • 如何：创建和终止线程（C# 编程指南）

  • 如何：对制造者线程和使用者线程进行同步（C# 编程指南）

  • 如何：使用线程池（C# 编程指南）

相关章节

有关更多信息，请参见：

  • 如何：使用 Visual C# .NET 创建线程

  • 如何：使用 Visual C# .NET 向线程池提交工作项

  • 如何：通过使用 Visual C# .NET 在多线程处理环境中同步对共享资源的访问

  • 线程与线程处理

  • 为多线程处理同步数据

请参见

任务

监视器同步技术示例
等待同步技术示例
“线程”示例

概念

C# 编程指南
监视器
互锁操作
AutoResetEvent

参考

Thread
Mutex

其他资源

托管线程处理
使用线程和线程处理
线程示例


线程同步（C# 编程指南）

[image]

C# 编程指南
线程同步（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

以下各节描述了在多线程应用程序中可以用来同步资源访问的功能和类。

在应用程序中使用多个线程的一个好处是每个线程都可以异步执行。对于 Windows 应用程序，耗时的任务可以在后台执行，而使应用程序窗口和控件保持响应。对于服务器应用程序，多线程处理提供了用不同线程处理每个传入请求的能力。否则，在完全满足前一个请求之前，将无法处理每个新请求。

然而，线程的异步特性意味着必须协调对资源（如文件句柄、网络连接和内存）的访问。否则，两个或更多的线程可能在同一时间访问相同的资源，而每个线程都不知道其他线程的操作。结果将产生不可预知的数据损坏。

对于整数数据类型的简单操作，可以用 Interlocked 类的成员来实现线程同步。对于其他所有数据类型和非线程安全的资源，只有使用本主题中的结构才能安全地执行多线程处理。

有关多线程编程的背景信息，请参见：

  • 使用线程处理（C# 编程指南）

  • 托管线程处理基本知识

  • 使用线程和线程处理

  • 托管线程处理的最佳做法

lock 关键字

lock 关键字可以用来确保代码块完成运行，而不会被其他线程中断。这是通过在代码块运行期间为给定对象获取互斥锁来实现的。

lock 语句以关键字 lock 开头，它有一个作为参数的对象，在该参数的后面还有一个一次只能由一个线程执行的代码块。例如：

C# 复制代码
public class TestThreading
{
    private System.Object lockThis = new System.Object();

    public void Function()
    {

        lock (lockThis)
        {
            // Access thread-sensitive resources.
        }
    }

}

提供给 lock 关键字的参数必须为基于引用类型的对象，该对象用来定义锁的范围。在上面的示例中，锁的范围限定为此函数，因为函数外不存在任何对对象 lockThis 的引用。如果确实存在此类引用，锁的范围将扩展到该对象。严格地说，提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源，所以它可以是任意类实例。然而，实际上，此对象通常表示需要进行线程同步的资源。例如，如果一个容器对象将被多个线程使用，则可以将该容器传递给 lock，而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器，则对该对象的访问将是安全同步的。

通常，最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如，如果该实例可以被公开访问，则 lock(this) 可能会有问题，因为不受控制的代码也可能会锁定该对象。这可能导致死锁，即两个或更多个线程等待释放同一对象。出于同样的原因，锁定公共数据类型（相比于对象）也可能导致问题。锁定字符串尤其危险，因为字符串被公共语言运行时 (CLR)“暂留”。这意味着整个程序中任何给定字符串都只有一个实例，就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此，只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁，就将锁定应用程序中该字符串的所有实例。因此，最好锁定不会被暂留的私有或受保护成员。某些类提供专门用于锁定的成员。例如，Array 类型提供 SyncRoot。许多集合类型也提供 SyncRoot。

有关 lock 关键字的更多信息，请参见：

  • lock 语句（C# 参考）

  • 如何：对制造者线程和使用者线程进行同步（C# 编程指南）

监视器

与 lock 关键字类似，监视器防止多个线程同时执行代码块。Enter 方法允许一个且仅一个线程继续执行后面的语句；其他所有线程都将被阻止，直到执行语句的线程调用 Exit。这与使用 lock 关键字一样。事实上，lock 关键字就是用 Monitor 类来实现的。例如：

C# 复制代码
lock (x)
{
    DoSomething();
}

这等效于：

C# 复制代码
System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
    DoSomething();
}
finally
{
    System.Threading.Monitor.Exit(obj);
}

使用 lock 关键字通常比直接使用 Monitor 类更可取，一方面是因为 lock 更简洁，另一方面是因为 lock 确保了即使受保护的代码引发异常，也可以释放基础监视器。这是通过 finally 关键字来实现的，无论是否引发异常它都执行关联的代码块。

有关监视器的更多信息，请参见监视器同步技术示例。

同步事件和等待句柄

使用锁或监视器对于防止同时执行区分线程的代码块很有用，但是这些构造不允许一个线程向另一个线程传达事件。这需要“同步事件”，它是有两个状态（终止和非终止）的对象，可以用来激活和挂起线程。让线程等待非终止的同步事件可以将线程挂起，将事件状态更改为终止可以将线程激活。如果线程试图等待已经终止的事件，则线程将继续执行，而不会延迟。

同步事件有两种：AutoResetEvent 和 ManualResetEvent。它们之间唯一的不同在于，无论何时，只要 AutoResetEvent 激活线程，它的状态将自动从终止变为非终止。相反，ManualResetEvent 允许它的终止状态激活任意多个线程，只有当它的 Reset 方法被调用时才还原到非终止状态。

可以通过调用 WaitOne、WaitAny 或 WaitAll 等中的某个等待方法使线程等待事件。WaitHandle..::.WaitOne()()() 使线程一直等待，直到单个事件变为终止状态；WaitHandle..::.WaitAny()()() 阻止线程，直到一个或多个指示的事件变为终止状态；WaitHandle..::.WaitAll()()() 阻止线程，直到所有指示的事件都变为终止状态。当调用事件的 Set 方法时，事件将变为终止状态。

在下面的示例中，创建了一个线程，并由 Main 函数启动该线程。新线程使用 WaitOne 方法等待一个事件。在该事件被执行 Main 函数的主线程终止之前，该线程一直处于挂起状态。一旦该事件终止，辅助线程将返回。在本示例中，因为事件只用于一个线程的激活，所以使用 AutoResetEvent 或 ManualResetEvent 类都可以。

C# 复制代码
using System;
using System.Threading;

class ThreadingExample
{
    static AutoResetEvent autoEvent;

    static void DoWork()
    {
        Console.WriteLine("   worker thread started, now waiting on event...");
        autoEvent.WaitOne();
        Console.WriteLine("   worker thread reactivated, now exiting...");
    }

    static void Main()
    {
        autoEvent = new AutoResetEvent(false);

        Console.WriteLine("main thread starting worker thread...");
        Thread t = new Thread(DoWork);
        t.Start();

        Console.WriteLine("main thread sleeping for 1 second...");
        Thread.Sleep(1000);

        Console.WriteLine("main thread signaling worker thread...");
        autoEvent.Set();
    }
}

有关线程同步事件用法的更多示例，请参见：

  • 监视器同步技术示例

  • 读取器－编写器同步技术示例

  • 线程池技术示例

  • 等待同步技术示例

Mutex 对象

mutex 与监视器类似；它防止多个线程在某一时间同时执行某个代码块。事实上，名称“mutex”是术语“互相排斥 (mutually exclusive)”的简写形式。然而与监视器不同的是，mutex 可以用来使跨进程的线程同步。mutex 由 Mutex 类表示。

当用于进程间同步时，mutex 称为“命名 mutex”，因为它将用于另一个应用程序，因此它不能通过全局变量或静态变量共享。必须给它指定一个名称，才能使两个应用程序访问同一个 mutex 对象。

尽管 mutex 可以用于进程内的线程同步，但是使用 Monitor 通常更为可取，因为监视器是专门为 .NET Framework 而设计的，因而它可以更好地利用资源。相比之下，Mutex 类是 Win32 构造的包装。尽管 mutex 比监视器更为强大，但是相对于 Monitor 类，它所需要的互操作转换更消耗计算资源。有关 mutex 的用法示例，请参见 Mutex。

相关章节

  • 如何：创建和终止线程（C# 编程指南）

  • 如何：使用线程池（C# 编程指南）

  • 如何：使用 Visual C# .NET 同步对多线程环境中共享资源的访问

  • 如何：使用 Visual C# .NET 创建线程

  • 如何：使用 Visual C# .NET 将工作项提交到线程池

  • 如何：使用 Visual C# .NET 同步对多线程环境中共享资源的访问

请参见

概念

C# 编程指南

参考

Thread
WaitOne
WaitAny
WaitAll
Monitor
Mutex
AutoResetEvent
ManualResetEvent
Interlocked
WaitHandle

其他资源

实现 CLR 异步编程模型
通过 C# 简化 APM
死锁监控


如何：创建和终止线程（C# 编程指南）

[image]

C# 编程指南
如何：创建和终止线程（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此示例演示如何创建辅助线程，并用它与主线程并行执行处理。还将演示如何使一个线程等待另一个线程，并正确地终止线程。有关多线程处理的背景信息，请参见托管线程处理和使用线程处理（C# 编程指南）。

该示例创建一个名为 Worker 的类，该类包含辅助线程将执行的方法 DoWork。这实际上是辅助线程的 Main 函数。辅助线程将通过调用此方法来开始执行，并在此方法返回时自动终止。DoWork 方法如下所示：

C# 复制代码
public void DoWork()
{
    while (!_shouldStop)
    {
        Console.WriteLine("worker thread: working...");
    }
    Console.WriteLine("worker thread: terminating gracefully.");
}

Worker 类包含另一个方法，该方法用于通知 DoWork 它应当返回。此方法名为 RequestStop，如下所示：

C# 复制代码
public void RequestStop()
{
    _shouldStop = true;
}

RequestStop 方法只是将 true 赋给 _shouldStop 数据成员。由于此数据成员由 DoWork 方法来检查，因此这会间接导致 DoWork 返回，从而终止辅助线程。但是，您应当注意：DoWork 和 RequestStop 将由不同线程执行。DoWork 由辅助线程执行，而 RequestStop 由主线程执行，因此 _shouldStop 数据成员声明为 volatile，如下所示：

C# 复制代码
private volatile bool _shouldStop;

volatile 关键字用于通知编译器，将有多个线程访问 _shouldStop 数据成员，因此它不应当对此成员的状态做任何优化假设。有关更多信息，请参见volatile（C# 参考）。

通过将 volatile 与 _shouldStop 数据成员一起使用，可以从多个线程安全地访问此成员，而不需要使用正式的线程同步技术，但这仅仅是因为 _shouldStop 是 bool。这意味着只需要执行单个原子操作就能修改 _shouldStop。但是，如果此数据成员是类、结构或数组，那么，从多个线程访问它可能会导致间歇的数据损坏。假设有一个更改数组中的值的线程。Windows 会定期中断线程，以便允许其他线程执行。因此，此线程会在分配某些数组元素之后和分配其他元素之前被停止。由于数组现在有了一个程序员从不想要的状态，因此，读取此数组的另一个线程可能会失败。

在实际创建辅助线程之前，Main 函数会创建一个 Worker 对象和 Thread 的一个实例。线程对象被配置为：通过将对 Worker.DoWork 方法的引用传递给 Thread 构造函数，来将该方法用作入口点，如下所示：

C# 复制代码
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork);

此时，尽管辅助线程对象已存在并已配置，但尚未创建实际的辅助线程。只有当 Main 调用 Start 方法后，才会创建实际的辅助线程：

C# 复制代码
workerThread.Start();

此时，系统将启动辅助线程的执行，但这是在与主线程异步执行的。这意味着 Main 函数将在辅助线程进行初始化的同时继续执行代码。为了确保 Main 函数不会尝试在辅助线程有机会执行之前将它终止，Main 函数将一直循环，直到辅助线程对象的 IsAlive 属性设置为 true：

C# 复制代码
while (!workerThread.IsAlive);

下一步，通过调用 Sleep 来将主线程停止片刻。这确保了辅助线程的 DoWork 函数在 Main 函数执行其他任何命令之前，在 DoWork 方法内部执行若干次循环：

C# 复制代码
Thread.Sleep(1);

在 1 毫秒之后，Main 将通知辅助线程对象，它应当使用 Worker.RequestStop 方法（前面已介绍）自行终止：

C# 复制代码
workerObject.RequestStop();

还可以通过调用 Abort 从另一个线程中终止某个线程。这将强行终止受影响的线程，即使该线程尚未完成其任务，并且未提供清理资源的机会。此示例中显示的技术是首选方法。

最后，Main 函数对辅助线程对象调用 Join 方法。此方法导致当前线程阻塞或等待，直到对象所表示的线程终止。因此，直到辅助线程返回后，Join 才会返回，然后自行终止：

C# 复制代码
workerThread.Join();

此时，只有执行 Main 的主线程还存在。它会显示一条最终消息，然后返回，从而使主线程也终止。

下面是完整的示例。

示例

C# 复制代码
using System;
using System.Threading;

public class Worker
{
    // This method will be called when the thread is started.
    public void DoWork()
    {
        while (!_shouldStop)
        {
            Console.WriteLine("worker thread: working...");
        }
        Console.WriteLine("worker thread: terminating gracefully.");
    }
    public void RequestStop()
    {
        _shouldStop = true;
    }
    // Volatile is used as hint to the compiler that this data
    // member will be accessed by multiple threads.
    private volatile bool _shouldStop;
}

public class WorkerThreadExample
{
    static void Main()
    {
        // Create the thread object. This does not start the thread.
        Worker workerObject = new Worker();
        Thread workerThread = new Thread(workerObject.DoWork);

        // Start the worker thread.
        workerThread.Start();
        Console.WriteLine("main thread: Starting worker thread...");

        // Loop until worker thread activates.
        while (!workerThread.IsAlive);

        // Put the main thread to sleep for 1 millisecond to
        // allow the worker thread to do some work:
        Thread.Sleep(1);

        // Request that the worker thread stop itself:
        workerObject.RequestStop();

        // Use the Join method to block the current thread
        // until the object's thread terminates.
        workerThread.Join();
        Console.WriteLine("main thread: Worker thread has terminated.");
    }
}

输出如下：

复制代码
main thread: starting worker thread...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: working...
worker thread: terminating gracefully...
main thread: worker thread has terminated

请参见

任务

“线程”示例

概念

C# 编程指南

参考

线程处理（C# 编程指南）
使用线程处理（C# 编程指南）
Thread
volatile（C# 参考）
Mutex
Monitor
Start
IsAlive
Sleep
Join
Abort

其他资源

托管线程处理
线程示例


如何：对制造者线程和使用者线程进行同步（C# 编程指南）

[image]

C# 编程指南
如何：对制造者线程和使用者线程进行同步（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

下面的示例演示使用 lock 关键字以及 AutoResetEvent 和 ManualResetEvent 类对主线程和两个辅助线程进行线程同步。有关更多信息，请参见 lock 语句（C# 参考）。

该示例创建两个辅助线程。一个线程生成元素并将它们存储在非线程安全的泛型队列中。有关更多信息，请参见 Queue<(Of <(T>)>)。另一个线程使用此队列中的项。另外，主线程定期显示队列的内容，以便该队列可由三个线程进行访问。lock 关键字用于同步对队列的访问，以确保队列的状态不会被破坏。

除了只是使用 lock 关键字来防止同时访问以外，还可以用两个事件对象提供进一步的同步。一个事件对象用来通知辅助线程终止，另一个事件对象由制造者线程用来在有新项添加到队列中时通知使用者线程。这两个事件对象封装在一个名为 SyncEvents 的类中。这使事件可以轻松传递给表示制造者线程和使用者线程的对象。SyncEvents 类按如下方式定义：

C# 复制代码
public class SyncEvents
{
    public SyncEvents()
    {

        _newItemEvent = new AutoResetEvent(false);
        _exitThreadEvent = new ManualResetEvent(false);
        _eventArray = new WaitHandle[2];
        _eventArray[0] = _newItemEvent;
        _eventArray[1] = _exitThreadEvent;
    }

    public EventWaitHandle ExitThreadEvent
    {
        get { return _exitThreadEvent; }
    }
    public EventWaitHandle NewItemEvent
    {
        get { return _newItemEvent; }
    }
    public WaitHandle[] EventArray
    {
        get { return _eventArray; }
    }

    private EventWaitHandle _newItemEvent;
    private EventWaitHandle _exitThreadEvent;
    private WaitHandle[] _eventArray;
}

对“新项”事件使用 AutoResetEvent 类，因为您希望每当使用者线程响应此事件后，此事件都能自动重置。或者，将 ManualResetEvent 类用于“退出”事件，因为您希望当此事件终止时有多个线程响应。如果您改为使用 AutoResetEvent，则仅在一个线程响应该事件以后，该事件就还原到非终止状态。另一个线程不会响应，因此在这种情况下，将无法终止。

SyncEvents 类创建两个事件，并将它们以两种不同的形式存储：一种是作为 EventWaitHandle（它是 AutoResetEvent 和 ManualResetEvent 的基类），一种是作为基于 WaitHandle 的数组。如关于使用者线程的讨论中所述，此数组是必需的，因为它使使用者线程可以响应两个事件中的任何一个。

使用者线程和制造者线程分别由名为 Consumer 和 Producer 的类表示。这两个类都定义了一个名为 ThreadRun 的方法。这些方法用作 Main 方法创建的辅助线程的入口点。

Producer 类定义的 ThreadRun 方法如下所示：

C# 复制代码
// Producer.ThreadRun
public void ThreadRun()
{
    int count = 0;
    Random r = new Random();
    while (!_syncEvents.ExitThreadEvent.WaitOne(0, false))
    {
        lock (((ICollection)_queue).SyncRoot)
        {
            while (_queue.Count < 20)
            {
                _queue.Enqueue(r.Next(0,100));
                _syncEvents.NewItemEvent.Set();
                count++;
            }
        }
    }
    Console.WriteLine("Producer thread: produced {0} items", count);
}

此方法一直循环，直到“退出线程”事件变为终止状态。此事件的状态由 WaitOne 方法使用 SyncEvents 类定义的 ExitThreadEvent 属性进行测试。在这种情况下，检查该事件的状态不会阻止当前线程，因为 WaitOne 使用的第一个参数为零，这表示该方法应立即返回。如果 WaitOne 返回 true，则说明该事件当前处于终止状态。如果是这样，ThreadRun 方法将返回，其效果相当于终止执行此方法的辅助线程。

在“退出线程”事件终止前，Producer.ThreadStart 方法将尝试在队列中保留 20 项。项只是 0 到 100 之间的一个整数。在添加新项前，必须锁定该集合，以防止使用者线程和主线程同时访问该集合。这一点是使用 lock 关键字完成的。传递给 lock 的参数是通过 ICollection 接口公开的 SyncRoot 字段。此字段专门为同步线程访问而提供。对该集合的独占访问权限被授予 lock 后面的代码块中包含的所有指令。对于制造者添加到队列中的每个新项，都将调用“新项”事件的 Set 方法。这将通知使用者线程离开挂起状态并开始处理新项。

Consumer 对象还定义名为 ThreadRun 的方法。与制造者的 ThreadRun 类似，此方法由 Main 方法创建的辅助线程执行。然而，使用者的 ThreadStart 必须响应两个事件。Consumer.ThreadRun 方法如下所示：

C# 复制代码
// Consumer.ThreadRun
public void ThreadRun()
{
    int count = 0;
    while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1)
    {
        lock (((ICollection)_queue).SyncRoot)
        {
            int item = _queue.Dequeue();
        }
        count++;
    }
    Console.WriteLine("Consumer Thread: consumed {0} items", count);
}

此方法使用 WaitAny 来阻止使用者线程，直到所提供的数组中的任意一个等待句柄变为终止状态。在这种情况下，数组中有两个句柄，一个用来终止辅助线程，另一个用来指示有新项添加到集合中。WaitAny 返回变为终止状态的事件的索引。“新项”事件是数组中的第一个事件，因此索引零表示新项。在这种情况下，检查索引 1（它指示“退出线程”事件），并使用它来确定此方法是否继续使用项。如果“新项”事件处于终止状态，您将通过 lock 获得对集合的独占访问权限并使用新项。因为此示例生成并使用数千个项，所以不显示使用的每个项，而是使用 Main 定期显示队列中的内容，如下面所演示的那样。

Main 方法首先创建一个队列（该队列的内容将被生成和使用）和 SyncEvents 的一个实例（已在前面演示）：

C# 复制代码
Queue<int> queue = new Queue<int>();
SyncEvents syncEvents = new SyncEvents();

然后，Main 配置 Producer 和 Consumer 对象以供辅助线程使用。然而，此步骤并不创建或启动实际的辅助线程：

C# 复制代码
Producer producer = new Producer(queue, syncEvents);
Consumer consumer = new Consumer(queue, syncEvents);
Thread producerThread = new Thread(producer.ThreadRun);
Thread consumerThread = new Thread(consumer.ThreadRun);

请注意，队列和同步事件对象作为构造函数参数同时传递给 Consumer 和 Producer 线程。这提供了两个对象，它们具有执行各自任务所需的共享资源。然后创建两个新的 Thread 对象，并使用每个对象的 ThreadRun 方法作为参数。每个辅助线程在启动时都将此参数用作线程的入口点。

接着，Main 通过调用 Start 方法来启动两个辅助线程，如下所示：

C# 复制代码
producerThread.Start();
consumerThread.Start();

此时，创建了两个新的辅助线程，它们独立于当前正在执行 Main 方法的主线程开始异步执行过程。事实上，Main 接下来要做的事情是通过调用 Sleep 方法将主线程挂起。该方法将当前正在执行的线程挂起指定的时间（毫秒）。在此时间间隔过后，Main 将重新激活，这时它将显示队列的内容。Main 重复此过程四次，如下所示：

C# 复制代码
for (int i=0; i<4; i++)
{
    Thread.Sleep(2500);
    ShowQueueContents(queue);
}

最后，Main 通过调用“退出线程”事件的 Set 方法通知辅助线程终止，然后对每个辅助线程调用 Join 方法以阻止主线程，直到每个辅助线程都响应该事件并终止。

有一个线程同步的最终示例：ShowQueueContents 方法。与制造者线程和使用者线程类似，此方法使用 lock 获得对队列的独占访问权限。然而在这种情况下，独占访问非常重要，因为 ShowQueueContents 对整个集合进行枚举。对集合进行枚举是一个特别容易由于异步操作而造成数据损坏的操作，因为它需要遍历整个集合的内容。

请注意，ShowQueueContents 是由主线程执行的，因为它被 Main 调用。这意味着，当此方法获得对项队列的独占访问权限时，既阻止了制造者线程访问队列，也阻止了使用者线程访问队列。ShowQueueContents 锁定队列并枚举其内容：

C# 复制代码
private static void ShowQueueContents(Queue<int> q)
{
    lock (((ICollection)q).SyncRoot)
    {
        foreach (int item in q)
        {
            Console.Write("{0} ", item);
        }
    }
    Console.WriteLine();
}

下面是完整的示例。

示例

C# 复制代码
using System;
using System.Threading;
using System.Collections;
using System.Collections.Generic;

public class SyncEvents
{
    public SyncEvents()
    {

        _newItemEvent = new AutoResetEvent(false);
        _exitThreadEvent = new ManualResetEvent(false);
        _eventArray = new WaitHandle[2];
        _eventArray[0] = _newItemEvent;
        _eventArray[1] = _exitThreadEvent;
    }

    public EventWaitHandle ExitThreadEvent
    {
        get { return _exitThreadEvent; }
    }
    public EventWaitHandle NewItemEvent
    {
        get { return _newItemEvent; }
    }
    public WaitHandle[] EventArray
    {
        get { return _eventArray; }
    }

    private EventWaitHandle _newItemEvent;
    private EventWaitHandle _exitThreadEvent;
    private WaitHandle[] _eventArray;
}
public class Producer
{
    public Producer(Queue<int> q, SyncEvents e)
    {
        _queue = q;
        _syncEvents = e;
    }
    // Producer.ThreadRun
    public void ThreadRun()
    {
        int count = 0;
        Random r = new Random();
        while (!_syncEvents.ExitThreadEvent.WaitOne(0, false))
        {
            lock (((ICollection)_queue).SyncRoot)
            {
                while (_queue.Count < 20)
                {
                    _queue.Enqueue(r.Next(0,100));
                    _syncEvents.NewItemEvent.Set();
                    count++;
                }
            }
        }
        Console.WriteLine("Producer thread: produced {0} items", count);
    }
    private Queue<int> _queue;
    private SyncEvents _syncEvents;
}

public class Consumer
{
    public Consumer(Queue<int> q, SyncEvents e)
    {
        _queue = q;
        _syncEvents = e;
    }
    // Consumer.ThreadRun
    public void ThreadRun()
    {
        int count = 0;
        while (WaitHandle.WaitAny(_syncEvents.EventArray) != 1)
        {
            lock (((ICollection)_queue).SyncRoot)
            {
                int item = _queue.Dequeue();
            }
            count++;
        }
        Console.WriteLine("Consumer Thread: consumed {0} items", count);
    }
    private Queue<int> _queue;
    private SyncEvents _syncEvents;
}

public class ThreadSyncSample
{
    private static void ShowQueueContents(Queue<int> q)
    {
        lock (((ICollection)q).SyncRoot)
        {
            foreach (int item in q)
            {
                Console.Write("{0} ", item);
            }
        }
        Console.WriteLine();
    }

    static void Main()
    {
        Queue<int> queue = new Queue<int>();
        SyncEvents syncEvents = new SyncEvents();

        Console.WriteLine("Configuring worker threads...");
        Producer producer = new Producer(queue, syncEvents);
        Consumer consumer = new Consumer(queue, syncEvents);
        Thread producerThread = new Thread(producer.ThreadRun);
        Thread consumerThread = new Thread(consumer.ThreadRun);

        Console.WriteLine("Launching producer and consumer threads...");
        producerThread.Start();
        consumerThread.Start();

        for (int i=0; i<4; i++)
        {
            Thread.Sleep(2500);
            ShowQueueContents(queue);
        }

        Console.WriteLine("Signaling threads to terminate...");
        syncEvents.ExitThreadEvent.Set();

        producerThread.Join();
        consumerThread.Join();
    }

}
复制代码
Configuring worker threads...
Launching producer and consumer threads...
22 92 64 70 13 59 9 2 43 52 91 98 50 96 46 22 40 94 24 87
79 54 5 39 21 29 77 77 1 68 69 81 4 75 43 70 87 72 59
0 69 98 54 92 16 84 61 30 45 50 17 86 16 59 20 73 43 21
38 46 84 59 11 87 77 5 53 65 7 16 66 26 79 74 26 37 56 92
Signalling threads to terminate...
Consumer Thread: consumed 1053771 items
Producer thread: produced 1053791 items

请参见

任务

监视器同步技术示例
等待同步技术示例

概念

C# 编程指南

参考

线程同步（C# 编程指南）
Thread
lock 语句（C# 参考）
AutoResetEvent
ManualResetEvent
Set
Join
WaitOne
WaitAll
Queue
ICollection
Start
Sleep
WaitHandle
EventWaitHandle


如何：使用线程池（C# 编程指南）

[image]

C# 编程指南
如何：使用线程池（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

“线程池”是可以用来在后台执行多个任务的线程集合。（有关背景信息，请参见使用线程处理（C# 编程指南）。）这使主线程可以自由地异步执行其他任务。

线程池通常用于服务器应用程序。每个传入请求都将分配给线程池中的一个线程，因此可以异步处理请求，而不会占用主线程，也不会延迟后续请求的处理。

一旦池中的某个线程完成任务，它将返回到等待线程队列中，等待被再次使用。这种重用使应用程序可以避免为每个任务创建新线程的开销。

线程池通常具有最大线程数限制。如果所有线程都繁忙，则额外的任务将放入队列中，直到有线程可用时才能够得到处理。

您可以实现自己的线程池，但是通过 ThreadPool 类使用 .NET Framework 提供的线程池更容易一些。

下面的示例使用 .NET Framework 线程池计算 20 和 40 之间的十个数的 Fibonacci 结果。每个 Fibonacci 结果都由 Fibonacci 类表示，该类提供一种名为 ThreadPoolCallback 的方法来执行此计算。将创建表示每个 Fibonacci 值的对象，ThreadPoolCallback 方法将传递给 QueueUserWorkItem，它分配池中的一个可用线程来执行此方法。

由于为每个 Fibonacci 对象都提供了一个半随机值来进行计算，而且每个线程都将争用处理器时间，因此无法提前知道十个结果全部计算出来所需的时间。这就是为何会在构造期间为每个 Fibonacci 对象传递 ManualResetEvent 类的一个实例的原因。当计算完成时，每个对象都通知提供的事件对象，使主线程用 WaitAll 阻止执行，直到十个 Fibonacci 对象全部计算出了结果。然后 Main 方法将显示每个 Fibonacci 结果。

示例

C# 复制代码
using System;
using System.Threading;

public class Fibonacci
{
    public Fibonacci(int n, ManualResetEvent doneEvent)
    {
        _n = n;
        _doneEvent = doneEvent;
    }

    // Wrapper method for use with thread pool.
    public void ThreadPoolCallback(Object threadContext)
    {
        int threadIndex = (int)threadContext;
        Console.WriteLine("thread {0} started...", threadIndex);
        _fibOfN = Calculate(_n);
        Console.WriteLine("thread {0} result calculated...", threadIndex);
        _doneEvent.Set();
    }

    // Recursive method that calculates the Nth Fibonacci number.
    public int Calculate(int n)
    {
        if (n <= 1)
        {
            return n;
        }

        return Calculate(n - 1) + Calculate(n - 2);
    }

    public int N { get { return _n; } }
    private int _n;

    public int FibOfN { get { return _fibOfN; } }
    private int _fibOfN;

    private ManualResetEvent _doneEvent;
}

public class ThreadPoolExample
{
    static void Main()
    {
        const int FibonacciCalculations = 10;

        // One event is used for each Fibonacci object
        ManualResetEvent[] doneEvents = new ManualResetEvent[FibonacciCalculations];
        Fibonacci[] fibArray = new Fibonacci[FibonacciCalculations];
        Random r = new Random();

        // Configure and launch threads using ThreadPool:
        Console.WriteLine("launching {0} tasks...", FibonacciCalculations);
        for (int i = 0; i < FibonacciCalculations; i++)
        {
            doneEvents[i] = new ManualResetEvent(false);
            Fibonacci f = new Fibonacci(r.Next(20,40), doneEvents[i]);
            fibArray[i] = f;
            ThreadPool.QueueUserWorkItem(f.ThreadPoolCallback, i);
        }

        // Wait for all threads in pool to calculation...
        WaitHandle.WaitAll(doneEvents);
        Console.WriteLine("All calculations are complete.");

        // Display the results...
        for (int i= 0; i<FibonacciCalculations; i++)
        {
            Fibonacci f = fibArray[i];
            Console.WriteLine("Fibonacci({0}) = {1}", f.N, f.FibOfN);
        }
    }
}

输出如下：

复制代码
launching 10 tasks...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
result calculated...
all calculations complete
Fibonacci(22) = 17711
Fibonacci(25) = 75025
Fibonacci(32) = 2178309
Fibonacci(36) = 14930352
Fibonacci(32) = 2178309
Fibonacci(26) = 121393
Fibonacci(35) = 9227465
Fibonacci(23) = 28657
Fibonacci(39) = 63245986
Fibonacci(22) = 17711

请参见

任务

监视器同步技术示例
等待同步技术示例

概念

C# 编程指南
监视器

参考

线程处理（C# 编程指南）
使用线程处理（C# 编程指南）
Mutex
WaitAll
ManualResetEvent
Set
ThreadPool
QueueUserWorkItem
ManualResetEvent

其他资源

如何：通过使用 Visual C# .NET 在多线程处理环境中同步对共享资源的访问
.NET Framework 中的安全性


性能（C# 编程指南）

[image]

C# 编程指南
性能（C# 编程指南）
另请参见 发送反馈意见

更新： 2008 年 7 月

术语“性能”通常指程序的执行速度。有时候，通过遵循源代码中的一些基本规则便可以提高执行速度。在某些程序中，仔细检查代码并使用探查器确保程序可以尽可能快地运行非常重要。在另一些程序中，由于在编写时代码运行得足够快，因此不必进行此类优化。

在衡量性能并针对性能进行优化时，应遵循下面这些通用准则：

  • 首先，确立性能目标并衡量程序的性能，以确定代码何时达不到这些目标。

  • 开始编写代码时，使代码正确，遵循良好的设计准则并清楚地表达您的意图。仅当您确定代码达不到性能目标时才在以后进行优化。针对性能进行优化的代码通常更加难于阅读和维护。一般情况下，编写可读、可靠且可维护的代码会更好一些，但其运行速度比您编写的多数优化代码要慢一些。

  • 如果必须进行优化，应首先从程序的最慢部分入手。如果确定某个程序达不到性能目标，请找出性能可以提高的具体位置并确定哪些性能问题是程序运行慢的主要原因。优化几乎不调用的方法，或者如果整个程序必须等待 30 秒让方法 B 在另一线程上运行，才能对方法 A 进行优化使其在 50 毫秒内运行，这些通常是没有用的。

装箱和取消装箱

如果必须频繁地将值类型装箱，则最好避免使用值类型，例如在非泛型集合类（如 System.Collections..::.ArrayList）中。可以通过使用泛型集合（例如 System.Collections.Generic..::.List<(Of <(T>)>)）来避免将值类型装箱。装箱和取消装箱都是需要大量运算的过程。对值类型进行装箱时，必须创建一个全新的对象。此操作所需时间可比简单的引用赋值操作长 20 倍。取消装箱时，强制转换过程所需时间可达赋值操作的四倍。有关更多信息，请参见装箱和取消装箱。

字符串

在连接大量字符串变量时，例如在紧凑循环中，请使用 System.Text..::.StringBuilder 而不是 C# + 运算符。有关更多信息，请参见如何：串联多个字符串（C# 编程指南）。

析构函数

不应使用空析构函数。如果类包含析构函数，Finalize 队列中则会创建一个项。调用析构函数时，将调用垃圾回收器来处理该队列。如果析构函数为空，只会导致性能降低。有关更多信息，请参见析构函数。

其他资源

  • Writing Faster Managed Code: Know What Things Cost（编写更快的托管代码：了解开销）

  • 编写高性能的托管应用程序：入门

  • Garbage Collector Basics and Performance Hints（垃圾回收器基础知识和性能提示）

  • Performance Tips and Tricks in .NET Applications（.NET 应用程序的性能提示和技巧）

  • .NET 的内部诊断工具

  • Rico Mariani's Performance Tidbits（Rico Mariani 关于性能问题的见解）

请参见

概念

C# 编程指南

参考

安全性（C# 编程指南）

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 在有关装箱和取消装箱的章节中增加了一些信息。 | 信息补充。
2008 年 7 月 | 增加了有关字符串的一节。 | 信息补充。
2008 年 7 月 | 增加了简介部分的信息。 | 信息补充。


反射（C# 编程指南）

[image]

C# 编程指南
反射（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

反射提供了描述程序集、模块和类型的对象（Type 类型）。可以使用反射动态创建类型的实例，将类型绑定到现有对象，或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了属性，可以利用反射对它们进行访问。有关更多信息，请参见 属性（C# 编程指南）。

下面是使用静态方法 GetType-- 从 Object 基类派生的所有类型都继承该方法 -- 获取变量类型的简单反射示例：

C# 复制代码
// Using GetType to obtain type information:
int i = 42;
System.Type type = i.GetType();
System.Console.WriteLine(type);

输出为：

System.Int32

此示例使用反射获取已加载的程序集的完整名称：

C# 复制代码
// Using Reflection to get information from an Assembly:
System.Reflection.Assembly o = System.Reflection.Assembly.Load("mscorlib.dll");
System.Console.WriteLine(o.GetName());

输出为：

mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

说明：

C# 关键字 protected 和 internal 在 IL 中没有意义并且在反射 API 中不会使用。IL 中对应的术语为“家族”和“程序集”。若要使用反射来标识 internal 方法，请使用 IsAssembly 属性。若要标识 protected internal 方法，请使用 IsFamilyOrAssembly。

反射概述

反射在下列情况下很有用：

  • 当需要访问程序元数据中的属性时。请参见主题使用反射访问属性。

  • 检查和实例化程序集中的类型。

  • 在运行时构建新类型。使用 System.Reflection.Emit 中的类。

  • 执行后期绑定，访问在运行时创建的类型的方法。请参见主题动态加载和使用类型。

相关章节

更多信息：

  • 反射概述

  • 查看类型信息

  • 反射类型和泛型类型

  • System.Reflection.Emit

  • 使用反射访问属性（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.12 属性

  • 7.5.11 typeof 运算符

请参见

概念

C# 编程指南

参考

应用程序域（C# 编程指南）
程序集和全局程序集缓存（C# 编程指南）


C# DLL（C# 编程指南）

[image]

C# 编程指南
C# DLL（C# 编程指南）
发送反馈意见

更新：2007 年 11 月

可以使用 Visual C# 来创建可由其他托管应用程序和非托管代码调用的 DLL。

本节内容

如何：创建和使用 C# DLL（C# 编程指南）

显示如何在 Visual C# 中创建类库项目。

相关章节

/baseaddress（指定 DLL 的基址）（C# 编译器选项）

描述使您可以指定用来加载 DLL 的首选基址的 /baseaddress 编译器选项。

创建用于容纳 DLL 函数的类

解释将常用的 DLL 函数包装在托管类中如何成为一种封装平台功能的有效方法。

为什么 DLL 断点不起作用？

列出断点无法在 DLL 中按照预期方式工作的原因以及每种情况的解决方案或解决办法。

调试 DLL 项目

提供有关如何调试 DLL 项目的信息。

C# 编程指南

提供指向解释 C# 中功能的主题的链接。


如何：创建和使用 C# DLL（C# 编程指南）

[image]

C# 编程指南
如何：创建和使用 C# DLL（C# 编程指南）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

动态链接库 (DLL) 在运行时链接到程序。为说明如何生成和使用 DLL，请看以下方案：

  • MathLibrary.DLL：为库文件，其中包含运行时要调用的方法。此例中，DLL 包含两个方法：Add 和 Multiply。

  • Add.cs：为源文件，其中包含 Add(long i, long j) 方法。该方法返回参数之和。包含 Add 方法的 AddClass 类是命名空间 UtilityMethods 的成员。

  • Mult.cs：为源文件，其中包含 Multiply(long x, long y) 方法。该方法返回参数之积。包含 Multiply 方法的 MultiplyClass 类也是命名空间 UtilityMethods 的成员。

  • TestCode.cs：包含 Main 方法的文件。它使用 DLL 文件中的方法来计算运行时参数的和与积。

示例

C# 复制代码
// File: Add.cs
namespace UtilityMethods
{
    public class AddClass
    {
        public static long Add(long i, long j)
        {
            return (i + j);
        }
    }
}
C# 复制代码
// File: Mult.cs
namespace UtilityMethods
{
    public class MultiplyClass
    {
        public static long Multiply(long x, long y)
        {
            return (x * y);
        }
    }
}
C# 复制代码
// File: TestCode.cs

using UtilityMethods;

class TestCode
{
    static void Main(string[] args)
    {
        System.Console.WriteLine("Calling methods from MathLibrary.DLL:");

        if (args.Length != 2)
        {
            System.Console.WriteLine("Usage: TestCode <num1> <num2>");
            return;
        }

        long num1 = long.Parse(args[0]);
        long num2 = long.Parse(args[1]);

        long sum = AddClass.Add(num1, num2);
        long product = MultiplyClass.Multiply(num1, num2);

        System.Console.WriteLine("{0} + {1} = {2}", num1, num2, sum);
        System.Console.WriteLine("{0} * {1} = {2}", num1, num2, product);
    }
}
/* Output (assuming 1234 and 5678 are entered as command line args):
    Calling methods from MathLibrary.DLL:
    1234 + 5678 = 6912
    1234 * 5678 = 7006652
*/

此文件包含使用 DLL 方法 Add 和 Multiply 的算法。它首先分析从命令行输入的参数 num1 和 num2。然后使用 AddClass 类中的 Add 方法计算和，使用 MultiplyClass 类中的 Multiply 方法计算积。

请注意，文件开头的 using 指令使您得以在编译时使用未限定的类名来引用 DLL 方法，如下所示：

C# 复制代码
MultiplyClass.Multiply(num1, num2);

否则，必须使用完全限定名，如下所示：

C# 复制代码
UtilityMethods.MultiplyClass.Multiply(num1, num2);

执行

若要运行程序，请输入 EXE 文件的名称，文件名的后面跟两个数字，如下所示：

TestCode 1234 5678

编译代码

若要生成文件 MathLibrary.DLL，请使用以下命令行编译文件 Add.cs 和文件 Mult.cs：

csc /target:library /out:MathLibrary.DLL Add.cs Mult.cs

/target:library 编译器选项通知编译器输出 DLL 文件而不是 EXE 文件。后跟文件名的 /out 编译器选项用于指定 DLL 文件名。否则，编译器使用第一个文件 (Add.cs) 作为 DLL 文件名。

若要生成可执行文件 TestCode.exe，请使用以下命令行：

csc /out:TestCode.exe /reference:MathLibrary.DLL TestCode.cs

/out 编译器选项通知编译器输出 EXE 文件并且指定输出文件名 (TestCode.exe)。此编译器选项是可选的。/引用编译器选项指定该程序使用的 DLL 文件。

请参见

任务

如何：为 DLL 指定基址

概念

C# 编程指南
创建用于容纳 DLL 函数的类


安全性（C# 编程指南）

[image]

C# 编程指南
安全性（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

安全是每个 C# 应用程序的一个不可缺少的方面，并且在每个开发阶段都必须加以考虑，而不仅仅是在完成设计和实现后才加以考虑。

特定于 C# 的安全建议

本列表并未列出所有潜在的安全问题。它只是重点强调了 C# 开发人员遇到的一些常见问题。

  • 使用 checked 关键字控制整型算术运算和转换的溢出检查上下文。

  • 始终对参数使用最严格的数据类型。例如，在将值传递给一个描述数据结构大小的方法时，应使用无符号整数而不是整数。

  • 不要基于文件名做出判断。文件名的表示方法可以有很多种，因此攻击者可能避开您对特定文件设置的测试。

  • 千万不要将密码或其他敏感信息硬编码到应用程序中。

  • 始终验证用于生成 SQL 查询的输入。

  • 验证传入方法的所有输入。System.Text.RegularExpressions 命名空间中的正则表达式方法可用于确认输入是否具有正确的格式（如电子邮件地址）。

  • 不要显示异常信息：它会给任何潜在的攻击者提供有价值的线索。

  • 确保应用程序在最低可能权限下运行时能够正常工作。很少有应用程序要求用户以管理员的身份登录。

  • 不要使用自己的加密算法。应该使用 System.Security.Cryptography 类。

  • 为程序集指定强名称。

  • 不要在 XML 或其他配置文件中存储敏感信息。

  • 仔细检查包装本机代码的托管代码。确认本机代码是安全的。

  • 在使用从应用程序外部传递的委托时应十分谨慎。

  • 对程序集运行 Visual Studio 代码分析工具，以确保符合 Microsoft .NET Framework 设计准则。此工具还可以找到 200 个以上的代码缺陷并提出警告。有关更多信息，请参见检测和更正托管代码缺陷。

其他安全资源

下列 Microsoft Internet 网站提供了有关创建安全且可靠的软件的详细信息。

  • Microsoft Security Developer Center（Microsoft 安全开发人员中心）

  • .NET Security（.NET 安全）

  • Security Alerts and Information（安全警报和信息）

请参见

概念

C# 编程指南
安全性工具

其他资源

本机代码和 .NET Framework 代码的安全性
代码安全维护指南


C# 参考

[image]

C# 语言参考
C# 参考
发送反馈意见

更新：2007 年 11 月

这部分提供有关 C# 关键字、运算符、编译器错误和警告的参考资料。

本节内容

C# 关键字

提供指向有关 C# 关键字和语法的信息的链接。

C# 运算符

提供指向有关 C# 运算符和语法的信息的链接。

C# 预处理器指令

提供指向有关在 C# 源代码进行嵌入所使用的编译器命令的信息的链接。

C# 编译器选项

包括有关编译器选项以及如何使用这些选项的信息。

C# 编译器错误

包含一些代码段，演示 C# 编译器错误和警告的原因以更正方法。

C# 术语

提供 C# 单词和短语的词汇表。

相关章节

C# 语言规范

提供一些链接，供获取 Microsoft Word 格式的最新版本的 C# 语言规范。

C# 常见问题

在 C# Developer Center 中提供内容不断增加的 C# 常见问题列表。

Microsoft 知识库中的 C# 知识库文章

提供对存储在 MSDN 上的 C# 相关知识库文章的动态搜索。

Visual C#

提供 Visual C# 文档门户。

Visual C# 示例

提供 Visual C# 示例的列表和有关如何在本地磁盘上查找这些示例的说明。

Visual C# 代码编辑器功能

提供一些链接，这些链接指向描述 IDE 和编辑器的概念性主题及任务主题。

用 Visual C# 编写应用程序

提供一些链接，这些链接指向说明如何执行某些常见编程任务的主题。

C# 编程指南

包括关于如何使用 C# 编程语言的信息。


C# 关键字

[image]

C# 语言参考
C# 关键字
另请参见 发送反馈意见

更新：2007 年 11 月

关键字是对编译器具有特殊意义的预定义保留标识符。它们不能在程序中用作标识符，除非它们有一个 @ 前缀。例如，@if 是有效的标识符，但 if 不是，因为 if 是关键字。

本主题中的第一个表列出的关键字在 C# 程序的任何部分都是保留标识符。本主题中的第二个表列出了 C# 中的上下文关键字。上下文关键字仅在受限制的程序上下文中具有特殊含义，并且可在该上下文外部用作标识符。通常，在将新关键字添加到 C# 语言的同时，也会将它们添加为上下文关键字，以便避免破坏用该语言的早期版本编写的程序。

abstract | event | new | struct
as | explicit | null | switch
base | extern | object | this
bool | false | operator | throw
break | finally | out | true
byte | fixed | override | try
case | float | params | typeof
catch | for | private | uint
char | foreach | protected | ulong
checked | goto | public | unchecked
class | if | readonly | unsafe
const | implicit | ref | ushort
continue | in | return | using
decimal | int | sbyte | virtual
default | interface | sealed | volatile
delegate | internal | short | void
do | is | sizeof | while
double | lock | stackalloc |
else | long | static |
enum | namespace | string |

上下文关键字

上下文关键字用于提供代码中的特定含义，但它不是 C# 中的保留字。某些上下文关键字（如 partial 和 where）在两个或更多个上下文中具有特殊含义。

from | get | group
into | join | let
orderby | partial（类型） | partial（方法）
select | set | value
var | where（泛型类型约束） | where（查询子句）
yield |  |

请参见

概念

C# 编程指南

其他资源

C# 参考


类型（C# 参考）

[image]

C# 语言参考
类型（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 类型体系包含下列几种类别：

  • 值类型

  • 引用类型

  • 指针类型

值类型的变量存储数据，而引用类型的变量存储对实际数据的引用。引用类型也称为对象。指针类型仅可用于 unsafe 模式。

通过装箱和取消装箱，可以将值类型转换为引用类型，然后再转换回值类型。除了装箱值类型外，无法将引用类型转换为值类型。

本节还介绍 void 类型。

值类型也可以为 null，这意味着它们能存储其他非值状态。有关更多信息，请参见可以为 null 的类型。

请参见

概念

C# 编程指南

参考

C# 关键字
强制转换和类型转换（C# 编程指南）
类型（C# 编程指南）

其他资源

C# 参考
类型参考表（C# 参考）


值类型（C# 参考）

[image]

C# 语言参考
值类型（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

值类型主要由两类组成：

  • 结构

  • 枚举

结构分为以下几类：

  • Numeric（数值）类型

    • 整型

    • 浮点型

    • decimal

  • bool

  • 用户定义的结构。

值类型的主要功能

基于值类型的变量直接包含值。将一个值类型变量赋给另一个值类型变量时，将复制包含的值。这与引用类型变量的赋值不同，引用类型变量的赋值只复制对对象的引用，而不复制对象本身。

所有的值类型均隐式派生自 System..::.ValueType。

与引用类型不同，可以从值类型派生出新的类型。但与引用类型相同的是，结构也可以实现接口。

与引用类型不同，值类型无法包含 null 值。然而，可译为 null 的类型功能确实允许将 null 赋给值类型。

每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。有关值类型的默认值的信息，请参见默认值表。

简单类型的主要功能

所有的简单类型（C# 语言的组成部分）均为 .NET Framework 系统类型的别名。例如，int 是 System..::.Int32 的别名。有关完整的别名列表，请参见 内置类型表（C# 参考）。

编译时计算操作数均为简单类型常数的常数表达式。

可使用文字初始化简单类型。例如，“A”是 char 类型的文字，而 2001 是 int 类型的文字。

初始化值类型

在使用 C# 中的局部变量之前，必须对其进行初始化。例如，可能声明未进行初始化的局部变量，如以下示例所示：

复制代码
int myInt;

那么在将其初始化之前，无法使用此变量。可使用下列语句将其初始化：

复制代码
myInt = new int();  // Invoke default constructor for int type.

此语句是下列语句的等效语句：

复制代码
myInt = 0;         // Assign an initial value, 0 in this example.

当然，可以用同一个语句进行声明和初始化，如下面示例所示：

复制代码
int myInt = new int();

- 或 -

复制代码
int myInt = 0;

使用 new 运算符时，将调用特定类型的默认构造函数并对变量赋以默认值。在上例中，默认构造函数将值 0 赋给了 myInt。有关通过调用默认构造函数所赋的值的更多信息，请参见默认值表。

对于用户定义的类型，使用 new 来调用默认构造函数。例如，下列语句调用了 Point 结构的默认构造函数：

复制代码
Point p = new Point(); // Invoke default constructor for the struct.

此调用后，该结构被认为已被明确赋值；也就是说该结构的所有成员均已初始化为各自的默认值。

有关 new 运算符的更多信息，请参见 new。

有关格式化数字类型输出的信息，请参见格式化数值结果表。

请参见

概念

C# 编程指南

参考

C# 关键字
引用类型（C# 参考）

其他资源

C# 参考
类型（C# 参考）
类型参考表（C# 参考）


bool（C# 参考）

[image]

C# 语言参考
bool（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

bool 关键字是 System..::.Boolean 的别名。它用于声明变量来存储布尔值 true 和 false。

说明：

如果需要一个也可以有 null 值的布尔型变量，请使用 bool?。有关更多信息，请参见 可空类型（C# 编程指南）。

文本

可将布尔值赋给 bool 变量。也可以将计算为 bool 类型的表达式赋给 bool 变量。

C# 复制代码
public class BoolTest
{
    static void Main()
    {
        bool b = true;

        // WriteLine automatically converts the value of b to text.
        Console.WriteLine(b);

        int days = DateTime.Now.DayOfYear;

        // Assign the result of a boolean expression to b.
        b = (days % 2 == 0);

        if (true == b)
        {
            Console.WriteLine("days is an even number");
        }
        else
        {
            Console.WriteLine("days is an odd number");
        }
    }
}
/* Output:
  True
  days is an <even/odd> number
*/

转换

在 C++ 中，bool 类型的值可转换为 int 类型的值；也就是说，false 等效于零值，而 true 等效于非零值。在 C# 中，不存在 bool 类型与其他类型之间的相互转换。例如，下面的 if 语句在 C# 中无效：

C# 复制代码
int x = 123;

// if (x)   // Error: "Cannot implicitly convert type 'int' to 'bool'"
{
    Console.Write("The value of x is nonzero.");
}

若要测试 int 类型的变量，必须将该变量与一个值（例如零）进行显式比较，如下所示：

C# 复制代码
if (x != 0)   // The C# way
{
    Console.Write("The value of x is nonzero.");
}

示例

在此例中，您从键盘输入一个字符，然后程序检查输入的字符是否是一个字母。如果字符是一个字母，则程序检查它是大写还是小写。这些检查是使用 IsLetter 和 IsLower（两者均返回 bool 类型）来执行的：

C# 复制代码
public class BoolKeyTest
{
    static void Main()
    {
        Console.Write("Enter a character: ");
        char c = (char)Console.Read();
        if (Char.IsLetter(c))
        {
            if (Char.IsLower(c))
            {
                Console.WriteLine("The character is lowercase.");
            }
            else
            {
                Console.WriteLine("The character is uppercase.");
            }
        }
        else
        {
            Console.WriteLine("Not an alphabetic character.");
        }
    }
}
/* Sample Output:
    Enter a character: X
    The character is uppercase.

    Enter a character: x
    The character is lowercase.

    Enter a character: 2
    The character is not an alphabetic character.
 */

C# 语言规范

有关 bool 和相关主题的更多信息，请参见 C# 语言规范中的以下各节：

  • 4.1.8 bool 类型

  • 7.9.4 布尔相等运算符

  • 7.11.1 布尔条件逻辑运算符

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）

其他资源

C# 参考


byte（C# 参考）

[image]

C# 语言参考
byte（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

byte 关键字代表一种整型，该类型按下表所示存储值：

类型 | 范围 | 大小 | .NET Framework 类型
byte | 0 到 255 | 无符号 8 位整数 | System..::.Byte

标识符

可如下例所示声明并初始化 byte 类型的变量：

复制代码
byte myByte = 255;

在以上声明中，整数 255 从 int 隐式转换为 byte。如果整数超出了 byte 的范围，将产生编译错误。

转换

存在从 byte 到 short、ushort、int、uint、long、ulong、float、double 或 decimal 的预定义隐式转换。

不能将更大存储大小的非文本数值类型隐式转换为 byte。有关整型的存储大小的更多信息，请参见 整型表（C# 参考）。例如，请看以下两个 byte 变量 x 和 y：

复制代码
byte x = 10, y = 20;

以下赋值语句将产生一个编译错误，原因是赋值运算符右侧的算术表达式在默认情况下的计算结果为 int 类型。

复制代码
// Error: conversion from int to byte:
byte z = x + y;

若要解决此问题，请使用强制转换：

复制代码
// OK: explicit conversion:
byte z = (byte)(x + y);

但是，在目标变量具有相同或更大的存储大小时，使用下列语句是可能的：

复制代码
int x = 10, y = 20;
int m = x + y;
long n = x + y;

同样，不存在从浮点型到 byte 类型的隐式转换。例如，除非使用显式强制转换，否则以下语句将生成一个编译器错误：

复制代码
// Error: no implicit conversion from double:
byte x = 3.0;
// OK: explicit conversion:
byte y = (byte)3.0;

调用重载方法时，必须使用显式转换。以下面使用 byte 和 int 参数的重载方法为例：

复制代码
public static void SampleMethod(int i) {}
public static void SampleMethod(byte b) {}

使用 byte 强制转换可保证调用正确的类型，例如：

复制代码
// Calling the method with the int parameter:
SampleMethod(5);
// Calling the method with the byte parameter:
SampleMethod((byte)5);

有关兼用浮点型和整型的算术表达式的信息，请参见 float 和 double。

有关隐式数值转换规则的更多信息，请参见 隐式数值转换表（C# 参考）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
Byte

其他资源

C# 参考


char（C# 参考）

[image]

C# 语言参考
char（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

char 关键字用于声明下表所示范围内的 Unicode 字符。Unicode 字符是 16 位字符，用于表示世界上大多数已知的书面语言。

类型 | 范围 | 大小 | .NET Framework 类型
char | U+0000 到 U+ffff | 16 位 Unicode 字符 | System..::.Char

文本

char 类型的常数可以写成字符、十六进制换码序列或 Unicode 表示形式。您也可以显式转换整数字符代码。在下面的示例中，四个 char 变量使用同一字符 X 初始化：

C# 复制代码
char[] chars = new char[4];

chars[0] = 'X';        // Character literal
chars[1] = '\x0058';   // Hexadecimal
chars[2] = (char)88;   // Cast from integral type
chars[3] = '\u0058';   // Unicode

foreach (char c in chars)
{
    Console.Write(c + " ");
}
// Output: X X X X

转换

char 可以隐式转换为 ushort、int、uint、long、ulong、float、double 或 decimal。但是，不存在从其他类型到 char 类型的隐式转换。

System..::.Char 类型提供几个处理 char 值的静态方法。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 2.4.4.4 字符

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
Char

其他资源

C# 参考


decimal（C# 参考）

[image]

C# 语言参考
decimal（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

decimal 关键字表示 128 位数据类型。同浮点型相比，decimal 类型具有更高的精度和更小的范围，这使它适合于财务和货币计算。decimal 类型的大致范围和精度如下表所示。

类型 | 大致范围 | 精度 | .NET Framework 类型
decimal | ±1.0 × 10-28 到 ±7.9 × 1028 | 28 到 29 位有效位 | System..::.Decimal

文本

如果希望实数被视为 decimal 类型，请使用后缀 m 或 M，例如：

复制代码
decimal myMoney = 300.5m;

如果没有后缀 m，数字将被视为 double 类型，从而导致编译器错误。

转换

整型被隐式转换为 decimal，其计算结果为 decimal。因此，可以用整数初始化十进制变量而不使用后缀，如下所示：

复制代码
decimal myMoney = 300;

在浮点型和 decimal 类型之间不存在隐式转换；因此，必须使用强制转换在这两种类型之间进行转换。例如：

复制代码
decimal myMoney = 99.9m;
double x = (double)myMoney;
myMoney = (decimal)x;

还可以在同一表达式中混合使用 decimal 和数值整型。但是，不进行强制转换就混合使用 decimal 和浮点型将导致编译错误。

有关隐式数值转换的更多信息，请参见隐式数值转换表（C# 参考）。

有关显式数值转换的更多信息，请参见显式数值转换表（C# 参考）。

格式化十进制输出

可以通过使用 String.Format 方法或 Console..::.Write 方法（它调用 String.Format()）来格式化结果。货币格式是使用标准货币格式字符串“C”或“c”指定的，如本文后面的第二个示例所示。有关 String.Format 方法的更多信息，请参见 String..::.Format。

示例

在此例中，同一个表达式中混合使用了 decimal 和 int。计算结果为 decimal 类型。

下面的示例使用某个语句来尝试添加 double 和 decimal 变量：

复制代码
double x = 9;
Console.WriteLine(d + x); // Error

其结果是导致以下错误：

Operator '+' cannot be applied to operands of type 'double' and 'decimal'

C# 复制代码
public class TestDecimal
{
    static void Main()
    {
        decimal d = 9.1m;
        int y = 3;
        Console.WriteLine(d + y);   // Result converted to decimal
    }
}
// Output: 12.1

在此例中，使用了货币格式字符串来设置输出格式。注意：其中 x 被舍入，因为其小数点位置超出了 $0.99。而表示最大精确位数的变量 y 严格按照正确的格式显示。

C# 复制代码
public class TestDecimalFormat
{
    static void Main()
    {
        decimal x = 0.999m;
        decimal y = 9999999999999999999999999999m;
        Console.WriteLine("My amount = {0:C}", x);
        Console.WriteLine("Your amount = {0:C}", y);
    }
}
/* Output:
    My amount = $1.00
    Your amount = $9,999,999,999,999,999,999,999,999,999.00
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.7 decimal 类型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
Decimal

其他资源

C# 参考


double（C# 参考）

[image]

C# 语言参考
double（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

double 关键字表示存储 64 位浮点值的简单类型。下表显示了 double 类型的精度和大致范围。

类型 | 大致范围 | 精度 | .NET Framework 类型
double | ±5.0 × 10-324 到 ±1.7 × 10308 | 15 到 16 位 | System..::.Double

标识符

默认情况下，赋值运算符右侧的实数被视为 double。但是，如果希望整数被视为 double，请使用后缀 d 或 D，例如：

复制代码
double x = 3D;

转换

可在一个表达式中兼用数值整型和浮点型。在此情况下，整型将转换为浮点型。根据以下规则计算表达式：

  • 如果其中一个浮点类型为 double，则表达式的计算结果为 double 或 bool（在关系表达式或布尔表达式中）。

  • 如果表达式中不存在 double 类型，则表达式的计算结果为 float 或 bool（在关系表达式或布尔表达式中）。

浮点表达式可以包含下列值集：

  • 正零和负零。

  • 正无穷和负无穷。

  • 非数字值 (NaN)。

  • 有限的非零值集。

有关这些值的更多信息，请参见 IEEE 网站上的“IEEE Standard for Binary Floating-Point Arithmetic”（二进制浮点算法的 IEEE 标准）。

示例

在下面的示例中，一个 int、一个 short、一个 float 和一个 double 相加，计算结果为 double 类型。

C# 复制代码
// Mixing types in expressions
class MixedTypes
{
    static void Main()
    {
        int x = 3;
        float y = 4.5f;
        short z = 5;
        double w = 1.7E+3;
        // Result of the 2nd argument is a double:
        Console.WriteLine("The sum is {0}", x + y + z + w);
    }
}
// Output: The sum is 1712.5

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）

其他资源

C# 参考
默认值表（C# 参考）
浮点型表（C# 参考）


enum（C# 参考）

[image]

C# 语言参考
enum（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

enum 关键字用于声明枚举，即一种由一组称为枚举数列表的命名常量组成的独特类型。

通常情况下，最好是在命名空间内直接定义枚举，以便该命名空间中的所有类都能够同样方便地访问它。但是，还可以将枚举嵌套在类或结构中。

默认情况下，第一个枚举数的值为 0，后面每个枚举数的值依次递增 1。例如：

复制代码
enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri};

在此枚举中，Sat 为 0，Sun 为 1，Mon 为 2，依此类推。枚举数可以具有重写默认值的初始值设定项。例如：

复制代码
enum Days {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};

在此枚举中，强制元素序列从 1 而不是 0 开始。但是，强烈建议枚举包含一个值为 0 的常量。有关更多信息，请参见枚举类型（C# 编程指南）。

每种枚举类型都有基础类型，该类型可以是除 char 以外的任何整型。枚举元素的默认基础类型为 int。若要声明另一整型枚举，例如 byte，请在标识符之后紧跟类型，然后再使用冒号：

复制代码
enum Days : byte {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};

准许使用的枚举类型有 byte、sbyte、short、ushort、int、uint、long 或 ulong。

可以给 Days 类型的变量赋以基础类型范围内的任何值，所赋的值不限于已命名的常数。

enum E 的默认值为表达式 (E)0 生成的值。

说明：

枚举数的名称中不能包含空白。

基础类型指定为每个枚举数分配的存储大小。但是，从 enum 类型到整型的转换需要用显式类型转换来完成。例如，下面的语句使用强制转换（从 enum 转换为 int）将枚举数 Sun 赋值给一个 int 类型的变量：

复制代码
int x = (int)Days.Sun;

将 System..::.FlagsAttribute 应用于某个枚举时，如果该枚举包含一些使用按位 OR 运算组合的元素，这时您会注意到该属性在用于某些工具时会影响 enum 的行为。当使用诸如 Console 类方法、表达式计算器这样的工具时，可以注意到这些变化。（请参见示例 3）。

可靠编程

与任何常量一样，对枚举中各个值的所有引用在编译时均将转换为数值文本。这可形成潜在的版本控制问题，如常量（C# 编程指南）中所述。

给新版本的枚举赋其他值，或者更改新版本中枚举成员的值，可导致相关源代码出现问题。通常在 switch 语句中使用枚举值。如果 enum 类型中添加了其他元素，则默认值的测试可能会意外地返回 true。

如果其他开发人员将使用您的代码，您则需要提供相关说明，告诉开发人员如果任何 enum 类型中添加了新元素，他们的代码应该如何响应。

示例

在此例中，声明了一个枚举 Days。两个枚举数被显式转换为整数并赋给整型变量。

C# 复制代码
public class EnumTest
{
    enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

    static void Main()
    {
        int x = (int)Days.Sun;
        int y = (int)Days.Fri;
        Console.WriteLine("Sun = {0}", x);
        Console.WriteLine("Fri = {0}", y);
    }
}
/* Output:
   Sun = 0
   Fri = 5
*/

在此例中，使用了基类选项来声明成员类型是 long 的 enum。请注意，即使枚举的基础类型是 long，也仍然必须使用强制转换将枚举成员显式转换为 long 类型。

C# 复制代码
public class EnumTest2
{
    enum Range : long { Max = 2147483648L, Min = 255L };
    static void Main()
    {
        long x = (long)Range.Max;
        long y = (long)Range.Min;
        Console.WriteLine("Max = {0}", x);
        Console.WriteLine("Min = {0}", y);
    }
}
/* Output:
   Max = 2147483648
   Min = 255
*/

下面的代码示例阐释 enum 声明上的 System..::.FlagsAttribute 属性的使用和效果。

C# 复制代码
[Flags]
public enum CarOptions
{
    SunRoof = 0x01,
    Spoiler = 0x02,
    FogLights = 0x04,
    TintedWindows = 0x08,
}

class FlagTest
{
    static void Main()
    {
        CarOptions options = CarOptions.SunRoof | CarOptions.FogLights;
        Console.WriteLine(options);
        Console.WriteLine((int)options);
    }
}
/* Output:
   SunRoof, FogLights
   5
*/

注释

请注意，如果移除 FlagsAttribute，此示例的输出为：

5

5

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.10 枚举

  • 6.2.2 显式枚举转换

  • 14 枚举

请参见

任务

“属性”示例

概念

枚举类型（C# 编程指南）
枚举设计

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）

其他资源

C# 参考


float（C# 参考）

[image]

C# 语言参考
float（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

float 关键字表示存储 32 位浮点值的简单类型。下表显示了 float 类型的精度和大致范围。

类型 | 大致范围 | 精度 | .NET Framework 类型
float | ±1.5 × 10-45 到 ±3.4 × 1038 | 7 位 | System..::.Single

文本

默认情况下，赋值运算符右侧的实数被视为 double。因此，应使用后缀 f 或 F 初始化浮点型变量，如以下示例中所示：

复制代码
float x = 3.5F;

如果在以上声明中不使用后缀，则会因为您试图将一个 double 值存储到 float 变量中而发生编译错误。

转换

可在一个表达式中兼用数值整型和浮点型。在此情况下，整型将转换为浮点型。根据以下规则计算表达式：

  • 如果其中一个浮点型为 double，则表达式的计算结果为 double 或 bool（在关系表达式或布尔表达式中）。

  • 如果表达式中不存在 double 类型，则表达式的计算结果为 float 或 bool（在关系表达式或布尔表达式中）。

浮点表达式可以包含下列值集：

  • 正零和负零

  • 正无穷和负无穷

  • 非数字值 (NaN)

  • 有限的非零值集

有关这些值的更多信息，请参见 IEEE 网站上的“IEEE Standard for Binary Floating-Point Arithmetic”（二进制浮点算法的 IEEE 标准）。

示例

在下面的示例中，包含 int、short 和 float 类型的数学表达式得到一个 float 结果。（请记住 float 是 System..::.Single 类型的别名。）请注意，表达式中没有 double。

C# 复制代码
class FloatTest
{
    static void Main()
    {
        int x = 3;
        float y = 4.5f;
        short z = 5;
        var result = x * y / z;
        Console.WriteLine("The result is {0}", result);
        Type type = result.GetType();
        Console.WriteLine("result is of type {0}", type.ToString());
    }
}
/* Output:
  The result is 2.7
  result is of type System.Single //'float' is alias for 'Single'
 */

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 4.1.6 浮点型

  • 6.2.1 显式数值转换

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
Single

其他资源

C# 参考


int（C# 参考）

[image]

C# 语言参考
int（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

int 关键字表示一种整型，该类型根据下表显示的大小和范围存储值。

类型 | 范围 | 大小 | .NET Framework 类型
int | -2,147,483,648 到 2,147,483,647 | 有符号 32 位整数 | System..::.Int32

标识符

可以声明并初始化 int 类型的变量，例如：

复制代码
int i = 123;

如果整数没有后缀，则其类型为以下类型中可表示其值的第一个类型：int、uint、long、ulong。在此例中为 int 类型。

转换

存在从 int 到 long、float、double 或 decimal 的预定义隐式转换。例如：

复制代码
// '123' is an int, so an implicit conversion takes place here:
float f = 123;

存在从 sbyte、byte、short、ushort 或 char 到 int 的预定义隐式转换。例如，如果不进行强制转换，下面的赋值语句将产生编译错误：

复制代码
long aLong = 22;
int i1 = aLong;       // Error: no implicit conversion from long.
int i2 = (int)aLong;  // OK: explicit conversion.

还请注意，不存在从浮点型到 int 类型的隐式转换。例如，除非使用显式强制转换，否则以下语句将生成一个编译器错误：

复制代码
int x = 3.0;         // Error: no implicit conversion from double.
int y = (int)3.0;    // OK: explicit conversion.

有关兼用浮点型和整型的算术表达式的更多信息，请参见 float 和 double。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
Int32

其他资源

C# 参考


long（C# 参考）

[image]

C# 语言参考
long（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

long 关键字表示一种整型，该类型根据下表显示的大小和范围存储值。

类型 | 范围 | 大小 | .NET Framework 类型
long | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 有符号 64 位整数 | System..::.Int64

标识符

可如下例所示声明并初始化 long 类型的变量：

复制代码
long long1 = 4294967296;

如果整数没有后缀，则其类型为以下类型中可表示其值的第一个类型：int、uint、long、ulong。在上例中，它是 long 类型，因为它超出了 uint 的范围（有关整型的存储大小，请参见 整型表（C# 参考））。

还可以像下面这样，在 long 类型中使用后缀 L：

复制代码
long long2 = 4294967296L;

当使用后缀 L 时，将根据整数的大小确定它的类型为 long 还是 ulong。在此例中，它是 long，因为它小于 ulong 的范围的下限。

此后缀常用于调用重载方法。以下面使用 long 和 int 参数的重载方法为例：

复制代码
public static void SampleMethod(int i) {}
public static void SampleMethod(long l) {}

使用后缀 L 可保证调用正确的类型，例如：

复制代码
SampleMethod(5);    // Calling the method with the int parameter
SampleMethod(5L);   // Calling the method with the long parameter

可在同一个表达式中同时使用 long 类型和其他数值整型，这时表达式的计算结果为 long（在关系表达式或布尔表达式中为 bool）类型。例如，下列表达式计算为 long：

复制代码
898L + 88
说明：

也可用小写字母“l”作后缀。但是，因为字母“l”容易与数字“1”混淆，会生成编译器警告。为清楚起见，请使用“L”。

有关兼用浮点型和整型的算术表达式的信息，请参见 float 和 double。

转换

存在从 long 到 float、double 或 decimal 的预定义隐式转换。其他情况下必须使用显式转换。例如，不使用显式类型转换时，下列语句将产生编译错误：

复制代码
int x = 8L;        // Error: no implicit conversion from long to int
int x = (int)8L;   // OK: explicit conversion to int

存在从 sbyte、byte、short、ushort、int、uint 或 char 到 long 的预定义隐式转换。

还请注意，不存在从浮点型到 long 类型的隐式转换。例如，除非使用显式强制转换，否则以下语句将生成一个编译器错误：

复制代码
long x = 3.0;         // Error: no implicit conversion from double
long y = (long)3.0;   // OK: explicit conversion

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
Int64

其他资源

C# 参考


sbyte（C# 参考）

[image]

C# 语言参考
sbyte（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

sbyte 关键字表示一种整型，该类型根据下表显示的大小和范围存储值。

类型 | 范围 | 大小 | .NET Framework 类型
sbyte | -128 到 127 | 有符号 8 位整数 | System..::.SByte

标识符

可使用下述方法声明并初始化 sbyte 类型的变量：

复制代码
sbyte sByte1 = 127;

在以上声明中，整数 127 从 int 隐式转换为 sbyte。如果整数超出了 sbyte 的范围，将产生编译错误。

调用重载方法时必须使用强制转换。以下面使用 sbyte 和 int 参数的重载方法为例：

复制代码
public static void SampleMethod(int i) {}
public static void SampleMethod(sbyte b) {}

使用 sbyte 强制转换可保证调用正确的类型，例如：

复制代码
// Calling the method with the int parameter:
SampleMethod(5);
// Calling the method with the sbyte parameter:
SampleMethod((sbyte)5);

转换

存在从 sbyte 到 short、int、long、float、double 或 decimal 的预定义隐式转换。

不能将存储大小更大的非文本数值类型隐式转换为 sbyte 类型（有关整型的存储大小的信息，请参见 整型表（C# 参考））。例如，请看以下两个 sbyte 变量 x 和 y：

复制代码
sbyte x = 10, y = 20;

以下赋值语句将产生一个编译错误，原因是赋值运算符右侧的算术表达式在默认情况下的计算结果为 int。

复制代码
sbyte z = x + y;   // Error: conversion from int to sbyte

若要更正此问题，请对该表达式执行强制转换，如下例所示：

复制代码
sbyte z = (sbyte)(x + y);   // OK: explicit conversion

但是，在目标变量具有相同或更大的存储大小时，使用下列语句是可能的：

复制代码
sbyte x = 10, y = 20;
int m = x + y;
long n = x + y;

还请注意，不存在从浮点型到 sbyte 类型的隐式转换。例如，除非使用显式强制转换，否则以下语句将生成一个编译器错误：

复制代码
sbyte x = 3.0;         // Error: no implicit conversion from double
sbyte y = (sbyte)3.0;  // OK: explicit conversion

有关兼用浮点型和整型的算术表达式的信息，请参见 float 和 double。

有关隐式数值转换规则的更多信息，请参见隐式数值转换表（C# 参考）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
SByte

其他资源

C# 参考


short（C# 参考）

[image]

C# 语言参考
short（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

short 关键字表示一种整数数据类型，该类型根据下表显示的大小和范围存储值。

类型 | 范围 | 大小 | .NET Framework 类型
short | -32,768 到 32,767 | 有符号 16 位整数 | System..::.Int16

标识符

可如下例所示声明并初始化 short 类型的变量：

复制代码
short x = 32767;

在以上声明中，整数 32767 从 int 隐式转换为 short。如果整数的长度超过了 short 存储位置的大小，则将产生编译错误。

调用重载方法时必须使用强制转换。以下面使用 short 和 int 参数的重载方法为例：

复制代码
public static void SampleMethod(int i) {}
public static void SampleMethod(short s) {}

使用 short 强制转换可保证调用正确的类型，例如：

复制代码
SampleMethod(5);         // Calling the method with the int parameter
SampleMethod((short)5);  // Calling the method with the short parameter

转换

存在从 short 到 int、long、float、double 或 decimal 的预定义隐式转换。

不能将存储大小更大的非文本数值类型隐式转换为 short 类型（有关整型的存储大小的信息，请参见 整型表（C# 参考））。例如，请看以下两个 short 变量 x 和 y：

复制代码
short x = 5, y = 12;

以下赋值语句将产生一个编译错误，原因是赋值运算符右侧的算术表达式在默认情况下的计算结果为 int 类型。

short z = x + y; // Error: no conversion from int to short

若要解决此问题，请使用强制转换：

short z = (short)(x + y); // OK: explicit conversion

但是，在目标变量具有相同或更大的存储大小时，使用下列语句是可能的：

复制代码
int m = x + y;
long n = x + y;

不存在从浮点型到 short 的隐式转换。例如，除非使用显式强制转换，否则以下语句将生成一个编译器错误：

复制代码
short x = 3.0;          // Error: no implicit conversion from double
short y = (short)3.0;   // OK: explicit conversion

有关兼用浮点型和整型的算术表达式的信息，请参见 float 和 double。

有关隐式数值转换规则的更多信息，请参见 隐式数值转换表（C# 参考）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
Int16

其他资源

C# 参考


struct（C# 参考）

[image]

C# 语言参考
struct（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

struct 类型是一种值类型，通常用来封装小型相关变量组，例如，矩形的坐标或库存商品的特征。下面的示例显示了一个简单的结构声明。

复制代码
public struct Book
{
    public decimal price;
    public string title;
    public string author;
}

备注

结构还可以包含构造函数、常量、字段、方法、属性、索引器、运算符、事件和嵌套类型，但如果同时需要上述几种成员，则应当考虑改为使用类作为类型。

结构可以实现接口，但它们无法继承另一个结构。因此，结构成员无法声明为 protected。

有关更多信息，请参见 结构（C# 编程指南）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 11 结构

请参见

概念

C# 编程指南

参考

C# 关键字
内置类型表（C# 参考）
值类型（C# 参考）
class（C# 参考）
接口（C# 参考）
类和结构（C# 编程指南）

其他资源

C# 参考
默认值表（C# 参考）
类型（C# 参考）


uint（C# 参考）

[image]

C# 语言参考
uint（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

uint 关键字表示一种整型，该类型根据下表显示的大小和范围存储值。

类型 | 范围 | 大小 | .NET Framework 类型
uint | 0 到 4,294,967,295 | 无符号 32 位整数 | System..::.UInt32

注意 uint 类型与 CLS 不兼容。应尽可能使用 int。

标识符

可如下例所示声明并初始化 uint 类型的变量：

复制代码
uint myUint = 4294967290;

如果整数没有后缀，则其类型为以下类型中可表示其值的第一个类型：int、uint、long、ulong。在此例中，它是 uint：

复制代码
uint uInt1 = 123;

还可以像下面这样使用后缀 u 或 U：

复制代码
uint uInt2 = 123U;

当使用后缀 U 或 u 时，将根据文本的数值来确定文本的类型是 uint 还是 ulong。例如：

复制代码
Console.WriteLine(44U.GetType());
Console.WriteLine(323442434344U.GetType());

此代码先后显示 System.UInt32 和 System.UInt64（它们分别是 uint 和 ulong 的基础类型），因为第二个文本太大，无法用 uint 类型来存储。

转换

存在从 uint 到 long、ulong、float、double 或 decimal 的预定义隐式转换。例如：

复制代码
float myFloat = 4294967290;   // OK: implicit conversion to float

存在从 byte、ushort 或 char 到 uint 的预定义隐式转换。否则必须使用显式转换。例如，如果不进行强制转换，下面的赋值语句将产生编译错误：

复制代码
long aLong = 22;
// Error -- no implicit conversion from long:
uint uInt1 = aLong;
// OK -- explicit conversion:
uint uInt2 = (uint)aLong;

还请注意，不存在从浮点型到 uint 类型的隐式转换。例如，除非使用显式强制转换，否则以下语句将生成一个编译器错误：

复制代码
// Error -- no implicit conversion from double:
uint x = 3.0;
// OK -- explicit conversion:
uint y = (uint)3.0;

有关兼用浮点型和整型的算术表达式的信息，请参见 float 和 double。

有关隐式数值转换规则的更多信息，请参见隐式数值转换表（C# 参考）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
UInt32

其他资源

C# 参考


ulong（C# 参考）

[image]

C# 语言参考
ulong（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

ulong 关键字表示一种整型，该类型根据下表显示的大小和范围存储值。

类型 | 范围 | 大小 | .NET Framework 类型
ulong | 0 到 18,446,744,073,709,551,615 | 无符号 64 位整数 | System..::.UInt64

标识符

可如下例所示声明并初始化 ulong 类型的变量：

复制代码
ulong uLong = 9223372036854775808;

如果整数没有后缀，则其类型为以下类型中可表示其值的第一个类型：int、uint、long、ulong。在上面的示例中，它是 ulong 类型。

还可根据以下规则使用后缀指定文字类型：

  • 如果使用 L 或 l，那么根据整数的大小，可以判断出其类型为 long 还是 ulong。

    说明：

    注意也可用小写字母“l”作后缀。但是，因为字母“l”容易与数字“1”混淆，会生成编译器警告。为清楚起见，请使用“L”。

  • 如果使用 U 或 u，那么根据整数的大小，可以判断出其类型为 uint 还是 ulong。

  • 如果使用 UL、ul、Ul、uL、LU、lu、Lu 或 lU，则整数的类型为 ulong。

    例如，以下三个语句的输出将为系统类型 UInt64，此类型对应于别名 ulong：

    复制代码
    Console.WriteLine(9223372036854775808L.GetType());
Console.WriteLine(123UL.GetType());
Console.WriteLine((123UL + 456).GetType());

此后缀常用于调用重载方法。以下面使用 ulong 和 int 参数的重载方法为例：

复制代码
public static void SampleMethod(int i) {}
public static void SampleMethod(ulong l) {}

在 ulong 参数后加上后缀可保证调用正确的类型，例如：

复制代码
SampleMethod(5);    // Calling the method with the int parameter
SampleMethod(5UL);  // Calling the method with the ulong parameter

转换

存在从 ulong 到 float、double 或 decimal 的预定义隐式转换。

不存在从 ulong 到任何整型的隐式转换。例如，不使用显式类型转换时，下列语句将产生编译错误：

复制代码
long long1 = 8UL;   // Error: no implicit conversion from ulong

存在从 byte、ushort、uint 或 char 到 ulong 的预定义隐式转换。

同样，不存在从浮点型到 ulong 类型的隐式转换。例如，除非使用显式强制转换，否则以下语句将生成一个编译器错误：

复制代码
// Error -- no implicit conversion from double:
ulong x = 3.0;
// OK -- explicit conversion:
ulong y = (ulong)3.0;

有关兼用浮点型和整型的算术表达式的信息，请参见 float 和 double。

有关隐式数值转换规则的更多信息，请参见 隐式数值转换表（C# 参考）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
UInt64

其他资源

C# 参考


ushort（C# 参考）

[image]

C# 语言参考
ushort（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

ushort 关键字表示整数数据类型，该类型根据下表显示的大小和范围存储值。

类型 | 范围 | 大小 | .NET Framework 类型
ushort | 0 到 65,535 | 无符号 16 位整数 | System..::.UInt16

标识符

可如下例所示声明并初始化 ushort 类型的变量：

复制代码
ushort myShort = 65535;

在以上声明中，整数 65535 从 int 隐式转换为 ushort。如果整数超出了 ushort 的范围，将产生编译错误。

调用重载方法时，必须使用强制转换。以下面使用 ushort 和 int 参数的重载方法为例：

复制代码
public static void SampleMethod(int i) {}
public static void SampleMethod(ushort s) {}

使用 ushort 强制转换可保证调用正确的类型，例如：

复制代码
// Calls the method with the int parameter:
SampleMethod(5);
// Calls the method with the ushort parameter:
SampleMethod((ushort)5);

转换

存在从 ushort 到 int、uint、long、ulong、float、double 或 decimal 的预定义隐式转换。

存在从 byte 或 char 到 ushort 的预定义隐式转换。其他情况下必须使用显式转换。例如，请看以下两个 ushort 变量 x 和 y：

复制代码
ushort x = 5, y = 12;

以下赋值语句将产生一个编译错误，原因是赋值运算符右侧的算术表达式在默认情况下的计算结果为 int。

复制代码
ushort z = x + y;   // Error: conversion from int to ushort

若要解决此问题，请使用强制转换：

复制代码
ushort z = (ushort)(x + y);   // OK: explicit conversion

但是，在目标变量具有相同或更大的存储大小时，使用下列语句是可能的：

复制代码
int m = x + y;
long n = x + y;

还请注意，不存在从浮点型到 ushort 类型的隐式转换。例如，除非使用显式强制转换，否则以下语句将生成一个编译器错误：

复制代码
// Error -- no implicit conversion from double:
ushort x = 3.0;
// OK -- explicit conversion:
ushort y = (ushort)3.0;

有关兼用浮点型和整型的算术表达式的信息，请参见 float 和 double。

有关隐式数值转换规则的更多信息，请参见隐式数值转换表（C# 参考）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.3 类型和变量

  • 4.1.5 整型

请参见

概念

C# 编程指南

参考

C# 关键字
整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）
显式数值转换表（C# 参考）
UInt16

其他资源

C# 参考


引用类型（C# 参考）

[image]

C# 语言参考
引用类型（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

引用类型的变量又称为对象，可存储对实际数据的引用。本节介绍以下用于声明引用类型的关键字：

  • class

  • interface

  • delegate

本节也介绍以下内置引用类型：

  • object

  • string

请参见

概念

C# 编程指南

参考

C# 关键字
值类型（C# 参考）

其他资源

C# 参考
类型（C# 参考）


class（C# 参考）

[image]

C# 语言参考
class（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

类是使用关键字 class 声明的，如下面的示例所示：

复制代码
class TestClass
{
    // Methods, properties, fields, events, delegates
    // and nested classes go here.
}

备注

与 C++ 不同，C# 中仅允许单个继承。也就是说，类只能从一个基类继承实现。但是，一个类可以实现一个以上的接口。下表给出了类继承和接口实现的一些示例：

继承 | 示例
无 | class ClassA { }
单一 | class DerivedClass: BaseClass { }
无，实现两个接口 | class ImplClass: IFace1, IFace2 { }
单一，实现一个接口 | class ImplDerivedClass: BaseClass, IFace1 { }

只有嵌套类允许访问级别 protected 和 private。

还可以声明有类型参数的泛型类；有关更多信息，请参见泛型类。

一个类可包含下列成员的声明：

  • 构造函数

  • 析构函数

  • 常量

  • 字段

  • 方法

  • 属性

  • 索引器

  • 运算符

  • 事件

  • 委托

  • 类

  • 接口

  • 结构

示例

下面的示例说明如何声明类的字段、构造函数和方法。该例还说明了如何实例化对象及如何打印实例数据。在此例中声明了两个类，一个是 Child 类，它包含两个私有字段（name 和 age）和两个公共方法。第二个类 TestClass 用来包含 Main。

C# 复制代码
class Child
{
    private int age;
    private string name;

    // Default constructor:
    public Child()
    {
        name = "N/A";
    }

    // Constructor:
    public Child(string name, int age)
    {
        this.name = name;
        this.age = age;
    }

    // Printing method:
    public void PrintChild()
    {
        Console.WriteLine("{0}, {1} years old.", name, age);
    }
}

class StringTest
{
    static void Main()
    {
        // Create objects by using the new operator:
        Child child1 = new Child("Craig", 11);
        Child child2 = new Child("Sally", 10);

        // Create an object using the default constructor:
        Child child3 = new Child();

        // Display results:
        Console.Write("Child #1: ");
        child1.PrintChild();
        Console.Write("Child #2: ");
        child2.PrintChild();
        Console.Write("Child #3: ");
        child3.PrintChild();
    }
}
/* Output:
    Child #1: Craig, 11 years old.
    Child #2: Sally, 10 years old.
    Child #3: N/A, 0 years old.
*/

注释

注意：在上例中，私有字段（name 和 age）只能通过 Child 类的公共方法访问。例如，不能在 Main 方法中使用如下语句打印 Child 的名称：

复制代码
Console.Write(child1.name);   // Error

只有当 Child 是 Main 的成员时，才能从 Main 访问该类的私有成员。

如果在类的内部声明的类型没有访问修饰符，则该类型默认为 private，因此，如果移除关键字，则此示例中的数据成员仍然会是 private 的。

最后要注意的是，默认情况下，对于使用默认构造函数 (child3) 创建的对象，age 字段初始化为零。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6 类和对象

  • 3.4.4 类成员

  • 4.2.1 类类型

  • 10 类

请参见

概念

C# 编程指南

参考

C# 关键字
引用类型（C# 参考）

其他资源

C# 参考


委托（C# 参考）

[image]

C# 语言参考
委托（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

委托类型的声明与方法签名相似，有一个返回值和任意数目任意类型的参数：

复制代码
public delegate void TestDelegate(string message);
public delegate int TestDelegate(MyType m, long num);

delegate 是一种可用于封装命名或匿名方法的引用类型。委托类似于 C++ 中的函数指针；但是，委托是类型安全和可靠的。有关委托的应用，请参见委托和泛型委托。

备注

委托是事件的基础。

通过将委托与命名方法或匿名方法关联，可以实例化委托。有关更多信息，请参见命名方法和匿名方法。

必须使用具有兼容返回类型和输入参数的方法或 lambda 表达式实例化委托。有关方法签名中允许的方差度的更多信息，请参见委托中的协变和逆变。为了与匿名方法一起使用，委托和与之关联的代码必须一起声明。本节讨论这两种实例化委托的方法。

示例

C# 复制代码
// Declare delegate -- defines required signature:
delegate double MathAction(double num);

class DelegateTest
{
    // Regular method that matches signature:
    static double Double(double input)
    {
        return input * 2;
    }

    static void Main()
    {
        // Instantiate delegate with named method:
        MathAction ma = Double;

        // Invoke delegate ma:
        double multByTwo = ma(4.5);
        Console.WriteLine(multByTwo);

        // Instantiate delegate with anonymous method:
        MathAction ma2 = delegate(double input)
        {
            return input * input;
        };

        double square = ma2(5);
        Console.WriteLine(square);

        // Instantiate delegate with lambda expression
        MathAction ma3 = s => s * s * s;
        double cube = ma3(4.375);

        Console.WriteLine(cube);
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.11 委托

  • 15 委托

请参见

概念

C# 编程指南

参考

C# 关键字
引用类型（C# 参考）
委托（C# 编程指南）
事件（C# 编程指南）
带有命名方法的委托与带有匿名方法的委托（C# 编程指南）
匿名方法（C# 编程指南）

其他资源

C# 参考


接口（C# 参考）

[image]

C# 语言参考
接口（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

接口只包含方法、属性、事件或索引器的签名。成员的实现是在实现接口的类或结构中完成的，如下面的示例所示：

示例

C# 复制代码
interface ISampleInterface
{
    void SampleMethod();
}

class ImplementationClass : ISampleInterface
{
    // Explicit interface member implementation:
    void ISampleInterface.SampleMethod()
    {
        // Method implementation.
    }

    static void Main()
    {
        // Declare an interface instance.
        ISampleInterface obj = new ImplementationClass();

        // Call the member.
        obj.SampleMethod();
    }
}

接口可以是命名空间或类的成员，并且可以包含下列成员的签名：

  • 方法

  • 属性

  • 索引器

  • 事件

一个接口可从一个或多个基接口继承。

当基类型列表包含基类和接口时，基类必须是列表中的第一项。

实现接口的类可以显式实现该接口的成员。显式实现的成员不能通过类实例访问，而只能通过接口实例访问。

有关显式接口实现的更多详细信息和代码示例，请参见显式接口实现（C# 编程指南）。

下面的示例演示了接口实现。在此示例中，接口包含属性声明，类包含实现。

C# 复制代码
interface IPoint
{
   // Property signatures:
   int x
   {
      get;
      set;
   }

   int y
   {
      get;
      set;
   }
}

class Point : IPoint
{
   // Fields:
   private int _x;
   private int _y;

   // Constructor:
   public Point(int x, int y)
   {
      _x = x;
      _y = y;
   }

   // Property implementation:
   public int x
   {
      get
      {
         return _x;
      }

      set
      {
         _x = value;
      }
   }

   public int y
   {
      get
      {
         return _y;
      }
      set
      {
         _y = value;
      }
   }
}

class MainClass
{
   static void PrintPoint(IPoint p)
   {
      Console.WriteLine("x={0}, y={1}", p.x, p.y);
   }

   static void Main()
   {
      Point p = new Point(2, 3);
      Console.Write("My Point: ");
      PrintPoint(p);
   }
}
// Output: My Point: x=2, y=3

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.9 接口

  • 3.4.5 接口成员

  • 4.2.4 接口类型

  • 10.1.2.2 接口实现

  • 11.2 结构接口

  • 13 接口

请参见

概念

C# 编程指南

参考

C# 关键字
引用类型（C# 参考）
使用属性（C# 编程指南）
使用索引器（C# 编程指南）
class（C# 参考）
struct（C# 参考）

其他资源

C# 参考


object（C# 参考）

[image]

C# 语言参考
object（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

object 类型在 .NET Framework 中是 Object 的别名。在 C# 的统一类型系统中，所有类型（预定义类型、用户定义类型、引用类型和值类型）都是直接或间接从 Object 继承的。可以将任何类型的值赋给 object 类型的变量。将值类型的变量转换为对象的过程称为“装箱”。将对象类型的变量转换为值类型的过程称为“取消装箱”。有关更多信息，请参见装箱和取消装箱。

示例

下面的示例演示了 object 类型的变量如何接受任何数据类型的值，以及 object 类型的变量如何在 .NET Framework 中使用 Object 的方法。

C# 复制代码
class ObjectTest
{
   public int i = 10;
}

class MainClass2
{
   static void Main()
   {
      object a;
      a = 1;   // an example of boxing
      Console.WriteLine(a);
      Console.WriteLine(a.GetType());
      Console.WriteLine(a.ToString());

      a = new ObjectTest();
      ObjectTest classRef;
      classRef = (ObjectTest)a;
      Console.WriteLine(classRef.i);
   }
}
/* Output
    1
    System.Int32
    1
 * 10
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1 简介

  • 4.2.2 对象类型

请参见

概念

C# 编程指南

参考

C# 关键字
引用类型（C# 参考）
值类型（C# 参考）

其他资源

C# 参考


string（C# 参考）

[image]

C# 语言参考
string（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

string 类型表示零或更多 Unicode 字符组成的序列。string 是 .NET Framework 中 String 的别名。

尽管 string 是引用类型，但定义相等运算符（== 和 !=）是为了比较 string 对象（而不是引用）的值。这使得对字符串相等性的测试更为直观。例如：

复制代码
string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine((object)a == (object)b);

这将先显示“True”，然后显示“False”，因为字符串的内容是相同的，但是 a 和 b 引用的不是同一个字符串实例。

+ 运算符用于连接字符串：

复制代码
string a = "good " + "morning";

这将创建一个包含“good morning”的字符串对象。

字符串是不可变的，即：字符串对象在创建后，尽管从语法上看您似乎可以更改其内容，但事实上并不可行。例如，编写此代码时，编译器实际上会创建一个新字符串对象来保存新的字符序列，变量 b 继续保存“h”。

复制代码
string b = "h";
b += "ello";

[] 运算符可以用于对 string 的各个字符的只读访问。

复制代码
string str = "test";
char x = str[2];  // x = 's';

字符串为 string 类型并可写成两种形式，即用引号引起来和用 @ 引起来。用引号引起来的字符串括在双引号 (") 内：

复制代码
"good morning"  // a string literal

字符串文本可包含任何字符。包括转义序列：

复制代码
string a = "\\\u0066\n";

上面的字符串包含一个反斜杠、字母 f 和一个新行。

说明：

转义码 \udddd（其中 dddd 是一个四位数）表示 Unicode 字符 U+dddd。此外还识别 8 位 Unicode 转义码：\udddd\udddd。

原义字符串以 @ 开头并且也用双引号引起来。例如：

复制代码
@"good morning"  // a string literal

原义字符串的优势在于不 处理转义序列，因此很容易写入，例如完全限定的文件名就是原义字符串：

复制代码
@"c:\Docs\Source\a.txt"  // rather than "c:\\Docs\\Source\\a.txt"

若要在一个用 @ 引起来的字符串中包括一个双引号，请使用两对双引号：

复制代码
@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

@ 符号的另一种用法是使用作为 C# 关键字的被引用的 (/reference) 标识符。

有关 C# 中字符串的更多信息，请参见字符串（C# 编程指南）。

示例

C# 复制代码
class SimpleStringTest
{
   static void Main()
   {
      string a = "\u0068ello ";
      string b = "world";
      Console.WriteLine( a + b );
      Console.WriteLine( a + b == "Hello World" ); // == performs a case-sensitive comparison
   }
}
/* Output:
    hello world
    False
 */

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 2.4.2 标识符

  • 2.4.4.5 字符串

  • 4.2.3 字符串类型

  • 7.9.7 字符串相等运算符

请参见

概念

C# 编程指南
C# 编程指南

参考

C# 关键字
引用类型（C# 参考）
值类型（C# 参考）

其他资源

C# 参考
设置数值结果表的格式（C# 参考）


void（C# 参考）

[image]

C# 语言参考
void（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

用作方法的返回类型时，void 关键字指定方法不返回值。

在方法的参数列表中不允许使用 void。采用以下形式声明一个无参数的、不返回值的方法：

复制代码
void SampleMethod();

也可在不安全上下文中使用 void 将指针声明为未知类型。有关更多信息，请参见 指针类型（C# 编程指南）。

void 是 .NET Framework System..::.Void 类型的别名。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.6 方法

请参见

概念

C# 编程指南

参考

C# 关键字
引用类型（C# 参考）
值类型（C# 参考）
方法（C# 编程指南）
不安全代码和指针（C# 编程指南）

其他资源

C# 参考


var（C# 参考）

[image]

C# 语言参考
var（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在方法范围中声明的变量可以具有隐式类型 var。隐式类型的本地变量是强类型变量（就好像您已经声明该类型一样），但由编译器确定类型。下面的两个 i 声明在功能上是等效的：

复制代码
var i = 10; // implicitly typed
int i = 10; //explicitly typed

有关更多信息，请参见隐式类型的局部变量（C# 编程指南）和查询操作中的类型关系 (LINQ)。

示例

下面的示例演示了两个查询表达式。在第一个表达式中，允许但不需要使用 var，因为可以将查询结果的类型显式声明为 IEnumerable<string>。但是，在第二个表达式中必须使用 var，因为结果是一个匿名类型集合，而该类型的名称只有编译器本身可以访问。请注意，在第二个示例中，foreach 迭代变量 item 也必须转换为隐式类型。

C# 复制代码
// Example #1: var is optional because
// the select clause specifies a string
string[] words = { "apple", "strawberry", "grape", "peach", "banana" };
var wordQuery = from word in words
                where word[0] == 'g'
                select word;

// Because each element in the sequence is a string,
// not an anonymous type, var is optional here also.
foreach (string s in wordQuery)
{
    Console.WriteLine(s);
}

// Example #2: var is required because
// the select clause specifies an anonymous type
var custQuery = from cust in customers
                where cust.City == "Phoenix"
                select new { cust.Name, cust.Phone };

// var must be used because each item
// in the sequence is an anonymous type
foreach (var item in custQuery)
{
    Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone);
}

请参见

概念

C# 编程指南

参考

隐式类型的局部变量（C# 编程指南）

其他资源

C# 参考


类型参考表（C# 参考）

[image]

C# 语言参考
类型参考表（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

以下参考表概括了 C# 的类型：

内置类型表

整型

浮点型

默认值

值类型

隐式数值转换

显式数值转换表

有关格式化数字类型输出的信息，请参见格式化数值结果表。

请参见

概念

C# 编程指南

参考

引用类型（C# 参考）
值类型（C# 参考）

其他资源

C# 参考


内置类型表（C# 参考）

[image]

C# 语言参考
内置类型表（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

下表显示了内置 C# 类型的关键字，这些类型是 System 命名空间中的预定义类型的别名。

C# 类型 | .NET Framework 类型
bool | System.Boolean
byte | System.Byte
sbyte | System.SByte
char | System.Char
decimal | System.Decimal
double | System.Double
float | System.Single
int | System.Int32
uint | System.UInt32
long | System.Int64
ulong | System.UInt64
object | System.Object
short | System.Int16
ushort | System.UInt16
string | System.String

备注

除了 object 和 string 外，表中的所有类型均称为简单类型。

C# 类型的关键字及其别名可以互换。例如，可使用下列两种声明中的一种来声明一个整数变量：

复制代码
int x = 123;
System.Int32 x = 123;

若要显示任何 C# 类型的实际类型，请使用系统方法 GetType()。例如，下列语句显示了表示 myVariable 类型的系统别名：

复制代码
Console.WriteLine(myVariable.GetType());

还可使用 typeof 运算符。

请参见

概念

C# 编程指南

参考

C# 关键字
值类型（C# 参考）

其他资源

C# 参考
默认值表（C# 参考）
设置数值结果表的格式（C# 参考）
类型参考表（C# 参考）


整型表（C# 参考）

[image]

C# 语言参考
整型表（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

下表显示了整型的大小和范围，这些类型构成了简单类型的一个子集。

类型 | 范围 | 大小
sbyte | -128 到 127 | 有符号 8 位整数
byte | 0 到 255 | 无符号 8 位整数
char | U+0000 到 U+ffff | 16 位 Unicode 字符
short | -32,768 到 32,767 | 有符号 16 位整数
ushort | 0 到 65,535 | 无符号 16 位整数
int | -2,147,483,648 到 2,147,483,647 | 有符号 32 位整数
uint | 0 到 4,294,967,295 | 无符号 32 位整数
long | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 有符号 64 位整数
ulong | 0 到 18,446,744,073,709,551,615 | 无符号 64 位整数

如果整数表示的值超出了 ulong 的范围，将产生编译错误。

请参见

概念

C# 编程指南

参考

C# 关键字
内置类型表（C# 参考）

其他资源

C# 参考
浮点型表（C# 参考）
默认值表（C# 参考）
设置数值结果表的格式（C# 参考）
类型参考表（C# 参考）


浮点型表（C# 参考）

[image]

C# 语言参考
浮点型表（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

下表显示了浮点型的精度和大致范围。

类型 | 大致范围 | 精度
float | ±1.5e−45 到 ±3.4e38 | 7 位
double | ±5.0e−324 到 ±1.7e308 | 15 到 16 位

请参见

概念

C# 编程指南

参考

内置类型表（C# 参考）
整型表（C# 参考）
decimal（C# 参考）

其他资源

C# 参考
默认值表（C# 参考）
设置数值结果表的格式（C# 参考）
类型参考表（C# 参考）


默认值表（C# 参考）

[image]

C# 语言参考
默认值表（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

下表显示了由默认构造函数返回的值类型的默认值。默认构造函数是通过 new 运算符来调用的，如下所示：

复制代码
int myInt = new int();

以上语句同下列语句效果相同：

复制代码
int myInt = 0;

请记住：在 C# 中不允许使用未初始化的变量。

值类型 | 默认值
bool | false
byte | 0
char | '\0'
decimal | 0.0M
double | 0.0D
enum | 表达式 (E)0 产生的值，其中 E 为 enum 标识符。
float | 0.0F
int | 0
long | 0L
sbyte | 0
short | 0
struct | 将所有的值类型字段设置为默认值并将所有的引用类型字段设置为 null 时产生的值。
uint | 0
ulong | 0
ushort | 0

请参见

概念

C# 编程指南

参考

值类型（C# 参考）
内置类型表（C# 参考）

其他资源

C# 参考
类型参考表（C# 参考）


值类型表（C# 参考）

[image]

C# 语言参考
值类型表（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

下表按类别列出了 C# 的值类型。

值类型 | 类别
bool | 布尔型
byte | 无符号、数值、整数
char | 无符号、数值、整数
decimal | 数值、十进制
double | 数值、浮点
enum | 枚举
float | 数值、浮点
int | 有符号、数值、整数
long | 有符号、数值、整数
sbyte | 有符号、数值、整数
short | 有符号、数值、整数
struct | 用户定义的结构
uint | 无符号、数值、整数
ulong | 无符号、数值、整数
ushort | 无符号、数值、整数

请参见

概念

C# 编程指南

参考

值类型（C# 参考）

其他资源

C# 参考
设置数值结果表的格式（C# 参考）
类型参考表（C# 参考）


隐式数值转换表（C# 参考）

[image]

C# 语言参考
隐式数值转换表（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

下表显示了预定义的隐式数值转换。隐式转换可能在多种情形下发生，包括调用方法时和在赋值语句中。

从 | 到
sbyte | short、int、long、float、double 或 decimal
byte | short、ushort、int、uint、long、ulong、float、double 或 decimal
short | int、long、float、double 或 decimal
ushort | int、uint、long、ulong、float、double 或 decimal
int | long、float、double 或 decimal
uint | long、ulong、float、double 或 decimal
long | float、double 或 decimal
char | ushort、int、uint、long、ulong、float、double 或 decimal
float | double
ulong | float、double 或 decimal

备注

  • 从 int、uint 或 long 到 float 的转换以及从 long 到 double 的转换的精度可能会降低，但数值大小不受影响。

  • 不存在到 char 类型的隐式转换。

  • 不存在浮点型与 decimal 类型之间的隐式转换。

  • int 类型的常数表达式可转换为 sbyte、byte、short、ushort、uint 或 ulong，前提是常数表达式的值处于目标类型的范围之内。

C# 语言规范

有关更多信息，请参见 C# 语言规范（C# 语言规范）：

  • 6.1 隐式转换

  • 7.15 常数表达式

请参见

概念

C# 编程指南

参考

整型表（C# 参考）
内置类型表（C# 参考）
显式数值转换表（C# 参考）

其他资源

C# 参考


显式数值转换表（C# 参考）

[image]

C# 语言参考
显式数值转换表（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

显式数值转换用于通过显式转换表达式，将任何数字类型转换为任何其他数字类型。对于它不存在隐式转换。下表显示了这些转换。

从 | 到
sbyte | byte、ushort、uint、ulong 或 char
byte | Sbyte 或者char
short | sbyte、byte、ushort、uint、ulong 或 char
ushort | sbyte、byte、short 或 char
int | sbyte、byte、short、ushort、uint、ulong 或 char
uint | sbyte、byte、short、ushort、int 或 char
long | sbyte、byte、short、ushort、int、uint、ulong 或 char
ulong | sbyte、byte、short、ushort、int、uint、long 或 char
char | sbyte、byte 或 short
float | sbyte、byte、short、ushort、int、uint、long、ulong、char 或 decimal
double | sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 decimal
decimal | sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 double

备注

  • 显式数值转换可能导致精度损失或引发异常。

  • 将 decimal 值转换为整型时，该值将舍入为与零最接近的整数值。如果结果整数值超出目标类型的范围，则会引发 OverflowException。

  • 将 double 或 float 值转换为整型时，值会被截断。如果该结果整数值超出了目标值的范围，其结果将取决于溢出检查上下文。在 checked 上下文中，将引发 OverflowException；而在 unchecked 上下文中，结果将是一个未指定的目标类型的值。

  • 将 double 转换为 float 时，double 值将舍入为最接近的 float 值。如果 double 值因过小或过大而使目标类型无法容纳它，则结果将为零或无穷大。

  • 将 float 或 double 转换为 decimal 时，源值将转换为 decimal 表示形式，并舍入为第 28 个小数位之后最接近的数（如果需要）。根据源值的不同，可能产生以下结果：

    • 如果源值因过小而无法表示为 decimal，那么结果将为零。

    • 如果源值为 NaN（非数字值）、无穷大或因过大而无法表示为 decimal，则会引发 OverflowException。

  • 将 decimal 转换为 float 或 double 时，decimal 值将舍入为最接近的 double 或 float 值。

有关显式转换的更多信息，请参见“6.2 C# 语言规范中的显式”。有关如何访问此规范的更多信息，请参见 C# 语言规范。

请参见

概念

C# 编程指南

参考

整型表（C# 参考）
内置类型表（C# 参考）
隐式数值转换表（C# 参考）

其他资源

C# 参考


设置数值结果表的格式（C# 参考）

[image]

C# 语言参考
设置数值结果表的格式（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

可以通过使用 String.Format 方法或 Console.Write 方法（它调用 String.Format）来设置数值结果的格式。通过使用格式字符串指定格式。下表包含受支持的标准格式字符串。格式字符串采用以下形式：Axx，其中 A 是格式说明符，xx 是精度说明符。格式说明符控制应用于数值的格式类型，而精度说明符则控制格式化输出的有效位数或小数位数。

有关标准及自定义格式化字符串的更多信息，请参见格式化概述。有关 String.Format 元素的更多信息，请参见 String..::.Format。

字符 | 说明 | 示例 | 输出
C 或 c | 货币 | Console.Write("{0:C}", 2.5); Console.Write("{0:C}", -2.5); | $2.50 ($2.50)
D 或 d | 十进制数 | Console.Write("{0:D5}", 25); | 00025
E 或 e | 科学型 | Console.Write("{0:E}", 250000); | 2.500000E+005
F 或 f | 固定点 | Console.Write("{0:F2}", 25); Console.Write("{0:F0}", 25); | 25.00 25
G 或 g | 常规 | Console.Write("{0:G}", 2.5); | 2.5
N 或 n | 数字 | Console.Write("{0:N}", 2500000); | 2,500,000.00
X 或 x | 十六进制 | Console.Write("{0:X}", 250); Console.Write("{0:X}", 0xffff); | FA FFFF

请参见

概念

C# 编程指南

参考

string（C# 参考）

其他资源

C# 参考
类型参考表（C# 参考）


修饰符（C# 参考）

[image]

C# 语言参考
修饰符（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

修饰符用于修改类型和类型成员的声明。本节介绍 C# 修饰符：

修饰符

用途

访问修饰符

  • public

  • private

  • internal

  • protected

指定声明的类型和类型成员的可访问性。

abstract

指示某个类只能是其他类的基类。

const

指定无法修改字段或局部变量的值。

event

声明事件。

extern

指示在外部实现方法。

new

从基类成员隐藏继承的成员。

override

提供从基类继承的虚拟成员的新实现。

partial

在整个同一程序集中定义分部类、结构和方法。

readonly

声明一个字段，该字段只能赋值为该声明的一部分或者在同一个类的构造函数中。

sealed

指定类不能被继承。

static

声明属于类型本身而不是属于特定对象的成员。

unsafe

声明不安全的上下文。

virtual

在派生类中声明其实现可由重写成员更改的方法或访问器。

volatile

指示字段可由操作系统、硬件或并发执行线程等在程序中进行修改。

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


访问修饰符（C# 参考）

[image]

C# 语言参考
访问修饰符（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

访问修饰符是一些关键字，用于指定声明的成员或类型的可访问性。本节介绍四个访问修饰符：

  • public

  • protected

  • internal

  • private

使用这些访问修饰符可指定下列五个可访问性级别：

public：访问不受限制。

protected：访问仅限于包含类或从包含类派生的类型。

Internal：访问仅限于当前程序集。

protected internal：访问仅限于当前程序集或从包含类派生的类型。

private：访问仅限于包含类型。

本节还介绍以下内容：

  • 可访问性级别：使用四个访问修饰符声明五个可访问性级别。

  • 可访问域：指定在程序节的哪个位置可以引用成员。

  • 可访问性级别的使用限制：概述了声明的可访问性级别的使用限制。

请参见

概念

C# 编程指南

参考

C# 关键字
修饰符（C# 参考）

其他资源

C# 参考


可访问性级别（C# 参考）

[image]

C# 语言参考
可访问性级别（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

使用访问修饰符 public、protected、internal 或 private 可以为成员指定以下声明的可访问性之一。

声明的可访问性 | 含义
public | 访问不受限制。
protected | 访问仅限于包含类或从包含类派生的类型。
internal | 访问仅限于当前程序集。
protectedinternal | 访问仅限于从包含类派生的当前程序集或类型。
private | 访问仅限于包含类型。

一个成员或类型只能有一个访问修饰符，但使用 protectedprotected 组合时除外。

命名空间上不允许使用访问修饰符。命名空间没有访问限制。

根据出现成员声明的上下文，只允许某些声明的可访问性。如果在成员声明中未指定访问修饰符，则使用默认的可访问性。

不嵌套在其他类型中的顶级类型的可访问性只能是 internal 或 public。这些类型的默认可访问性是 internal。

嵌套类型是其他类型的成员，它们可以具有下表所示的声明的可访问性。

属于 | 默认的成员可访问性 | 该成员允许的声明的可访问性
enum | public | 无
class | private | public protected internal private protectedinternal
interface | public | 无
struct | private | public internal private

嵌套类型的可访问性取决于它的可访问域，该域是由已声明的成员可访问性和直接包含类型的可访问域这二者共同确定的。但是，嵌套类型的可访问域不能超出包含它的类型的可访问域。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.5.1 声明的可访问性

  • 3.5.3 对实例成员的受保护访问

  • 3.5.4 可访问性约束

  • 10.3.5 访问修饰符

  • 10.3.8.2 声明的可访问性

请参见

概念

C# 编程指南

参考

C# 关键字
访问修饰符（C# 参考）
可访问域（C# 参考）
可访问性级别的使用限制（C# 参考）
访问修饰符（C# 编程指南）
public（C# 参考）
private（C# 参考）
protected（C# 参考）
internal（C# 参考）

其他资源

C# 参考


可访问域（C# 参考）

[image]

C# 语言参考
可访问域（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

成员的可访问域指定程序中可以引用成员的部分。如果成员嵌套在其他类型中，其可访问域由该成员的可访问性级别和直接包含类型的可访问域共同确定。

顶级类型的可访问域至少是声明此类型的项目的程序文本。也就是说，域包括此项目的所有源文件。嵌套类型的可访问域至少是声明它的类型的程序文本，即域是一个类型体，包括所有嵌套的类型。嵌套类型的可访问域决不能超出包含类型的可访问域。这些概念在以下示例中加以说明。

示例

该示例包含一个顶级类型 T1 和两个嵌套类 M1 和 M2。这两个类包含具有不同声明的可访问性的字段。在 Main 方法中，每个语句后都有注释，指示每个成员的可访问域。注意，尝试引用不可访问的成员的语句被注释掉了。如果希望查看由引用不可访问的成员所导致的编译器错误，请逐个移除注释。

C# 复制代码
namespace AccessibilityDomainNamespace
{
    public class T1
    {
        public static int publicInt;
        internal static int internalInt;
        private static int privateInt = 0; // CS0414

        public class M1
        {
            public static int publicInt;
            internal static int internalInt;
            private static int privateInt = 0; // CS0414
        }

        private class M2
        {
            public static int publicInt = 0;
            internal static int internalInt = 0;
            private static int privateInt = 0; // CS0414
        }
    }

    class MainClass
    {
        static void Main()
        {
            // Access is unlimited:
            T1.publicInt = 1;

            // Accessible only in current assembly:
            T1.internalInt = 2;

            // Error: inaccessible outside T1:
            //    T1.myPrivateInt = 3;

            // Access is unlimited:
            T1.M1.publicInt = 1;

            // Accessible only in current assembly:
            T1.M1.internalInt = 2;

            // Error: inaccessible outside M1:
            //    T1.M1.myPrivateInt = 3;

            // Error: inaccessible outside T1:
            //    T1.M2.myPublicInt = 1;

            // Error: inaccessible outside T1:
            //    T1.M2.myInternalInt = 2;

            // Error: inaccessible outside M2:
            //    T1.M2.myPrivateInt = 3;
        }
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.5.1 声明的可访问性

  • 3.5.2 可访问域

  • 3.5.4 可访问性约束

  • 10.3.5 访问修饰符

  • 10.3.8.2 声明的可访问性

请参见

概念

C# 编程指南

参考

C# 关键字
访问修饰符（C# 参考）
可访问性级别（C# 参考）
可访问性级别的使用限制（C# 参考）
访问修饰符（C# 编程指南）
public（C# 参考）
private（C# 参考）
protected（C# 参考）
internal（C# 参考）

其他资源

C# 参考


可访问性级别的使用限制（C# 参考）

[image]

C# 语言参考
可访问性级别的使用限制（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

声明类型时，请确保查看该类型是否必须至少具有与另一个成员或类型相同的可访问性。例如，直接基类必须至少与派生类具有同样的可访问性。以下声明将导致编译器错误，因为基类 BaseClass 的可访问性小于 MyClass：

复制代码
class BaseClass {...}
public class MyClass: BaseClass {...} // Error

下表汇总了对使用声明的可访问性级别的限制。

上下文 | 备注
类 | 类类型的直接基类必须至少与类类型本身具有同样的可访问性。
接口 | 接口类型的显式基接口必须至少与接口类型本身具有同样的可访问性。
委托 | 委托类型的返回类型和参数类型必须至少与委托类型本身具有同样的可访问性。
常数 | 常数的类型必须至少与常数本身具有同样的可访问性。
字段 | 字段的类型必须至少与字段本身具有同样的可访问性。
方法 | 方法的返回类型和参数类型必须至少与方法本身具有同样的可访问性。
属性 | 属性的类型必须至少与属性本身具有同样的可访问性。
事件 | 事件的类型必须至少与事件本身具有同样的可访问性。
索引器 | 索引器的类型和参数类型必须至少与索引器本身具有同样的可访问性。
运算符 | 运算符的返回类型和参数类型必须至少与运算符本身具有同样的可访问性。
构造函数 | 构造函数的参数类型必须至少与构造函数本身具有同样的可访问性。

示例

下面的示例包含不同类型的错误声明。每个声明后的注释指示了预期的编译器错误。

复制代码
// Restrictions on Using Accessibility Levels
// CS0052 expected as well as CS0053, CS0056, and CS0057
// To make the program work, change access level of both class B
// and MyPrivateMethod() to public.

using System;

// A delegate:
delegate int MyDelegate();

class B
{
    // A private method:
    static int MyPrivateMethod()
    {
        return 0;
    }
}

public class A
{
    // Error: The type B is less accessible than the field A.myField.
    public B myField = new B();

    // Error: The type B is less accessible
    // than the constant A.myConst.
    public readonly B myConst = new B();

    public B MyMethod()
    {
        // Error: The type B is less accessible
        // than the method A.MyMethod.
        return new B();
    }

    // Error: The type B is less accessible than the property A.MyProp
    public B MyProp
    {
        set
        {
        }
    }

    MyDelegate d = new MyDelegate(B.MyPrivateMethod);
    // Even when B is declared public, you still get the error:
    // "The parameter B.MyPrivateMethod is not accessible due to
    // protection level."

    public static B operator +(A m1, B m2)
    {
        // Error: The type B is less accessible
        // than the operator A.operator +(A,B)
        return new B();
    }

    static void Main()
    {
        Console.Write("Compiled successfully");
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.5.1 声明的可访问性

  • 3.5.4 可访问性约束

  • 10.3.5 访问修饰符

  • 10.3.8.2 声明的可访问性

  • 10.3.8.5 访问包含类型的私有成员和受保护成员

请参见

概念

C# 编程指南

参考

C# 关键字
访问修饰符（C# 参考）
可访问域（C# 参考）
可访问性级别（C# 参考）
访问修饰符（C# 编程指南）
public（C# 参考）
private（C# 参考）
protected（C# 参考）
internal（C# 参考）

其他资源

C# 参考


internal（C# 参考）

[image]

C# 语言参考
internal（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

internal 关键字是类型和类型成员的访问修饰符。只有在同一程序集的文件中，内部类型或成员才是可访问的，如下例所示：

复制代码
public class BaseClass
{
    // Only accessible within the same assembly
    internal static int x = 0;
}

有关 internal 与其他访问修饰符的比较，请参见可访问性级别（C# 参考）和访问修饰符（C# 编程指南）。

有关程序集的更多信息，请参见 程序集和全局程序集缓存（C# 编程指南）。

内部访问通常用于基于组件的开发，因为它使一组组件能够以私有方式进行合作，而不必向应用程序代码的其余部分公开。例如，用于生成图形用户界面的框架可以提供 Control 和 Form 类，这两个类通过使用具有内部访问权限的成员进行合作。由于这些成员是内部的，它们不向正在使用框架的代码公开。

从定义具有内部访问能力的类型或成员的程序集外部引用该类型或成员是错误的。

说明：

尽管不能用 C# 重写 internalvirtual 方法，但可以用某些语言（如使用 Ilasm.exe 的文本 Microsoft 中间语言 (MSIL)）重写此方法。

示例

该示例包含两个文件：Assembly1.cs 和 Assembly2.cs。第一个文件包含内部基类 BaseClass。在第二个文件中，实例化 BaseClass 的尝试将产生错误。

复制代码
// Assembly1.cs
// Compile with: /target:library
internal class BaseClass
{
   public static int intM = 0;
}
复制代码
// Assembly1_a.cs
// Compile with: /reference:Assembly1.dll
class TestAccess
{
   static void Main()
   {
      BaseClass myBase = new BaseClass();   // CS0122
   }
}

在此示例中，使用与示例 1 中所用的文件相同的文件，并将 BaseClass 的可访问性级别更改为 public。还将成员 IntM 的可访问性级别更改为 internal。在此例中，您可以实例化类，但不能访问内部成员。

复制代码
// Assembly2.cs
// Compile with: /target:library
public class BaseClass
{
   internal static int intM = 0;
}
复制代码
// Assembly2_a.cs
// Compile with: /reference:Assembly1.dll
public class TestAccess
{
   static void Main()
   {
      BaseClass myBase = new BaseClass();   // Ok.
      BaseClass.intM = 444;    // CS0117
   }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.5.1 声明的可访问性

  • 3.5.4 可访问性约束

  • 10.3.5 访问修饰符

  • 10.3.8.2 声明的可访问性

请参见

概念

C# 编程指南

参考

C# 关键字
访问修饰符（C# 参考）
可访问性级别（C# 参考）
修饰符（C# 参考）
public（C# 参考）
private（C# 参考）
protected（C# 参考）

其他资源

C# 参考


private（C# 参考）

[image]

C# 语言参考
private（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

private 关键字是一个成员访问修饰符。私有访问是允许的最低访问级别。私有成员只有在声明它们的类和结构体中才是可访问的，如下例所示：

复制代码
class Employee
{
    private int i;
    double d;   // private access by default
}

同一体中的嵌套类型也可以访问那些私有成员。

在定义私有成员的类或结构外引用它会导致编译时错误。

有关 private 与其他访问修饰符的比较，请参见可访问性级别（C# 参考）和访问修饰符（C# 编程指南）。

示例

在此示例中，Employee 类包含两个私有数据成员 name 和 salary。作为私有成员，它们只能通过成员方法来访问。添加名为 GetName 和 Salary 的公共方法，以便可以对私有成员进行受控的访问。通过公共方法访问 name 成员，而通过公共只读属性访问salary 成员。（有关更多信息，请参见 属性（C# 编程指南）。）

C# 复制代码
class Employee2
{
    private string name = "FirstName, LastName";
    private double salary = 100.0;

    public string GetName()
    {
        return name;
    }

    public double Salary
    {
        get { return salary; }
    }
}

class PrivateTest
{
    static void Main()
    {
        Employee2 e = new Employee2();

        // The data members are inaccessible (private), so
        // they can't be accessed like this:
        //    string n = e.name;
        //    double s = e.salary;

        // 'name' is indirectly accessed via method:
        string n = e.GetName();

        // 'salary' is indirectly accessed via property
        double s = e.Salary;
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.5.1 声明的可访问性

  • 3.5.4 可访问性约束

  • 10.3.5 访问修饰符

  • 10.3.8.2 声明的可访问性

  • 10.3.8.5 访问包含类型的私有成员和受保护成员

请参见

概念

C# 编程指南

参考

C# 关键字
访问修饰符（C# 参考）
可访问性级别（C# 参考）
修饰符（C# 参考）
public（C# 参考）
protected（C# 参考）
internal（C# 参考）

其他资源

C# 参考


protected（C# 参考）

[image]

C# 语言参考
protected（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

protected 关键字是一个成员访问修饰符。受保护成员在其所在的类中可由派生类实例访问。有关 protected 与其他访问修饰符的比较，请参见可访问性级别。

示例

只有在通过派生类类型发生访问时，基类的受保护成员在派生类中才是可访问的。例如，请看以下代码段：

C# 复制代码
class A
{
    protected int x = 123;
}

class B : A
{
    static void Main()
    {
        A a = new A();
        B b = new B();

        // Error CS1540, because x can only be accessed by
        // classes derived from A.
        // a.x = 10;

        // OK, because this class derives from A.
        b.x = 10;
    }
}

语句 a.x = 10 生成错误，因为它是在静态方法 Main 中生成的，而不是类 B 的实例。

结构成员无法受保护，因为无法继承结构。

此示例中，DerivedPoint 类派生自 Point。因此，可以从派生类直接访问基类的受保护成员。

C# 复制代码
class Point
{
    protected int x;
    protected int y;
}

class DerivedPoint: Point
{
    static void Main()
    {
        DerivedPoint dpoint = new DerivedPoint();

        // Direct access to protected members:
        dpoint.x = 10;
        dpoint.y = 15;
        Console.WriteLine("x = {0}, y = {1}", dpoint.x, dpoint.y);
    }
}
// Output: x = 10, y = 15

如果将 x 和 y 的访问级别更改为 private，编译器将发出错误信息：

'Point.y' is inaccessible due to its protection level.

'Point.x' is inaccessible due to its protection level.

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.5.1 声明的可访问性

  • 3.5.3 对实例成员的受保护访问

  • 3.5.4 可访问性约束

  • 10.3.5 访问修饰符

请参见

概念

C# 编程指南

参考

C# 关键字
访问修饰符（C# 参考）
可访问性级别（C# 参考）
修饰符（C# 参考）
public（C# 参考）
private（C# 参考）
internal（C# 参考）

其他资源

C# 参考


public（C# 参考）

[image]

C# 语言参考
public（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

public 关键字是类型和类型成员的访问修饰符。公共访问是允许的最高访问级别。对访问公共成员没有限制，如下例所示：

复制代码
class SampleClass
{
    public int x; // No access restrictions.
}

有关更多信息，请参见 访问修饰符（C# 编程指南） 和 可访问性级别（C# 参考）。

示例

在下面的示例中，声明了两个类：Point 和 MainClass。直接从 MainClass 访问 Point 的公共成员 x 和 y。

C# 复制代码
class PointTest
{
    public int x;
    public int y;
}

class MainClass4
{
    static void Main()
    {
        PointTest p = new PointTest();
        // Direct access to public members:
        p.x = 10;
        p.y = 15;
        Console.WriteLine("x = {0}, y = {1}", p.x, p.y);
    }
}
// Output: x = 10, y = 15

如果将 public 访问级别更改为 private 或 protected，您将收到错误信息：

不可访问“Point.y”，因为它受保护级别限制。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.5.1 声明的可访问性

  • 3.5.4 可访问性约束

  • 10.3.5 访问修饰符

  • 10.3.8.2 声明的可访问性

请参见

概念

C# 编程指南

参考

访问修饰符（C# 编程指南）
C# 关键字
访问修饰符（C# 参考）
可访问性级别（C# 参考）
修饰符（C# 参考）
private（C# 参考）
protected（C# 参考）
internal（C# 参考）

其他资源

C# 参考


abstract（C# 参考）

[image]

C# 语言参考
abstract（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

abstract 修饰符可以和类、方法、属性、索引器及事件一起使用。在类声明中使用 abstract 修饰符以指示某个类只能是其他类的基类。标记为抽象或包含在抽象类中的成员必须通过从抽象类派生的类来实现。

示例

在此例中，类 Square 必须提供 Area 的实现，因为它派生自 ShapesClass：

C# 复制代码
abstract class ShapesClass
{
    abstract public int Area();
}
class Square : ShapesClass
{
    int side = 0;

    public Square(int n)
    {
        side = n;
    }
    // Area method is required to avoid
    // a compile-time error.
    public override int Area()
    {
        return side * side;
    }

    static void Main()
    {
        Square sq = new Square(12);
        Console.WriteLine("Area of the square = {0}", sq.Area());
    }

    interface I
    {
        void M();
    }
    abstract class C : I
    {
        public abstract void M();
    }

}
// Output: Area of the square = 144

抽象类具有以下特性：

  • 抽象类不能实例化。

  • 抽象类可以包含抽象方法和抽象访问器。

  • 不能用 sealed（C# 参考） 修饰符修改抽象类，因为 sealed 会使得抽象类无法被继承。

  • 从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实实现。

在方法或属性声明中使用 abstract 修饰符以指示方法或属性不包含实现。

抽象方法具有以下特性：

  • 抽象方法是隐式的虚方法。

  • 只允许在抽象类中使用抽象方法声明。

  • 因为抽象方法声明不提供实际的实现，所以没有方法体；方法声明只是以一个分号结束，并且在签名后没有大括号 ({ })。例如：

    复制代码
    public abstract void MyMethod();

  • 实现由一个重写方法override（C# 参考）提供，此重写方法是非抽象类的一个成员。

  • 在抽象方法声明中使用 static 或 virtual 修饰符是错误的。

除了在声明和调用语法上不同外，抽象属性的行为与抽象方法一样。

  • 在静态属性上使用 abstract 修饰符是错误的。

  • 在派生类中，通过包括使用 override 修饰符的属性声明，可以重写抽象的继承属性。

有关抽象类的更多信息，请参见抽象类、密封类及类成员（C# 编程指南）。

抽象类必须为所有接口成员提供实现。

实现接口的抽象类可以将接口方法映射到抽象方法上。例如：

C# 复制代码
interface I
{
    void M();
}
abstract class C : I
{
    public abstract void M();
}

在本例中，DerivedClass 类是从抽象类 BaseClass 派生的。抽象类包含一个抽象方法 AbstractMethod 和两个抽象属性 X 和 Y。

C# 复制代码
abstract class BaseClass   // Abstract class
{
    protected int _x = 100;
    protected int _y = 150;
    public abstract void AbstractMethod();   // Abstract method
    public abstract int X    { get; }
    public abstract int Y    { get; }
}

class DerivedClass : BaseClass
{
    public override void AbstractMethod()
    {
        _x++;
        _y++;
    }

    public override int X   // overriding property
    {
        get
        {
            return _x + 10;
        }
    }

    public override int Y   // overriding property
    {
        get
        {
            return _y + 10;
        }
    }

    static void Main()
    {
        DerivedClass o = new DerivedClass();
        o.AbstractMethod();
        Console.WriteLine("x = {0}, y = {1}", o.X, o.Y);
    }
}
// Output: x = 111, y = 161

在上面的示例中，如果试图通过使用下面的语句将抽象类实例化：

复制代码
BaseClass bc = new BaseClass();   // Error

将出现错误，指出编译器无法创建抽象类“BaseClass”的实例。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.5.4 虚方法、重写方法和抽象方法

  • 10.1.1.1 抽象类

请参见

概念

C# 编程指南

参考

修饰符（C# 参考）
virtual（C# 参考）
override（C# 参考）
C# 关键字

其他资源

C# 参考


const（C# 参考）

[image]

C# 语言参考
const（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

const 关键字用于修改字段或局部变量的声明。它指定字段或局部变量的值是常数，不能被修改。例如：

复制代码
const int x = 0;
public const double gravitationalConstant = 6.673e-11;
private const string productName = "Visual C#";

备注

常数声明的类型指定声明引入的成员类型。常数表达式必须产生具有目标类型或者可隐式转换为目标类型的类型的值。

常数表达式是在编译时可被完全计算的表达式。因此，对于引用类型的常数，可能的值只能是 string 和 null。

常数声明可以声明多个常数，例如：

复制代码
public const double x = 1.0, y = 2.0, z = 3.0;

不允许在常数声明中使用 static 修饰符。

常数可以参与常数表达式，如下所示：

复制代码
public const int c1 = 5;
public const int c2 = c1 + 100;
说明：

readonly 关键字与 const 关键字不同。const 字段只能在该字段的声明中初始化。readonly 字段可以在声明或构造函数中初始化。因此，根据所使用的构造函数，readonly 字段可能具有不同的值。另外，虽然 const 字段是编译时常量，但 readonly 字段可用于运行时常量，如此行所示：public static readonly uint l1 = (uint)DateTime.Now.Ticks;

示例

C# 复制代码
public class ConstTest
{
    class SampleClass
    {
        public int x;
        public int y;
        public const int c1 = 5;
        public const int c2 = c1 + 5;

        public SampleClass(int p1, int p2)
        {
            x = p1;
            y = p2;
        }
    }

    static void Main()
    {
        SampleClass mC = new SampleClass(11, 22);
        Console.WriteLine("x = {0}, y = {1}", mC.x, mC.y);
        Console.WriteLine("c1 = {0}, c2 = {1}",
                          SampleClass.c1, SampleClass.c2 );
    }
}
/* Output
    x = 11, y = 22
    c1 = 5, c2 = 10
 */

此示例说明如何将常数用作局部变量。

C# 复制代码
public class SealedTest
{
    static void Main()
    {
        const int c = 707;
        Console.WriteLine("My local constant = {0}", c);
    }
}
// Output: My local constant = 707

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 6.1.6 隐式常数表达式转换

  • 8.5.2 局部常数声明

请参见

概念

C# 编程指南

参考

C# 关键字
修饰符（C# 参考）
readonly（C# 参考）

其他资源

C# 参考


event（C# 参考）

[image]

C# 语言参考
event（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

event 关键字用于在发行者类中声明事件。

示例

下面的示例演示如何声明和引发将 EventHandler 用作基础委托类型的事件。有关演示如何使用泛型 EventHandler<(Of <(TEventArgs>)>) 委托类型、如何订阅事件以及如何创建事件处理程序方法的完整代码示例，请参见如何：发布符合 .NET Framework 准则的事件（C# 编程指南）。

C# 复制代码
public class SampleEventArgs
{
    public SampleEventArgs(string s) { Text = s; }
    public String Text {get; private set;} // readonly
}
public class Publisher
{
    // Declare the delegate (if using non-generic pattern).
    public delegate void SampleEventHandler(object sender, SampleEventArgs e);

    // Declare the event.
    public event SampleEventHandler SampleEvent;

    // Wrap the event in a protected virtual method
    // to enable derived classes to raise the event.
    protected virtual void RaiseSampleEvent()
    {
        // Raise the event by using the () operator.
        SampleEvent(this, new SampleEventArgs("Hello"));
    }
}

事件是特殊类型的多路广播委托，仅可从声明它们的类或结构（发行者类）中调用。如果其他类或结构订阅了该事件，则当发行者类引发该事件时，会调用其事件处理程序方法。有关更多信息和代码示例，请参见事件（C# 编程指南） 和委托（C# 编程指南）。

事件可标记为 public、private、protected、internal 或 protectedinternal。这些访问修饰符定义类的用户访问事件的方式。有关更多信息，请参见访问修饰符（C# 编程指南）。

关键字和事件

下面的关键字可应用于事件。

关键字 | 说明 | 更多信息
static | 即使类没有实例，调用方也能在任何时候使用该事件。 | 静态类和静态类成员（C# 编程指南）
virtual | 允许派生类通过使用 override 关键字来重写事件行为。 | 继承（C# 编程指南）
sealed | 指定对于派生类它不再属虚拟性质。 |
abstract | 编译器不会生成 add 和 remove 事件访问器块，因此派生类必须提供自己的实现。 |

通过使用 static 关键字，可以将事件声明为静态事件。即使类没有任何实例，调用方也能在任何时候使用静态事件。有关更多信息，请参见 静态类和静态类成员（C# 编程指南）。

通过使用 virtual 关键字，可以将事件标记为虚拟事件。这样，派生类就可以通过使用 override 关键字来重写事件行为。有关更多信息，请参见 继承（C# 编程指南）。重写虚事件的事件也可以为 sealed，以表示其对于派生类不再是虚事件。最后，可以将事件声明为 abstract，这意味着编译器不会生成 add 和 remove 事件访问器块。因此派生类必须提供其自己的实现。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.6.4 事件

  • 7.16.3 事件分配

  • 10.8 事件

  • 13.2.3 接口事件

请参见

任务

如何：合并委托（多路广播委托）（C# 编程指南）

概念

C# 编程指南

参考

C# 关键字
修饰符（C# 参考）

其他资源

C# 参考


extern（C# 参考）

[image]

C# 语言参考
extern（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

extern 修饰符用于声明在外部实现的方法。extern 修饰符的常见用法是在使用 Interop 服务调入非托管代码时与 DllImport 属性一起使用。在这种情况下，还必须将方法声明为 static，如下示例所示：

复制代码
[DllImport("avifil32.dll")]
private static extern void AVIFileInit();
说明：

extern 关键字还可以定义外部程序集别名，使得可以从单个程序集中引用同一组件的不同版本。有关更多信息，请参见 外部别名（C# 参考）。

将 abstract（C# 参考）和 extern 修饰符一起使用来修改同一成员是错误的。使用 extern 修饰符意味着方法在 C# 代码的外部实现，而使用 abstract 修饰符意味着在类中未提供方法实现。

说明：

extern 关键字在使用上比在 C++ 中有更多限制。若要与 C++ 关键字进行比较，请参见 C++ Language Reference 中的 Using extern to Specify Linkage。

示例

在该示例中，程序接收来自用户的字符串并将该字符串显示在消息框中。程序使用从 User32.dll 库导入的 MessageBox 方法。

C# 复制代码
//using System.Runtime.InteropServices;
class ExternTest
{
    [DllImport("User32.dll", CharSet=CharSet.Unicode)]
    public static extern int MessageBox(int h, string m, string c, int type);

    static int Main()
    {
        string myString;
        Console.Write("Enter your message: ");
        myString = Console.ReadLine();
        return MessageBox(0, myString, "My Message Box", 0);
    }

}

此示例使用 C 程序创建一个 DLL，在下一示例中将从 C# 程序调用该 DLL。

复制代码
// cmdll.c
// Compile with: /LD
int __declspec(dllexport) SampleMethod(int i)
{
   return i*10;
}

该示例使用两个文件 CM.cs 和 Cmdll.c 来说明 extern。C 文件是示例 2 中创建的外部 DLL，它从 C# 程序内调用。

复制代码
// cm.cs
using System;
using System.Runtime.InteropServices;
public class MainClass
{
   [DllImport("Cmdll.dll")]
   public static extern int SampleMethod(int x);

   static void Main()
   {
      Console.WriteLine("SampleMethod() returns {0}.", SampleMethod(5));
   }
}
复制代码
SampleMethod() returns 50.

备注

生成项目：

  • 使用 Visual C++ 命令行将 Cmdll.c 编译为 DLL：

    cl /LD Cmdll.c

  • 使用命令行编译 CM.cs：

    csc CM.cs

这将创建可执行文件 CM.exe。运行此程序时，SampleMethod 将值 5 传递到 DLL 文件，该文件将此值乘以 10 返回。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.6.7 外部方法

请参见

概念

C# 编程指南

参考

C# 关键字
修饰符（C# 参考）
System.Runtime.InteropServices..::.DllImportAttribute

其他资源

C# 参考


override（C# 参考）

[image]

C# 语言参考
override（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

要扩展或修改继承的方法、属性、索引器或事件的抽象实现或虚实现，必须使用 override 修饰符。

示例

在此示例中，Square 类必须提供 Area 的重写实现，因为 Area 继承自抽象的 ShapesClass：

C# 复制代码
abstract class ShapesClass
{
    abstract public int Area();
}
class Square : ShapesClass
{
    int side = 0;

    public Square(int n)
    {
        side = n;
    }
    // Area method is required to avoid
    // a compile-time error.
    public override int Area()
    {
        return side * side;
    }

    static void Main()
    {
        Square sq = new Square(12);
        Console.WriteLine("Area of the square = {0}", sq.Area());
    }

    interface I
    {
        void M();
    }
    abstract class C : I
    {
        public abstract void M();
    }

}
// Output: Area of the square = 144

override 方法提供从基类继承的成员的新实现。由 override 声明重写的方法称为重写基方法。重写的基方法必须与 override 方法具有相同的签名。有关继承的信息，请参见继承（C# 编程指南）。

不能重写非虚方法或静态方法。重写的基方法必须是 virtual、abstract 或 override 的。

override 声明不能更改 virtual 方法的可访问性。override 方法和 virtual 方法必须具有相同的访问级别修饰符。

不能使用 new、static、virtual 或 abstract 修饰符来修改 override 方法。

重写属性声明必须指定与继承属性完全相同的访问修饰符、类型和名称，并且被重写的属性必须是 virtual、abstract 或 override 的。

有关如何使用 override 关键字的更多信息，请参见使用 Override 和 New 关键字进行版本控制（C# 编程指南）和了解何时使用 Override 和 New 关键字。

此示例定义了一个名为 Employee 的基类和一个名为 SalesEmployee 的派生类。SalesEmployee 类包括一个额外的属性 salesbonus，并重写方法 CalculatePay 以便将该属性考虑在内。

C# 复制代码
class TestOverride
{
    public class Employee
    {
        public string name;

        // Basepay is defined as protected, so that it may be
        // accessed only by this class and derrived classes.
        protected decimal basepay;

        // Constructor to set the name and basepay values.
        public Employee(string name, decimal basepay)
        {
            this.name = name;
            this.basepay = basepay;
        }

        // Declared virtual so it can be overridden.
        public virtual decimal CalculatePay()
        {
            return basepay;
        }
    }

    // Derive a new class from Employee.
    public class SalesEmployee : Employee
    {
        // New field that will affect the base pay.
        private decimal salesbonus;

        // The constructor calls the base-class version, and
        // initializes the salesbonus field.
        public SalesEmployee(string name, decimal basepay,
                  decimal salesbonus) : base(name, basepay)
        {
            this.salesbonus = salesbonus;
        }

        // Override the CalculatePay method
        // to take bonus into account.
        public override decimal CalculatePay()
        {
            return basepay + salesbonus;
        }
    }

    static void Main()
    {
        // Create some new employees.
        SalesEmployee employee1 = new SalesEmployee("Alice",
                      1000, 500);
        Employee employee2 = new Employee("Bob", 1200);

        Console.WriteLine("Employee4 " + employee1.name +
                  " earned: " + employee1.CalculatePay());
        Console.WriteLine("Employee4 " + employee2.name +
                  " earned: " + employee2.CalculatePay());
    }
}
/*
    Output:
    Employee4 Alice earned: 1500
    Employee4 Bob earned: 1200
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.6.4 虚方法、重写方法和抽象方法

  • 10.6.4 重写方法

请参见

概念

C# 编程指南

参考

继承（C# 编程指南）
C# 关键字
修饰符（C# 参考）
abstract（C# 参考）
virtual（C# 参考）
new（C# 参考）
多态性（C# 编程指南）

其他资源

C# 参考


readonly（C# 参考）

[image]

C# 语言参考
readonly（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

readonly 关键字是可以在字段上使用的修饰符。当字段声明包括 readonly 修饰符时，该声明引入的字段赋值只能作为声明的一部分出现，或者出现在同一类的构造函数中。

示例

在此示例中，字段 year 的值无法在 ChangeYear 方法中更改，即使在类构造函数中给它赋了值。

C# 复制代码
class Age
{
    readonly int _year;
    Age(int year)
    {
        _year = year;
    }
    void ChangeYear()
    {
        //_year = 1967; // Compile error if uncommented.
    }
}

只能在下列上下文中对 readonly 字段进行赋值：

  • 当在声明中初始化变量时，例如：

    复制代码
    public readonly int y = 5;

  • 对于实例字段，在包含字段声明的类的实例构造函数中；或者，对于静态字段，在包含字段声明的类的静态构造函数中。也只有在这些上下文中，将 readonly 字段作为 out 或 ref 参数传递才有效。

说明：

readonly 关键字与 const 关键字不同。const 字段只能在该字段的声明中初始化。readonly 字段可以在声明或构造函数中初始化。因此，根据所使用的构造函数，readonly 字段可能具有不同的值。另外，const 字段为编译时常数，而 readonly 字段可用于运行时常数，如下例所示：

复制代码
public static readonly uint timeStamp = (uint)DateTime.Now.Ticks;
C# 复制代码
public class ReadOnlyTest
{
   class SampleClass
   {
      public int x;
      // Initialize a readonly field
      public readonly int y = 25;
      public readonly int z;

      public SampleClass()
      {
         // Initialize a readonly instance field
         z = 24;
      }

      public SampleClass(int p1, int p2, int p3)
      {
         x = p1;
         y = p2;
         z = p3;
      }
   }

   static void Main()
   {
      SampleClass p1 = new SampleClass(11, 21, 32);   // OK
      Console.WriteLine("p1: x={0}, y={1}, z={2}", p1.x, p1.y, p1.z);
      SampleClass p2 = new SampleClass();
      p2.x = 55;   // OK
      Console.WriteLine("p2: x={0}, y={1}, z={2}", p2.x, p2.y, p2.z);
   }
}
/*
 Output:
    p1: x=11, y=21, z=32
    p2: x=55, y=25, z=24
*/

在前面的示例中，如果使用这样的语句：

p2.y = 66; // Error

将收到编译器错误信息：

The left-hand side of an assignment must be an l-value

这与试图将值赋给常数时收到的错误相同。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.4.2 只读字段

请参见

概念

C# 编程指南

参考

C# 关键字
修饰符（C# 参考）
const（C# 参考）
字段（C# 编程指南）

其他资源

C# 参考


sealed（C# 参考）

[image]

C# 语言参考
sealed（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

当对一个类应用 sealed 修饰符时，此修饰符会阻止其他类从该类继承。在下面的示例中，类 B 从类 A 继承，但是任何类都不能从类 B 继承。

复制代码
    class A {}
    sealed class B : A {}

还可以在重写基类中的虚方法或虚属性的方法或属性上使用 sealed 修饰符。这将使您能够允许类从您的类继承，并防止它们重写特定的虚方法或虚属性。

示例

在下面的示例中，C 从 B 继承，但 C 无法重写在 A 中声明并在 B 中密封的虚函数 F。

C# 复制代码
class X
{
    protected virtual void F() { Console.WriteLine("X.F"); }
    protected virtual void F2() { Console.WriteLine("X.F2"); }
}
class Y : X
{
    sealed protected override void F() { Console.WriteLine("Y.F"); }
    protected override void F2() { Console.WriteLine("X.F3"); }
}
class Z : Y
{
    // Attempting to override F causes compiler error CS0239.
    // protected override void F() { Console.WriteLine("C.F"); }

    // Overriding F2 is allowed.
    protected override void F2() { Console.WriteLine("Z.F2"); }
}

当在类中定义新的方法或属性时，通过不将这些方法或属性声明为 virtual，可防止派生类重写这些方法或属性。

将 abstract 修饰符用于密封类是错误的做法，因为抽象类必须由提供抽象方法或属性的实现的类继承。

当应用于方法或属性时，sealed 修饰符必须始终与 override 一起使用。

由于结构是隐式密封的，因此它们不能被继承。

有关更多信息，请参见 继承（C# 编程指南）。

C# 复制代码
sealed class SealedClass
{
    public int x;
    public int y;
}

class SealedTest2
{
    static void Main()
    {
        SealedClass sc = new SealedClass();
        sc.x = 110;
        sc.y = 150;
        Console.WriteLine("x = {0}, y = {1}", sc.x, sc.y);
    }
}
// Output: x = 110, y = 150

在上一个示例中，您可能试图使用下面的语句从密封类继承：

class MyDerivedC: SealedClass {} // Error

将产生一条错误消息：

'MyDerivedC' cannot inherit from sealed class 'SealedClass'.

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.1.1.2 密封类

  • 10.6.5 密封方法

备注

若要确定是否密封类、方法或属性，通常应考虑以下两点：

  • 派生类利用自定义类的功能所获得的可能好处。

  • 派生类在修改类之后导致其无法正常工作或按预期工作的可能性。

请参见

概念

C# 编程指南

参考

C# 关键字
静态类和静态类成员（C# 编程指南）
抽象类、密封类及类成员（C# 编程指南）
访问修饰符（C# 编程指南）
修饰符（C# 参考）
override（C# 参考）
virtual（C# 参考）

其他资源

C# 参考


static（C# 参考）

[image]

C# 语言参考
static（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员。static 修饰符可用于类、字段、方法、属性、运算符、事件和构造函数，但不能用于索引器、析构函数或类以外的类型。有关更多信息，请参见 静态类和静态类成员（C# 编程指南）。

示例

下面的类声明为 static，并且只包含 static 方法：

C# 复制代码
static class CompanyEmployee
{
    public static void DoSomething() { /*...*/ }
    public static void DoSomethingElse() { /*...*/  }
}

常数或者类型声明隐式地是静态成员。

不能通过实例引用静态成员。然而，可以通过类型名称引用它。例如，请考虑以下类：

C# 复制代码
public class MyBaseC
{
    public struct MyStruct
    {
        public static int x = 100;
    }
}

若要引用静态成员 x，请使用完全限定名（除非可从相同范围访问）：

复制代码
MyBaseC.MyStruct.x

尽管类的实例包含该类所有实例字段的单独副本，但每个静态字段只有一个副本。

不可以使用 this 来引用静态方法或属性访问器。

如果对类应用 static 关键字，则该类的所有成员都必须是静态的。

类和静态类可以有静态构造函数。静态构造函数在程序开始和类实例化之间的某个时刻调用。

说明：

static 关键字在使用上比在 C++ 中有更多限制。若要与 C++ 关键字比较，请参见 Static (C++)。

为了说明静态成员，请看一个表示公司雇员的类。假设该类包含一种对雇员计数的方法和一个存储雇员数的字段。该方法和字段都不属于任何实例雇员，而是属于公司类。因此，应该将它们声明为此类的静态成员。

此示例读取新雇员的姓名和 ID，将雇员计数器加一，并显示新雇员的信息和新的雇员数。为简单起见，该程序从键盘读取当前的雇员数。在实际的应用中，应从文件读取此信息。

C# 复制代码
    public class Employee4
{
    public string id;
    public string name;

    public Employee4()
    {
    }

    public Employee4(string name, string id)
    {
        this.name = name;
        this.id = id;
    }

    public static int employeeCounter;

    public static int AddEmployee()
    {
        return ++employeeCounter;
    }
}

class MainClass : Employee4
{
    static void Main()
    {
        Console.Write("Enter the employee's name: ");
        string name = Console.ReadLine();
        Console.Write("Enter the employee's ID: ");
        string id = Console.ReadLine();

        // Create and configure the employee object:
        Employee4 e = new Employee4(name, id);
        Console.Write("Enter the current number of employees: ");
        string n = Console.ReadLine();
        Employee4.employeeCounter = Int32.Parse(n);
        Employee4.AddEmployee();

        // Display the new information:
        Console.WriteLine("Name: {0}", e.name);
        Console.WriteLine("ID:   {0}", e.id);
        Console.WriteLine("New Number of Employees: {0}",
                      Employee4.employeeCounter);
    }
}
    /*
    Input:
    Matthias Berndt
    AF643G
    15
     *
    Sample Output:
    Enter the employee's name: Matthias Berndt
    Enter the employee's ID: AF643G
    Enter the current number of employees: 15
    Name: Matthias Berndt
    ID:   AF643G
    New Number of Employees: 16
    */

此示例说明：虽然可以用另一个尚未声明的静态字段实例化一个静态字段，但直到向后者显式赋值后，才能确定结果。

C# 复制代码
class Test
{
   static int x = y;
   static int y = 5;

   static void Main()
   {
      Console.WriteLine(Test.x);
      Console.WriteLine(Test.y);

      Test.x = 99;
      Console.WriteLine(Test.x);
   }
}
/*
Output:
    0
    5
    99
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.6.3 静态方法和实例方法

  • 5.1.1 静态变量

  • 10.3.7 静态成员和实例成员

  • 10.5.1 静态字段和实例字段

  • 10.5.5.1 静态字段初始化

  • 10.6.2 静态方法和实例方法

  • 10.7.1 静态属性和实例属性

  • 10.8.3 静态事件和实例事件

  • 10.12 静态构造函数

请参见

概念

C# 编程指南

参考

C# 关键字
修饰符（C# 参考）
静态类和静态类成员（C# 编程指南）

其他资源

C# 参考


unsafe（C# 参考）

[image]

C# 语言参考
unsafe（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

unsafe 关键字表示不安全上下文，该上下文是任何涉及指针的操作所必需的。有关更多信息，请参见 不安全代码和指针（C# 编程指南）。

可以在类型或成员的声明中使用 unsafe 修饰符。因此，类型或成员的整个正文范围均被视为不安全上下文。例如，以下是用 unsafe 修饰符声明的方法：

复制代码
unsafe static void FastCopy(byte[] src, byte[] dst, int count)
{
    // Unsafe context: can use pointers here.
}

不安全上下文的范围从参数列表扩展到方法的结尾，因此指针在以下参数列表中也可以使用：

复制代码
unsafe static void FastCopy ( byte* ps, byte* pd, int count ) {...}

还可以使用不安全块从而能够使用该块内的不安全代码。例如：

复制代码
unsafe
{
    // Unsafe context: can use pointers here.
}

若要编译不安全代码，必须指定 /unsafe 编译器选项。无法通过公共语言运行时验证不安全代码。

示例

C# 复制代码
// compile with: /unsafe

class UnsafeTest
{
   // Unsafe method: takes pointer to int:
   unsafe static void SquarePtrParam(int* p)
   {
      *p *= *p;
   }

   unsafe static void Main()
   {
      int i = 5;
      // Unsafe method: uses address-of operator (&):
      SquarePtrParam(&i);
      Console.WriteLine(i);
   }
}
// Output: 25

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 18 不安全代码

请参见

概念

C# 编程指南

参考

C# 关键字
fixed 语句（C# 参考）
不安全代码和指针（C# 编程指南）
固定大小的缓冲区（C# 编程指南）

其他资源

C# 参考


virtual（C# 参考）

[image]

C# 语言参考
virtual（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

virtual 关键字用于修饰方法、属性、索引器或事件声明，并使它们可以在派生类中被重写。例如，此方法可被任何继承它的类重写。

复制代码
public virtual double Area()
{
    return x * y;
}

虚拟成员的实现可由派生类中的重写成员更改。有关如何使用 virtual 关键字的更多信息，请参见使用 Override 和 New 关键字进行版本控制（C# 编程指南）和了解何时使用 Override 和 New 关键字（C# 编程指南）。

备注

调用虚方法时，将为重写成员检查该对象的运行时类型。将调用大部分派生类中的该重写成员，如果没有派生类重写该成员，则它可能是原始成员。

默认情况下，方法是非虚拟的。不能重写非虚方法。

virtual 修饰符不能与 static、abstract, private 或 override 修饰符一起使用。

除了声明和调用语法不同外，虚拟属性的行为与抽象方法一样。

  • 在静态属性上使用 virtual 修饰符是错误的。

  • 通过包括使用 override 修饰符的属性声明，可在派生类中重写虚拟继承属性。

示例

在该示例中，Dimensions 类包含 x、y 两个坐标和 Area() 虚方法。不同的形状类，如 Circle、Cylinder 和 Sphere 继承 Dimensions 类，并为每个图形计算表面积。每个派生类都有各自的 Area() 重写实现。根据与此方法关联的对象，通过调用适当的 Area() 实现，程序为每个图形计算并显示适当的面积。

在前面的示例中，注意继承的类 Circle、Sphere 和 Cylinder 都使用了初始化基类的构造函数，例如：

复制代码
public Cylinder(double r, double h): base(r, h) {}

这类似于 C++ 的初始化列表。

C# 复制代码
class TestClass
{
    public class Dimensions
    {
        public const double PI = Math.PI;
        protected double x, y;
        public Dimensions()
        {
        }
        public Dimensions(double x, double y)
        {
            this.x = x;
            this.y = y;
        }

        public virtual double Area()
        {
            return x * y;
        }
    }

    public class Circle : Dimensions
    {
        public Circle(double r) : base(r, 0)
        {
        }

        public override double Area()
        {
            return PI * x * x;
        }
    }

    class Sphere : Dimensions
    {
        public Sphere(double r) : base(r, 0)
        {
        }

        public override double Area()
        {
            return 4 * PI * x * x;
        }
    }

    class Cylinder : Dimensions
    {
        public Cylinder(double r, double h) : base(r, h)
        {
        }

        public override double Area()
        {
            return 2 * PI * x * x + 2 * PI * x * y;
        }
    }

    static void Main()
    {
        double r = 3.0, h = 5.0;
        Dimensions c = new Circle(r);
        Dimensions s = new Sphere(r);
        Dimensions l = new Cylinder(r, h);
        // Display results:
        Console.WriteLine("Area of Circle   = {0:F2}", c.Area());
        Console.WriteLine("Area of Sphere   = {0:F2}", s.Area());
        Console.WriteLine("Area of Cylinder = {0:F2}", l.Area());
        }
    }
    /*
        Output:
        Area of Circle   = 28.27
        Area of Sphere   = 113.10
        Area of Cylinder = 150.80
    */

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.6.4 虚方法、重写方法和抽象方法

  • 10.6.3 虚方法

  • 10.7.5 虚访问器、密封访问器、重写访问器和抽象访问器

请参见

概念

C# 编程指南

参考

修饰符（C# 参考）
C# 关键字
多态性（C# 编程指南）
abstract（C# 参考）
override（C# 参考）
new（C# 参考）

其他资源

C# 参考


volatile（C# 参考）

[image]

C# 语言参考
volatile（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

volatile 关键字指示一个字段可以由多个同时执行的线程修改。声明为 volatile 的字段不受编译器优化（假定由单个线程访问）的限制。这样可以确保该字段在任何时间呈现的都是最新的值。

volatile 修饰符通常用于由多个线程访问但不使用 lock 语句对访问进行序列化的字段。有关在多线程方案中使用 volatile 的示例，请参见如何：创建和终止线程（C# 编程指南）。

volatile 关键字可应用于以下类型的字段：

  • 引用类型。

  • 指针类型（在不安全的上下文中）。请注意，虽然指针本身可以是可变的，但是它指向的对象不能是可变的。换句话说，您无法声明“指向可变对象的指针”。

  • 整型，如 sbyte、byte、short、ushort、int、uint、char、float 和 bool。

  • 具有整数基类型的枚举类型。

  • 已知为引用类型的泛型类型参数。

  • IntPtr 和 UIntPtr。

可变关键字仅可应用于类或结构字段。不能将局部变量声明为 volatile。

示例

下面的示例说明如何将公共字段变量声明为 volatile。

C# 复制代码
class VolatileTest
{
    public volatile int i;

    public void Test(int _i)
    {
        i = _i;
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.10 执行顺序

  • 10.5.3 可变字段

请参见

概念

C# 编程指南

参考

C# 关键字
修饰符（C# 参考）

其他资源

C# 参考


语句关键字（C# 参考）

[image]

C# 语言参考
语句关键字（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

语句是程序指令。除了下表中引用的主题中介绍的以外，语句都是按照顺序执行的。下表列出了 C# 语句关键字。有关不用任何关键字表示的语句的更多信息，请参见语句（C# 编程指南）。

类别 | C# 关键字
选择语句 | if, else, switch, case
迭代语句 | do, for, foreach, in, while
跳转语句 | break, continue, default, goto, return，yield
异常处理语句 | throw, try-catch, try-finally, try-catch-finally
检查和未检查 | checked, unchecked
fixed 语句 | fixed
lock 语句 | lock

请参见

参考

语句（C# 编程指南）
C# 关键字

其他资源

C# 参考


选择语句（C# 参考）

[image]

C# 语言参考
选择语句（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

选择语句根据某个条件是否为 true 来将程序控制权移交给特定的流。

选择语句中使用下列关键字：

  • if

  • else

  • switch

  • case

  • default

请参见

概念

C# 编程指南

参考

C# 关键字
语句关键字（C# 参考）

其他资源

C# 参考


if-else（C# 参考）

[image]

C# 语言参考
if-else（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

if 语句根据 Boolean 表达式的值选择要执行的语句。下面的示例将 Boolean 标志 flagCheck 设置为 true，然后在 if 语句中检查该标志。输出为：The flag is set to true。

C# 复制代码
bool result = true;

if (result == true)
{
    Console.WriteLine("The variable is set to true.");
}
else
{
    Console.WriteLine("The variable is set to false.");
}

如果括号里的表达式计算为 true，则执行 Console.WriteLine("The boolean flag is set to true."); 语句。执行 if 语句之后，控制传递给下一个语句。在此例中不执行 else 语句。

如果想要执行的语句不止一个，可以通过使用 {} 将多个语句包含在块中，有条件地执行多个语句，如上例所示。

在测试条件时执行的语句可以是任何种类的，包括嵌套在原始 if 语句中的另一个 if 语句。在嵌套的 if 语句中，else 子句属于最后一个没有对应的 else 的 if。例如：

C# 复制代码
int x = 12;
int y = 18;

if (x > 10)
    if (y > 20)
        Console.Write("Statement_1");
    else
        Console.Write("Statement_2");

在此例中，如果条件 (y > 20) 计算为 false，将显示 Statement_2。但如果要使 Statement_2 与条件 Statement_2 关联，则使用大括号：

C# 复制代码
if (x > 10)
{
    if (y > 20)
        Console.Write("Statement_1");
}
else
    Console.Write("Statement_2");

在此例中，如果条件 (x > 10) 计算为 false，将显示 Statement_2

示例

在此例中，您从键盘输入一个字符，而程序检查输入字符是否为字母字符。如果输入的字符是字母，则程序检查是大写还是小写。在任何一种情况下，都会显示适当的消息。

C# 复制代码
class IfTest
{
    static void Main()
    {
        Console.Write("Enter a character: ");
        char c = (char)Console.Read();
        if (Char.IsLetter(c))
        {
            if (Char.IsLower(c))
            {
                Console.WriteLine("The character is lowercase.");
            }
            else
            {
                Console.WriteLine("The character is uppercase.");
            }
        }
        else
        {
            Console.WriteLine("Not an alphabetic character.");
        }
    }

    /*
    Input:
    2
    Sample Output
    Enter a character: 2
    The character is not an alphabetic character.
    Additional sample might look as follows:
    Run #2:
    Enter a character: A
    The character is uppercase.
    Run #3:
    Enter a character: h
    The character is lowercase.
     * */
}

还可以扩展 if 语句，使用下列 else-if 排列来处理多个条件：

C# 复制代码
if (Condition_1)
{
    // Statement_1;
}
else if (Condition_2)
{
    // Statement_2;
}
else if (Condition_3)
{
    // Statement_3;
}
else
{
    // Statement_n;
}

此示例检查输入字符是否是小写字符、大写字符或数字。否则，输入字符不是字母字符。程序利用 else-if 阶梯。

C# 复制代码
    public class IfTest3
{
    static void Main()
    {
        Console.Write("Enter a character: ");
        char c = (char)Console.Read();

        if (Char.IsUpper(c))
        {
            Console.WriteLine("Character is uppercase.");
        }
        else if (Char.IsLower(c))
        {
            Console.WriteLine("Character is lowercase.");
        }
        else if (Char.IsDigit(c))
        {
            Console.WriteLine("Character is a number.");
        }
        else
        {
            Console.WriteLine("Character is not alphanumeric.");
        }
    }
}
    /*
        Sample Input:  E

        Sample Output:
        Enter a character: E
        The character is uppercase.

        Additional sample runs might look as follows:
        Run #2
        Enter a character: e
        The character is lowercase.
        Run #3:
        Enter a character: 4
        The character is a number.
        Run #4:
        Enter a character: $
        The character is not alphanumeric.
    */

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.5 If 语句

  • 8.7.1 if 语句

请参见

概念

C# 编程指南

参考

C# 关键字
?: 运算符（C# 参考）
The if-else Statement
switch（C# 参考）

其他资源

C# 参考


switch（C# 参考）

[image]

C# 语言参考
switch（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

switch 语句是一个控制语句，它通过将控制传递给其体内的一个 case 语句来处理多个选择和枚举。例如：

示例

C# 复制代码
int caseSwitch = 1;
switch (caseSwitch)
{
    case 1:
        Console.WriteLine("Case 1");
        break;
    case 2:
        Console.WriteLine("Case 2");
        break;
    default:
        Console.WriteLine("Default case");
        break;
}

控制传递给与开关的值匹配的 case 语句。switch 语句可以包括任意数目的 case 实例，但是任何两个 case 语句都不能具有相同的值。语句体从选定的语句开始执行，直到 break 将控制传递到 case 体以外。在每一个 case 块（包括上一个块，不论它是 case 语句还是 default 语句）的后面，都必须有一个跳转语句（如 break）。但有一个例外，（与 C++ switch 语句不同）C# 不支持从一个 case 标签显式贯穿到另一个 case 标签。这个例外是当 case 语句中没有代码时。

如果没有任何 case 表达式与开关值匹配，则控制传递给跟在可选 default 标签后的语句。如果没有 default 标签，则控制传递到 switch 以外。

C# 复制代码
    class SwitchTest
{
    static void Main()
    {
        Console.WriteLine("Coffee sizes: 1=Small 2=Medium 3=Large");
        Console.Write("Please enter your selection: ");
        string s = Console.ReadLine();
        int n = int.Parse(s);
        int cost = 0;
        switch(n)
        {
        case 1:
            cost += 25;
            break;
        case 2:
            cost += 25;
            goto case 1;
        case 3:
            cost += 50;
            goto case 1;
        default:
            Console.WriteLine("Invalid selection. Please select 1, 2, or 3.");
            break;
        }
        if (cost != 0)
        {
            Console.WriteLine("Please insert {0} cents.", cost);
        }
        Console.WriteLine("Thank you for your business.");
    }
}
    /*
        Sample Input: 2

        Sample Output:
        Coffee sizes: 1=Small 2=Medium 3=Large
        Please enter your selection: 2
        Please insert 50 cents.
        Thank you for your business.
    */

  • 在前面的示例中，整型变量 n 用于 switch case。注意还可以直接使用字符串变量 s。在这种情况下，可以以下列方式使用 switch case：

复制代码
switch(s)
{
    case "1":
        // ...
    case "2":
        // ...
}

下面的示例显示了空 case 标签可以从一个 case 标签贯穿到另一个。

C# 复制代码
class SwitchTest2
{
    static void Main()
    {
        int n = 2;
        switch(n)
        {
            case 1:
            case 2:
            case 3:
                Console.WriteLine("It's 1, 2, or 3.");
                break;
        default:
            Console.WriteLine("Not sure what it is.");
            break;
        }
    }
}
//  Output: It's 1, 2, or 3.

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.6 switch 语句

  • 8.7.2 switch 语句

请参见

概念

C# 编程指南

参考

C# 关键字
The switch Statement
if-else（C# 参考）

其他资源

C# 参考


迭代语句（C# 参考）

[image]

C# 语言参考
迭代语句（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

通过使用迭代语句可以创建循环。迭代语句导致嵌入语句根据循环终止条件多次执行。除非遇到跳转语句，否则这些语句将按顺序执行。

迭代语句中使用下列关键字：

  • do

  • for

  • foreach

  • in

  • while

请参见

概念

C# 编程指南

参考

C# 关键字
语句关键字（C# 参考）

其他资源

C# 参考


do（C# 参考）

[image]

C# 语言参考
do（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

do 语句重复执行括在 {} 里的一个语句或语句块，直到指定的表达式计算为 false。

示例

在下面的示例中，只要变量 x 小于 5，do-while 循环语句就开始执行。

C# 复制代码
public class TestDoWhile
{
    public static void Main ()
    {
        int x = 0;
        do
        {
            Console.WriteLine(x);
            x++;
        } while (x < 5);
    }
}
/*
    Output:
    0
    1
    2
    3
    4
*/

与 while 语句不同的是，do-while 循环会在计算条件表达式之前执行一次。

在 do-while 块中的任何点，都可使用 break 语句跳出循环。使用 continue 语句可以直接进入 while 表达式计算语句；如果表达式计算结果为 true，则会继续从循环的第一个语句执行。如果表达式计算结果为 false，则会继续从 do-while 循环后的第一个语句执行。

do-while 循环还可以通过 goto、return 或 throw 语句退出。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.8 do 语句

  • 8.8.2 do 语句

请参见

概念

C# 编程指南

参考

C# 关键字
The do-while Statement (C++)
迭代语句（C# 参考）

其他资源

C# 参考


for（C# 参考）

[image]

C# 语言参考
for（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

for 循环重复执行一个语句或语句块，直到指定的表达式计算为 false 值。for 循环对于迭代数组和顺序处理很有用。

示例

在下面的示例中，int i 的值将写入控制台，并且 i 在每次通过循环时都加 1。

C# 复制代码
class ForLoopTest
{
    static void Main()
    {
        for (int i = 1; i <= 5; i++)
        {
            Console.WriteLine(i);
        }
    }
}
/*
Output:
1
2
3
4
5
*/

for 语句重复执行括起来的语句，如下所述：

  • 首先，计算变量 i 的初始值。

  • 然后，只要 i 的值小于或等于 5，条件计算结果就为 true。此时，将执行 Console.WriteLine 语句并重新计算 i。

  • 当 i 大于 5 时，条件变成 false 并且控制传递到循环外部。

由于条件表达式的测试发生在循环执行之前，因此 for 语句可能执行零次或多次。

for 语句的所有表达式都是可选的；例如，下列语句用于写一个无限循环：

C# 复制代码
for (; ; )
{
    // ...
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.9 for 语句

  • 8.8.3 for 语句

请参见

概念

C# 编程指南

参考

C# 关键字
foreach，in（C# 参考）
The for Statement
迭代语句（C# 参考）

其他资源

C# 参考


foreach，in（C# 参考）

[image]

C# 语言参考
foreach，in（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

foreach 语句对实现 System.Collections..::.IEnumerable 或 System.Collections.Generic..::.IEnumerable<(Of <(T>)>) 接口的数组或对象集合中的每个元素重复一组嵌入式语句。foreach 语句用于循环访问集合，以获取您需要的信息，但不能用于在源集合中添加或移除项，否则可能产生不可预知的副作用。如果需要在源集合中添加或移除项，请使用 for 循环。

嵌入语句为数组或集合中的每个元素继续执行。当为集合中的所有元素完成迭代后，控制传递给 foreach 块之后的下一个语句。

可以在 foreach 块的任何点使用 break 关键字跳出循环，或使用 continue 关键字进入循环的下一轮迭代。

foreach 循环还可以通过 goto、return 或 throw 语句退出。

有关 foreach 关键字和代码示例的更多信息，请参见下面的主题：

对数组使用 foreach（C# 编程指南）

如何：使用 foreach 访问集合类（C# 编程指南）

示例

在此示例中，使用 foreach 显示整数数组的内容。

C# 复制代码
class ForEachTest
{
    static void Main(string[] args)
    {
        int[] fibarray = new int[] { 0, 1, 2, 3, 5, 8, 13 };
        foreach (int i in fibarray)
        {
            System.Console.WriteLine(i);
        }
    }
}
/*
Output:
0
1
2
3
5
8
13
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.16 foreach 语句

  • 8.8.4 foreach 语句

请参见

概念

C# 编程指南

参考

C# 关键字
迭代语句（C# 参考）

其他资源

C# 参考


while（C# 参考）

[image]

C# 语言参考
while（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

while 语句执行一个语句或语句块，直到指定的表达式计算为 false。

示例

C# 复制代码
class WhileTest
{
    static void Main()
    {
        int n = 1;
        while (n < 6)
        {
            Console.WriteLine("Current value of n is {0}", n);
            n++;
        }
    }
}
/*
    Output:
    Current value of n is 1
    Current value of n is 2
    Current value of n is 3
    Current value of n is 4
    Current value of n is 5
 */
C# 复制代码
class WhileTest2
{
    static void Main()
    {
        int n = 1;
        while (n++ < 6)
        {
            Console.WriteLine("Current value of n is {0}", n);
        }
    }
}
/*
Output:
Current value of n is 2
Current value of n is 3
Current value of n is 4
Current value of n is 5
Current value of n is 6
*/

由于 while 表达式的测试在每次执行循环前发生，因此 while 循环执行零次或更多次。这与执行一次或多次的 do 循环不同。

当 break、goto、return 或 throw 语句将控制权转移到 while 循环之外时，可以终止该循环。若要将控制权传递给下一次迭代但不退出循环，请使用 continue 语句。请注意，在上面三个示例中，根据 int n 递增的位置的不同，输出也不同。在下面的示例中不生成输出。

C# 复制代码
class WhileTest3
{
    static void Main()
    {
        int n = 5;
        while (++n < 6)
        {
            Console.WriteLine("Current value of n is {0}", n);
        }
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.7 While 语句

  • 8.8.1 while 语句

请参见

概念

C# 编程指南

参考

C# 关键字
The while Statement
迭代语句（C# 参考）

其他资源

C# 参考


跳转语句（C# 参考）

[image]

C# 语言参考
跳转语句（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

使用跳转语句执行分支，该语句导致立即传递程序控制。跳转语句中使用下列关键字：

  • break

  • continue

  • goto

  • return

  • throw

请参见

概念

C# 编程指南

参考

C# 关键字
语句关键字（C# 参考）

其他资源

C# 参考


break（C# 参考）

[image]

C# 语言参考
break（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

break 语句用于终止最近的封闭循环或它所在的 switch 语句。控制传递给终止语句后面的语句（如果有的话）。

示例

在此例中，条件语句包含一个应该从 1 计数到 100 的计数器；但 break 语句在计数达到 4 后终止循环。

C# 复制代码
class BreakTest
{
    static void Main()
    {
        for (int i = 1; i <= 100; i++)
        {
            if (i == 5)
            {
                break;
            }
            Console.WriteLine(i);
        }
    }
}
/*
 Output:
    1
    2
    3
    4
*/

下面的示例演示 break 在 switch 语句中的用法。

C# 复制代码
class Switch
{
    static void Main()
    {
        Console.Write("Enter your selection (1, 2, or 3): ");
        string s = Console.ReadLine();
        int n = Int32.Parse(s);

        switch (n)
        {
            case 1:
                Console.WriteLine("Current value is {0}", 1);
                break;
            case 2:
                Console.WriteLine("Current value is {0}", 2);
                break;
            case 3:
                Console.WriteLine("Current value is {0}", 3);
                break;
            default:
                Console.WriteLine("Sorry, invalid selection.");
                break;
        }
    }
}
/*
Sample Input: 1

Sample Output:
Enter your selection (1, 2, or 3): 1
Current value is 1
*/

如果输入了 4，则输出为：

复制代码
Enter your selection (1, 2, or 3): 4
Sorry, invalid selection.

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.10 Break、continue 和 goto 语句

  • 8.9.1 break 语句

请参见

概念

C# 编程指南

参考

C# 关键字
The break Statement
switch（C# 参考）
跳转语句（C# 参考）

其他资源

C# 参考


continue（C# 参考）

[image]

C# 语言参考
continue（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

continue 语句将控制权传递给它所在的封闭迭代语句的下一次迭代。

示例

在此示例中，计数器最初是从 1 到 10 进行计数，但通过将 continue 语句与表达式 (i < 9) 一起使用，跳过了 continue 与 for 循环体末尾之间的语句。

C# 复制代码
class ContinueTest
{
    static void Main()
    {
        for (int i = 1; i <= 10; i++)
        {
            if (i < 9)
            {
                continue;
            }
            Console.WriteLine(i);
        }
    }
}
/*
Output:
9
10
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.10 Break、continue 和 goto 语句

  • 8.9.2 continue 语句

请参见

概念

C# 编程指南

参考

C# 关键字
The break Statement
跳转语句（C# 参考）

其他资源

C# 参考


goto（C# 参考）

[image]

C# 语言参考
goto（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

goto 语句将程序控制直接传递给标记语句。

goto 的一个通常用法是将控制传递给特定的 switch-case 标签或 switch 语句中的默认标签。

goto 语句还用于跳出深嵌套循环。

示例

下面的示例演示了 goto 在 switch 语句中的使用。

C# 复制代码
class SwitchTest
{
    static void Main()
    {
        Console.WriteLine("Coffee sizes: 1=Small 2=Medium 3=Large");
        Console.Write("Please enter your selection: ");
        string s = Console.ReadLine();
        int n = int.Parse(s);
        int cost = 0;
        switch (n)
        {
            case 1:
                cost += 25;
                break;
            case 2:
                cost += 25;
                goto case 1;
            case 3:
                cost += 50;
                goto case 1;
            default:
                Console.WriteLine("Invalid selection.");
                break;
        }
        if (cost != 0)
        {
            Console.WriteLine("Please insert {0} cents.", cost);
        }
        Console.WriteLine("Thank you for your business.");
    }
}
/*
Sample Input:  2

Sample Output:
Coffee sizes: 1=Small 2=Medium 3=Large
Please enter your selection: 2
Please insert 50 cents.
Thank you for your business.
*/

下面的示例演示了使用 goto 跳出嵌套循环。

C# 复制代码
public class GotoTest1
{
    static void Main()
    {
        int x = 200, y = 4;
        int count = 0;
        string[,] array = new string[x, y];

        // Initialize the array:
        for (int i = 0; i < x; i++)

            for (int j = 0; j < y; j++)
                array[i, j] = (++count).ToString();

        // Read input:
        Console.Write("Enter the number to search for: ");

        // Input a string:
        string myNumber = Console.ReadLine();

        // Search:
        for (int i = 0; i < x; i++)
        {
            for (int j = 0; j < y; j++)
            {
                if (array[i, j].Equals(myNumber))
                {
                    goto Found;
                }
            }
        }

        Console.WriteLine("The number {0} was not found.", myNumber);
        goto Finish;

    Found:
        Console.WriteLine("The number {0} is found.", myNumber);

    Finish:
        Console.WriteLine("End of search.");
    }
}
/*
Sample Input: 44

Sample Output
Enter the number to search for: 44
The number 44 is found.
End of search.
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.10 Break、continue 和 goto 语句

  • 8.9.3 goto 语句

请参见

概念

C# 编程指南

参考

C# 关键字
The goto Statement
跳转语句（C# 参考）

其他资源

C# 参考


return（C# 参考）

[image]

C# 语言参考
return（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

return 语句终止它出现在其中的方法的执行并将控制返回给调用方法。它还可以返回一个可选值。如果方法为 void 类型，则可以省略 return 语句。

如果 return 语句位于 try 块中，则将在控制流返回到调用方法之前执行 finally 块（如果存在）。

示例

在下面的示例中，方法 A() 以 double 值的形式返回变量 Area。

C# 复制代码
class ReturnTest
{
    static double CalculateArea(int r)
    {
        double area = r * r * Math.PI;
        return area;
    }

    static void Main()
    {
        int radius = 5;
        double result = CalculateArea(radius);
        Console.WriteLine("The area is {0:0.00}", result);
    }
}
// Output: The area is 78.54

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.12 Return 语句

  • 8.9.4 return 语句

请参见

概念

C# 编程指南

参考

C# 关键字
The return Statement
跳转语句（C# 参考）

其他资源

C# 参考

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 增加了与 try-finally 有关的说明。 | 客户反馈。


异常处理语句（C# 参考）

[image]

C# 语言参考
异常处理语句（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 为处理在程序执行期间可能出现的反常情况（称作异常）提供内置支持。这些异常由正常控制流之外的代码处理。

本节说明下列异常处理主题：

  • throw

  • try-catch

  • try-finally

  • try-catch-finally

请参见

概念

C# 编程指南

参考

C# 关键字
语句关键字（C# 参考）
异常和异常处理（C# 编程指南）

其他资源

C# 参考


throw（C# 参考）

[image]

C# 语言参考
throw（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

throw 语句用于发出在程序执行期间出现反常情况（异常）的信号。

备注

引发的异常是一个对象，该对象的类是从 System..::.Exception 派生的，例如：

复制代码
class MyException : System.Exception {}
// ...
throw new MyException();

通常 throw 语句与 try-catch 或 try-finally 语句一起使用。

也可以用 throw 语句重新引发已捕获的异常。有关更多信息和示例，请参见 try-catch 和 引发异常。

示例

此例演示如何使用 throw 语句引发异常。

C# 复制代码
public class ConstTest
{
    class SampleClass
    {
        public int x;
        public int y;
        public const int c1 = 5;
        public const int c2 = c1 + 5;

        public SampleClass(int p1, int p2)
        {
            x = p1;
            y = p2;
        }
    }

    static void Main()
    {
        SampleClass mC = new SampleClass(11, 22);
        Console.WriteLine("x = {0}, y = {1}", mC.x, mC.y);
        Console.WriteLine("c1 = {0}, c2 = {1}",
                          SampleClass.c1, SampleClass.c2 );
    }
}
/* Output
    x = 11, y = 22
    c1 = 5, c2 = 10
 */

代码示例

请参见 try-catch、try-finally 和 try-catch-finally 示例。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.11 throw 语句

  • 8.9.5 throw 语句

请参见

任务

如何：显式引发异常

概念

C# 编程指南

参考

The try, catch, and throw Statements
C# 关键字
异常处理语句（C# 参考）

其他资源

C# 参考


try-catch（C# 参考）

[image]

C# 语言参考
try-catch（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

try-catch 语句由一个 try 块后跟一个或多个 catch 子句构成，这些子句指定不同的异常处理程序。引发异常时，公共语言运行时 (CLR) 会查找处理此异常的 catch 语句。如果当前执行的方法不包含这样的 catch 块，则 CLR 会查看调用当前方法的方法，然后会遍历调用堆栈。如果找不到 catch 块，则 CLR 会向用户显示一条有关未处理异常的消息并停止执行程序。

try 块包含可能导致异常的保护代码。该块一直执行到引发异常或成功完成为止。例如，下列强制转换 null 对象的尝试引发 NullReferenceException 异常：

复制代码
object o2 = null;
try
{
    int i2 = (int)o2;   // Error
}

虽然可以使用不带参数的 catch 子句捕捉任何类型的异常，但不推荐这种用法。通常，您应该只捕捉那些您知道如何从中恢复的异常。因此，应该总是指定一个从 System..::.Exception 派生的对象参数。例如：

复制代码
catch (InvalidCastException e)
{
}

在同一个 try-catch 语句中可以使用一个以上的特定 catch 子句。这种情况下 catch 子句的顺序很重要，因为会按顺序检查 catch 子句。将先捕获特定程度较高的异常，而不是特定程度较小的异常。如果对 catch 块进行排序以使永远不能达到后面的块，编译器将产生错误。

在 catch 块中可以使用 throw 语句再次引发已由 catch 语句捕获的异常。例如：

复制代码
catch (InvalidCastException e)
{
    throw (e);    // Rethrowing exception e
}

您也可以引发新的异常。此时，请将捕捉的异常指定为内部异常：

复制代码
catch (InvalidCastException e)
{
   // Can do cleanup work here.
    throw new CustomException("Error message here.", e);
}

如果要再次引发当前由无参数的 catch 子句处理的异常，则使用不带参数的 throw 语句。例如：

复制代码
catch
{
    throw;
}

在 try 块内部时应该只初始化其中声明的变量；否则，完成该块的执行前可能发生异常。例如，在下面的代码示例中，变量 x 在 try 块内初始化。试图在 Write(x) 语句中的 try 块外部使用此变量时将产生编译器错误：使用了未赋值的局部变量。

复制代码
static void Main()
{
    int x;
    try
    {
        // Don't initialize this variable here.
        x = 123;
    }
    catch
    {
    }
    // Error: Use of unassigned local variable 'x'.
    Console.Write(x);
}

有关 catch 的更多信息，请参见 try-catch-finally。

示例

在此例中，try 块包含对可能导致异常的 ProcessString 方法的调用。catch 子句包含仅在屏幕上显示消息的异常处理程序。当从 MyMethod 内部调用 throw 语句时，系统查找 catch 语句并显示 Exception caught 消息。

C# 复制代码
    class TryFinallyTest
{
    static void ProcessString(string s)
    {
        if (s == null)
        {
            throw new ArgumentNullException();
        }
    }

    static void Main()
    {
        string s = null; // For demonstration purposes.

        try
        {
            ProcessString(s);
        }

        catch (Exception e)
        {
            Console.WriteLine("{0} Exception caught.", e);
        }
    }
}
    /*
    Output:
    System.ArgumentNullException: Value cannot be null.
       at TryFinallyTest.Main() Exception caught.
     * */

此例使用了两个 catch 语句。最先出现的最特定的异常被捕获。

C# 复制代码
class ThrowTest3
{
    static void ProcessString(string s)
    {
        if (s == null)
        {
            throw new ArgumentNullException();
        }
    }

    static void Main()
    {
        try
        {
            string s = null;
            ProcessString(s);
        }
        // Most specific:
        catch (ArgumentNullException e)
        {
            Console.WriteLine("{0} First exception caught.", e);
        }
        // Least specific:
        catch (Exception e)
        {
            Console.WriteLine("{0} Second exception caught.", e);
        }
    }
}
/*
 Output:
System.ArgumentNullException: Value cannot be null.
   at TryFinallyTest.Main() First exception caught.
*/

在前面的示例中，如果从具体程度最低的 catch 子句开始，您将收到以下错误信息：

A previous catch clause already catches all exceptions of this or a super type ('System.Exception')

但是，若要捕获特定程度最小的异常，请使用下面的语句替换 throw 语句：

throw new Exception();

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.13 Try-catch 语句

  • 8.10 try 语句

  • 16 异常

请参见

任务

如何：显式引发异常

概念

C# 编程指南

参考

C# 关键字
The try, catch, and throw Statements
异常处理语句（C# 参考）
throw（C# 参考）
try-finally（C# 参考）

其他资源

C# 参考


try-finally（C# 参考）

[image]

C# 语言参考
try-finally（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

finally 块用于清除 try 块中分配的任何资源，以及运行任何即使在发生异常时也必须执行的代码。控制总是传递给 finally 块，与 try 块的退出方式无关。

catch 用于处理语句块中出现的异常，而 finally 用于保证代码语句块的执行，与前面的 try 块的退出方式无关。

示例

在此例中，有一个导致异常的无效转换语句。当运行程序时，您收到一条运行时错误信息，但 finally 子句仍继续执行并显示输出。

C# 复制代码
public class ThrowTest
{
    static void Main()
    {
        int i = 123;
        string s = "Some string";
        object o = s;

        try
        {
            // Invalid conversion; o contains a string not an int
            i = (int)o;
        }
        finally
        {
            Console.Write("i = {0}", i);
        }
    }
}

上面的示例将导致引发 System.InvalidCastException。

尽管捕捉了异常，但仍会执行 finally 块中包含的输出语句，即：

i = 123

有关 finally 的更多信息，请参见 try-catch-finally。

C# 还提供了 using 语句，该语句为与 try-finally 语句完全相同的功能提供了简便语法。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.14 Try-finally 语句

  • 8.11 try 语句

  • 16 异常

请参见

任务

如何：显式引发异常

概念

C# 编程指南

参考

C# 关键字
The try, catch, and throw Statements
异常处理语句（C# 参考）
throw（C# 参考）
try-catch（C# 参考）

其他资源

C# 参考


try-catch-finally（C# 参考）

[image]

C# 语言参考
try-catch-finally（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

catch 和 finally 一起使用的常见方式是：在 try 块中获取并使用资源，在 catch 块中处理异常情况，并在 finally 块中释放资源。

有关重新引发异常的更多信息和示例，请参见 try-catch 和“引发异常”。

示例

C# 复制代码
public class EHClass
{
    void ReadFile(int index)
    {
        // To run this code, substitute a valid path from your local machine
        string path = @"c:\users\public\test.txt";
        System.IO.StreamReader file = new System.IO.StreamReader(path);
        char[] buffer = new char[10];
        try
        {
            file.ReadBlock(buffer, index, buffer.Length);
        }
        catch (System.IO.IOException e)
        {
            Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
        }

        finally
        {
            if (file != null)
            {
                file.Close();
            }
        }
        // Do something with buffer...
    }

}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.15 Try-catch-finally 语句

  • 8.10 try 语句

  • 16 异常

请参见

任务

如何：显式引发异常

概念

C# 编程指南

参考

C# 关键字
The try, catch, and throw Statements
异常处理语句（C# 参考）
throw（C# 参考）
using 语句（C# 参考）

其他资源

C# 参考


Checked 和 Unchecked（C# 参考）

[image]

C# 语言参考
Checked 和 Unchecked（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 语句既可以在已检查的上下文中执行，也可以在未检查的上下文中执行。在已检查的上下文中，算法溢出引发异常。在未检查的上下文中，算法溢出被忽略并且结果被截断。

  • checked 指定已检查的上下文。

  • unchecked 指定未检查的上下文。

如果既未指定 checked 也未指定 unchecked，则默认上下文取决于外部因素（如编译器选项）。

下列操作受溢出检查的影响：

  • 表达式在整型上使用下列预定义运算符：

    ++ ¡ª -（一元） + - * /

  • 整型间的显式数字转换。

/checked 编译器选项使您可以为 checked 或 unchecked 关键字范围内的所有非显式整型算术语句指定已检查或未检查的上下文。

请参见

概念

C# 编程指南

参考

C# 关键字
语句关键字（C# 参考）

其他资源

C# 参考


checked（C# 参考）

[image]

C# 语言参考
checked（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

checked 关键字用于对整型算术运算和转换显式启用溢出检查。

默认情况下，如果表达式产生的值超出了目标类型的范围，则常数表达式将导致编译时错误，而非常数表达式在运行时计算并将引发异常。不过，如果通过编译器选项或环境配置在全局范围内取消了溢出检查，则可以使用 checked 关键字来启用此项功能。

请参见有关 unchecked 关键字用法的 unchecked 示例。

示例

此示例演示如何对非常数表达式使用 checked。在运行时会报告溢出。

C# 复制代码
class OverFlowTest
{

    static short x = 32767;   // Max short value
    static short y = 32767;

    // Using a checked expression
    static int CheckedMethod()
    {
        int z = 0;
        try
        {
            z = checked((short)(x + y));
        }
        catch (System.OverflowException e)
        {
            Console.WriteLine(e.ToString());
        }
        return z;
    }

    static void Main()
    {
        Console.WriteLine("Checked output value is: {0}",
                     CheckedMethod());
    }
}
/*
    Output:
    System.OverflowException: Arithmetic operation resulted in an overflow.
       at OverFlowTest.CheckedMethod()
    Checked output value is: 0
 */

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.2 Block 语句、checked 和 unchecked 语句

  • 7.5.12 checked 和 unchecked 运算符

  • 8.11 checked 和 unchecked 语句

请参见

概念

C# 编程指南

参考

C# 关键字
Checked 和 Unchecked（C# 参考）
unchecked（C# 参考）

其他资源

C# 参考


unchecked（C# 参考）

[image]

C# 语言参考
unchecked（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

unchecked 关键字用于取消整型算术运算和转换的溢出检查。

在未检查的上下文中，如果表达式产生目标类型范围之外的值，则结果被截断。例如：

复制代码
unchecked
{
    int val = 2147483647 * 2;
}

因为上面的计算在 unchecked 块中执行，所以结果对于整数来说太大这一事实被忽略，并且 val 被赋予值 -2。默认情况下，启用溢出检测，这与使用 checked 具有相同的效果。

在上面的示例中，如果省略 unchecked，将产生编译错误，因为表达式使用常数，结果在编译时是已知的。unchecked 关键字还取消对非常数表达式的溢出检测，这是为了避免在运行时导致 OverflowException。

unchecked 关键字还可以用作运算符，如下所示：

复制代码
public int UncheckedAdd(int a, int b)
{
    return unchecked(a + b);
}

示例

此示例演示如何将 unchecked 语句与常量表达式配合使用。

C# 复制代码
class TestClass
{
    const int x = 2147483647;   // Max int
    const int y = 2;

    static void Main()
    {
        int z;
        unchecked
        {
            z = x * y;
        }
        Console.WriteLine("Unchecked output value: {0}", z);
    }
}
// Output: Unchecked output value: -2

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.2 Block 语句、checked 和 unchecked 语句

  • 7.5.12 checked 和 unchecked 运算符

  • 8.11 checked 和 unchecked 语句

请参见

概念

C# 编程指南

参考

C# 关键字
Checked 和 Unchecked（C# 参考）
checked（C# 参考）

其他资源

C# 参考


fixed 语句（C# 参考）

[image]

C# 语言参考
fixed 语句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

fixed 语句禁止垃圾回收器重定位可移动的变量。fixed 语句只能出现在不安全的上下文中。Fixed 还可用于创建固定大小的缓冲区。

fixed 语句设置指向托管变量的指针，并在执行该语句期间“固定”此变量。如果没有 fixed 语句，则指向可移动托管变量的指针的作用很小，因为垃圾回收可能不可预知地重定位变量。C# 编译器只允许在 fixed 语句中分配指向托管变量的指针。

C# 复制代码
unsafe static void TestMethod()
{

    // assume class Point { public int x, y; }
    // pt is a managed variable, subject to garbage collection.
    Point pt = new Point();

    // Using fixed allows the address of pt members to be
    // taken, and "pins" pt so it isn't relocated.

    fixed (int* p = &pt.x)
    {
        *p = 1;
    }

}

可以用数组或字符串的地址初始化指针：

C# 复制代码
unsafe void Test2()
{
    Point point = new Point();
    double[] arr = { 0, 1.5, 2.3, 3.4, 4.0, 5.9 };
    string str = "Hello World";

    fixed (double* p = arr) { /*...*/ }   // equivalent to p = &arr[0]
    fixed (char* p = str) { /*...*/ }  // equivalent to p = &str[0]

    fixed (int* p1 = &point.x)
    {
        fixed (double* p2 = &arr[5])
        {
            // Do something with p1 and p2.
        }
    }

}

只要指针的类型相同，就可以初始化多个指针：

复制代码
fixed (byte* ps = srcarray, pd = dstarray) {...}

要初始化不同类型的指针，只需嵌套 fixed 语句：

C# 复制代码
fixed (int* p1 = &point.x)
{
    fixed (double* p2 = &arr[5])
    {
        // Do something with p1 and p2.
    }
}

执行完语句中的代码后，任何固定变量都被解除固定并受垃圾回收的制约。因此，不要指向 fixed 语句之外的那些变量。

说明：

无法修改在 fixed 语句中初始化的指针。

在不安全模式中，可以在堆栈上分配内存。堆栈不受垃圾回收的制约，因此不需要被锁定。有关更多信息，请参见 stackalloc。

示例

C# 复制代码
class Point
{
    public int x, y;
}

class FixedTest2
{
    // Unsafe method: takes a pointer to an int.
    unsafe static void SquarePtrParam (int* p)
    {
        *p *= *p;
    }

    unsafe static void Main()
    {
        Point pt = new Point();
        pt.x = 5;
        pt.y = 6;
        // Pin pt in place:
        fixed (int* p = &pt.x)
        {
            SquarePtrParam (p);
        }
        // pt now unpinned
        Console.WriteLine ("{0} {1}", pt.x, pt.y);
    }
}
/*
Output:
25 6
 */

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 18.3 固定变量和可移动变量

  • 18.6 fixed 语句

请参见

概念

C# 编程指南

参考

C# 关键字
unsafe（C# 参考）
固定大小的缓冲区（C# 编程指南）

其他资源

C# 参考


lock 语句（C# 参考）

[image]

C# 语言参考
lock 语句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

lock 关键字将语句块标记为临界区，方法是获取给定对象的互斥锁，执行语句，然后释放该锁。 此语句的形式如下：

复制代码
Object thisLock = new Object();
lock (thisLock)
{
    // Critical code section.
}

有关更多信息，请参见 线程同步（C# 编程指南）。

备注

lock 关键字可确保当一个线程位于代码的临界区时，另一个线程不会进入该临界区。 如果其他线程试图进入锁定的代码，则它将一直等待（即被阻止），直到该对象被释放。

线程处理（C# 编程指南） 这节讨论了线程处理。

lock 关键字在块的开始处调用 Enter，而在块的结尾处调用 Exit。

通常，应避免锁定 public 类型，否则实例将超出代码的控制范围。 常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则：

  • 如果实例可以被公共访问，将出现 lock (this) 问题。

  • 如果 MyType 可以被公共访问，将出现 lock (typeof (MyType)) 问题。

  • 由于进程中使用同一字符串的任何其他代码都将共享同一个锁，所以出现 lock(“myLock”) 问题。

最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。

示例

下面演示在 C# 中使用未锁定的线程的简单示例。

C# 复制代码
//using System.Threading;

class ThreadTest
{
    public void RunMe()
    {
        Console.WriteLine("RunMe called");
    }

    static void Main()
    {
        ThreadTest b = new ThreadTest();
        Thread t = new Thread(b.RunMe);
        t.Start();
    }
}
// Output: RunMe called

下例使用线程和 lock。 只要 lock 语句存在，语句块就是临界区并且 balance 永远不会是负数。

C# 复制代码
// using System.Threading;

class Account
{
    private Object thisLock = new Object();
    int balance;

    Random r = new Random();

    public Account(int initial)
    {
        balance = initial;
    }

    int Withdraw(int amount)
    {

        // This condition will never be true unless the lock statement
        // is commented out:
        if (balance < 0)
        {
            throw new Exception("Negative Balance");
        }

        // Comment out the next line to see the effect of leaving out
        // the lock keyword:
        lock (thisLock)
        {
            if (balance >= amount)
            {
                Console.WriteLine("Balance before Withdrawal :  " + balance);
                Console.WriteLine("Amount to Withdraw        : -" + amount);
                balance = balance - amount;
                Console.WriteLine("Balance after Withdrawal  :  " + balance);
                return amount;
            }
            else
            {
                return 0; // transaction rejected
            }
        }
    }

    public void DoTransactions()
    {
        for (int i = 0; i < 100; i++)
        {
            Withdraw(r.Next(1, 100));
        }
    }
}

class Test
{
    static void Main()
    {
        Thread[] threads = new Thread[10];
        Account acc = new Account(1000);
        for (int i = 0; i < 10; i++)
        {
            Thread t = new Thread(new ThreadStart(acc.DoTransactions));
            threads[i] = t;
        }
        for (int i = 0; i < 10; i++)
        {
            threads[i].Start();
        }
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.18 Lock 语句

  • 8.12 lock 语句

请参见

任务

监视器同步技术示例
等待同步技术示例

概念

C# 编程指南
监视器
互锁操作
AutoResetEvent

参考

线程处理（C# 编程指南）
C# 关键字
语句关键字（C# 参考）
MethodImplAttributes
Mutex
线程同步（C# 编程指南）

其他资源

C# 参考


方法参数（C# 参考）

[image]

C# 语言参考
方法参数（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

如果在为方法声明参数时未使用 ref 或 out，则该参数可以具有关联的值。可以在方法中更改该值，但当控制传递回调用过程时，不会保留更改的值。通过使用方法参数关键字，可以更改这种行为。

本节描述声明方法参数时可以使用的关键字：

  • params

  • ref

  • out

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


params（C# 参考）

[image]

C# 语言参考
params（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

params 关键字可以指定在参数数目可变处采用参数的方法参数。

在方法声明中的 params 关键字之后不允许任何其他参数，并且在方法声明中只允许一个 params 关键字。

示例

C# 复制代码
public class MyClass
{

    public static void UseParams(params int[] list)
    {
        for (int i = 0 ; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    public static void UseParams2(params object[] list)
    {
        for (int i = 0 ; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        UseParams(1, 2, 3);
        UseParams2(1, 'a', "test");

        // An array of objects can also be passed, as long as
        // the array type matches the method being called.
        int[] myarray = new int[3] {10,11,12};
        UseParams(myarray);
    }
}
/*
Output:
    1 2 3
    1 a test
    10 11 12
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.6.1.4 参数数组

请参见

概念

C# 编程指南

参考

C# 关键字
方法参数（C# 参考）

其他资源

C# 参考


ref（C# 参考）

[image]

C# 语言参考
ref（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

ref 关键字使参数按引用传递。其效果是，当控制权传递回调用方法时，在方法中对参数的任何更改都将反映在该变量中。若要使用 ref 参数，则方法定义和调用方法都必须显式使用 ref 关键字。例如：

C# 复制代码
class RefExample
{
    static void Method(ref int i)
    {
        i = 44;
    }
    static void Main()
    {
        int val = 0;
        Method(ref val);
        // val is now 44
    }
}

传递到 ref 参数的参数必须最先初始化。这与 out 不同，后者的参数在传递之前不需要显式初始化。有关更多信息，请参见 out。

尽管 ref 和 out 在运行时的处理方式不同，但在编译时的处理方式相同。因此，如果一个方法采用 ref 参数，而另一个方法采用 out 参数，则无法重载这两个方法。例如，从编译的角度来看，以下代码中的两个方法是完全相同的，因此将不会编译以下代码：

C# 复制代码
class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}

但是，如果一个方法采用 ref 或 out 参数，而另一个方法不采用这两个参数，则可以进行重载，如下例所示：

C# 复制代码
class RefOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(ref int i) { }
}

属性不是变量，因此不能作为 ref 参数传递。

有关如何传递数组的信息，请参见使用 ref 和 out 传递数组（C# 编程指南）。

示例

按引用传递值类型（如本主题前面所示）是有用的，但是 ref 对于传递引用类型也是很有用的。这允许被调用的方法修改该引用所引用的对象，因为引用本身是按引用来传递的。下面的示例显示出当引用类型作为 ref 参数传递时，可以更改对象本身。

C# 复制代码
class RefExample2
{
    static void Method(ref string s)
    {
        s = "changed";
    }
    static void Main()
    {
        string str = "original";
        Method(ref str);
        Console.WriteLine(str);
    }
}
// Output: changed

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.1.5 引用参数

  • 10.6.1.2 引用参数

请参见

概念

C# 编程指南

参考

传递参数（C# 编程指南）
方法参数（C# 参考）
C# 关键字

其他资源

C# 参考


out（C# 参考）

[image]

C# 语言参考
out（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

out 关键字会导致参数通过引用来传递。这与 ref 关键字类似，不同之处在于 ref 要求变量必须在传递之前进行初始化。若要使用 out 参数，方法定义和调用方法都必须显式使用 out 关键字。例如：

C# 复制代码
class OutExample
{
    static void Method(out int i)
    {
        i = 44;
    }
    static void Main()
    {
        int value;
        Method(out value);
        // value is now 44
    }
}

尽管作为 out 参数传递的变量不必在传递之前进行初始化，但需要调用方法以便在方法返回之前赋值。

ref 和 out 关键字在运行时的处理方式不同，但在编译时的处理方式相同。因此，如果一个方法采用 ref 参数，而另一个方法采用 out 参数，则无法重载这两个方法。例如，从编译的角度来看，以下代码中的两个方法是完全相同的，因此将不会编译以下代码：

C# 复制代码
class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}

但是，如果一个方法采用 ref 或 out 参数，而另一个方法不采用这两类参数，则可以进行重载，如下所示：

C# 复制代码
class OutOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(out int i) { i = 5; }
}

属性不是变量，因此不能作为 out 参数传递。

有关传递数组的信息，请参见使用 ref 和 out 传递数组（C# 编程指南）。

示例

当希望方法返回多个值时，声明 out 方法很有用。使用 out 参数的方法仍然可以将变量作为返回类型来访问（请参见 return），但它还可以将一个或多个对象作为 out 参数返回给调用方法。此示例使用 out 在一个方法调用中返回三个变量。请注意，第三个参数所赋的值为 Null。这样使方法可以有选择地返回值。

C# 复制代码
class OutReturnExample
{
    static void Method(out int i, out string s1, out string s2)
    {
        i = 44;
        s1 = "I've been returned";
        s2 = null;
    }
    static void Main()
    {
        int value;
        string str1, str2;
        Method(out value, out str1, out str2);
        // value is now 44
        // str1 is now "I've been returned"
        // str2 is (still) null;
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.1.6 输出参数

  • 10.6.1.3 输出参数

请参见

概念

C# 编程指南

参考

C# 关键字
方法参数（C# 参考）

其他资源

C# 参考


命名空间关键字（C# 参考）

[image]

C# 语言参考
命名空间关键字（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

本节描述与 using 命名空间关联的关键字和运算符：

  • 命名空间

  • using

  • . 运算符

  • :: 运算符

  • 外部别名

请参见

概念

C# 编程指南

参考

C# 关键字
命名空间（C# 编程指南）

其他资源

C# 参考


命名空间（C# 参考）

[image]

C# 语言参考
命名空间（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

namespace 关键字用于声明一个范围。此命名空间范围允许您组织代码并为您提供了创建全局唯一类型的方法。

C# 复制代码
namespace SampleNamespace
{
    class SampleClass { }

    interface SampleInterface { }

    struct SampleStruct { }

    enum SampleEnum { a, b }

    delegate void SampleDelegate(int i);

    namespace SampleNamespace.Nested
    {
        class SampleClass2 { }
    }
}

备注

在一个命名空间中，可以声明一个或多个下列类型：

  • 另一个命名空间

  • 类

  • 接口

  • 结构

  • 枚举

  • 委托

无论您是否在 C# 源文件中显式声明了命名空间，编译器都会添加一个默认的命名空间。该未命名的命名空间（有时称为全局命名空间）存在于每一个文件中。全局命名空间中的任何标识符都可用于命名的命名空间中。

命名空间隐式具有公共访问权，并且这是不可修改的。有关可以分配给命名空间中的元素的访问修饰符的讨论，请参见访问修饰符（C# 参考）。

在两个或更多的声明中定义一个命名空间是可以的。例如，下面的示例将两个类定义为 MyCompany 命名空间的一部分：

C# 复制代码
namespace MyCompany.Proj1
{
    class MyClass
    {
    }
}

namespace MyCompany.Proj1
{
    class MyClass1
    {
    }
}

示例

下面的示例显示了如何在嵌套的命名空间中调用静态方法。

C# 复制代码
namespace SomeNameSpace
{
    public class MyClass
    {
        static void Main()
        {
            Nested.NestedNameSpaceClass.SayHello();
        }
    }

    // a nested namespace
    namespace Nested
    {
        public class NestedNameSpaceClass
        {
            public static void SayHello()
            {
                Console.WriteLine("Hello");
            }
        }
    }
}
// Output: Hello

更多信息

有关使用命名空间的更多信息，请参见下列主题：

  • 命名空间（C# 编程指南）

  • 使用命名空间（C# 编程指南）

  • 如何：使用命名空间别名限定符（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 3.4.1 命名空间成员

  • 3.8 命名空间和类型名称

  • 9 命名空间

请参见

概念

C# 编程指南

参考

C# 关键字
命名空间关键字（C# 参考）
using（C# 参考）

其他资源

C# 参考


using（C# 参考）

[image]

C# 语言参考
using（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

using 关键字有两个主要用途：

  • 作为指令，用于为命名空间创建别名或导入其他命名空间中定义的类型。请参见 using 指令。

  • 作为语句，用于定义一个范围，在此范围的末尾将释放对象。请参见 using 语句。

请参见

概念

C# 编程指南

参考

C# 关键字
命名空间关键字（C# 参考）
命名空间（C# 编程指南）
extern（C# 参考）

其他资源

C# 参考


using 指令（C# 参考）

[image]

C# 语言参考
using 指令（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

using 指令有两个用途：

  • 允许在命名空间中使用类型，这样，您就不必在该命名空间中限定某个类型的使用：

    复制代码
    using System.Text;

  • 为命名空间或类型创建别名。这称为“using 别名指令”。

    复制代码
    using Project = PC.MyCompany.Project;

using 关键字还用来创建 using 语句，此类语句有助于确保正确处理 IDisposable 对象（如文件和字体）。有关更多信息，请参见 using 语句。

备注

using 指令的范围限制为包含它的文件。

创建 using 别名，以便更易于将标识符限定到命名空间或类型。using 别名指令的右侧必须始终是一个完全限定类型，而与前面的 using 指令无关。

创建 using 指令，以便在命名空间中使用类型而不必指定命名空间。using 指令不为您提供对嵌套在指定命名空间中的任何命名空间的访问。

命名空间分为两类：用户定义的命名空间和系统定义的命名空间。用户定义的命名空间是在代码中定义的命名空间。若要查看系统定义的命名空间的列表，请参见 .NET Framework 类库参考。

有关引用其他程序集中的方法的示例，请参见创建和使用 C# DLL。

示例 1

说明

下面的示例显示了如何为命名空间定义和使用 using 别名：

代码

C# 复制代码
namespace PC
{
    // Define an alias for the nested namespace.
    using Project = PC.MyCompany.Project;
    class A
    {
        void M()
        {
            // Use the alias
            Project.MyClass mc = new Project.MyClass();
        }
    }
    namespace MyCompany
    {
        namespace Project
        {
            public class MyClass { }
        }
    }
}

注释

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

示例 2

说明

下面的示例显示了如何为类定义 using 指令和 using 别名：

代码

C# 复制代码
using System;
// Using alias for a class.
using AliasToMyClass = NameSpace1.MyClass;

namespace NameSpace1
{
    public class MyClass
    {
        public override string ToString()
        {
            return "You are in NameSpace1.MyClass";
        }
    }
}

namespace NameSpace2
{
    class MyClass
    {
    }
}

namespace NameSpace3
{
    // Using directive:
    using NameSpace1;
    // Using directive:
    using NameSpace2;

    class MainClass
    {
        static void Main()
        {
            AliasToMyClass somevar = new AliasToMyClass();
            Console.WriteLine(somevar);
        }
    }
}
// Output: You are in NameSpace1.MyClass

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各节：

  • 9.3 Using 指令

请参见

概念

C# 编程指南

参考

C# 关键字
命名空间关键字（C# 参考）
命名空间（C# 编程指南）
using 语句（C# 参考）

其他资源

C# 参考


using 语句（C# 参考）

[image]

C# 语言参考
using 语句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

提供能确保正确使用 IDisposable 对象的方便语法。

示例

下面的示例演示如何使用 using 语句。

C# 复制代码
using (System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\Users\Public\Documents\test.txt"))
{
    string s = null;
    while((s = sr.ReadLine()) != null)
    {
        Console.WriteLine(s);
    }
}

备注

File 和 Font 是访问非托管资源（本例中为文件句柄和设备上下文）的托管类型的示例。有许多其他类别的非托管资源和封装这些资源的类库类型。所有这些类型都必须实现 IDisposable 接口。

按照规则，当使用 IDisposable 对象时，应在 using 语句中声明和实例化此对象。using 语句按照正确的方式调用对象上的 Dispose 方法，并（在您按照前面所示方式使用它时）会导致在调用 Dispose 时对象自身处于范围之外。在 using 块中，对象是只读的并且无法进行修改或重新分配。

using 语句确保调用 Dispose，即使在调用对象上的方法时发生异常也是如此。通过将对象放入 try 块中，并在调用 finally 块中的 Dispose，可以获得相同的结果；实际上，这就是编译器转换 using 语句的方式。前面的代码示例在编译时将扩展到以下代码（请注意，使用额外的大括号为对象创建有限范围）：

C# 复制代码
{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

可以将多个对象与 using 语句一起使用，但必须在 using 语句中声明这些对象，如以下示例所示：

C# 复制代码
using (Font font3 = new Font("Arial", 10.0f),
            font4 = new Font("Arial", 10.0f))
{
    // Use font3 and font4.
}

可以实例化资源对象，然后将变量传递给 using 语句，但这不是最佳做法。在这种情况下，该对象将在控制权离开 using 块之后保持在范围内，即使它可能将不再具有对其非托管资源的访问权也是如此。换句话说，再也不能完全初始化该对象。如果试图在 using 块外部使用该对象，则可能导致引发异常。由于这个原因，通常最好是在 using 语句中实例化该对象并将其范围限制在 using 块中。

C# 复制代码
Font font2 = new Font("Arial", 10.0f);
using (font2) // not recommended
{
    // use font2
}
// font2 is still in scope
// but the method call throws an exception
float f = font2.GetHeight();

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 5.3.3.17 Using 语句

  • 8.13 using 语句

请参见

概念

C# 编程指南
实现 Dispose 方法

参考

C# 关键字
using 指令（C# 参考）
实现 Finalize 和 Dispose 以清理非托管资源

其他资源

C# 参考
垃圾回收

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 在介绍后面增加了代码示例。 | 内容 Bug 修复


外部别名（C# 参考）

[image]

C# 语言参考
外部别名（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

可能必须引用两个具有相同完全限定类型名的程序集版本。例如，可能必须在同一应用程序中使用程序集的两个或多个版本。通过使用外部程序集别名，可以将来自每个程序集的命名空间包装在由别名命名的根级别命名空间中，从而使这些命名空间可以在同一文件中使用。

说明：

extern 关键字还用作方法修饰符，声明用非托管代码编写的方法。

若要引用两个具有相同完全限定类型名的程序集，必须在命令提示符下指定别名，如下所示：

/r:GridV1=grid.dll

/r:GridV2=grid20.dll

这将创建外部别名 GridV1 和 GridV2。若要从程序中使用这些别名，请使用 extern 关键字引用它们。例如：

extern alias GridV1;

extern alias GridV2;

每一个外部别名声明都引入一个额外的根级别命名空间，它与全局命名空间平行，而不是在全局命名空间内。因此，通过使用根源于相应命名空间别名的完全限定名，可以无歧义地引用每个程序集的类型。

在上面的示例中，GridV1::Grid 是来自 grid.dll 的网格控件，而 GridV2::Grid 是来自 grid20.dll 的网格控件。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 9.3 Extern 别名

请参见

概念

C# 编程指南

参考

C# 关键字
命名空间关键字（C# 参考）
:: 运算符（C# 参考）
/reference（导入元数据）（C# 编译器选项）

其他资源

C# 参考


运算符关键字（C# 参考）

[image]

C# 语言参考
运算符关键字（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

用于执行杂项操作，例如创建对象，检查对象的运行时类型，获取类型的大小，等等。本节介绍下列关键字：

  • as 将对象转换为可兼容类型。

  • is 检查对象的运行时类型。

  • new

    • new 运算符 创建对象。

    • new 修饰符 隐藏继承成员。

    • new 约束 限定类型参数。

  • sizeof 获取类型的大小。

  • typeof 获取类型的 System.Type 对象。

  • true

    • true 运算符 返回布尔值 true 表示真，否则返回 false。

    • true 表示布尔值 true。

  • false

    • false 运算符 返回布尔值 true 表示假，否则返回 false。

    • false 表示布尔值 false。

  • stackalloc 在堆栈上分配内存块。

在语句一节中介绍了下列可用作运算符和语句的关键字：

  • checked 指定已检查的上下文。

  • unchecked 指定未检查的上下文。

请参见

概念

C# 编程指南

参考

C# 关键字
C# 运算符

其他资源

C# 参考


as（C# 参考）

[image]

C# 语言参考
as（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

as 运算符用于在兼容的引用类型之间执行某些类型的转换。例如：

C# 复制代码
   class csrefKeywordsOperators
   {
       class Base
       {
           public override string  ToString()
           {
                 return "Base";
           }
       }
       class Derived : Base
       { }

       class Program
       {
           static void Main()
           {

               Derived d = new Derived();

               Base b = d as Base;
               if (b != null)
               {
                   Console.WriteLine(b.ToString());
               }

           }
       }
   }

备注

as 运算符类似于强制转换操作。但是，如果无法进行转换，则 as 返回 null 而非引发异常。请看下面的表达式：

复制代码
expression as type

它等效于以下表达式，但只计算一次 expression。

复制代码
expression is type ? (type)expression : (type)null

注意，as 运算符只执行引用转换和装箱转换。as 运算符无法执行其他转换，如用户定义的转换，这类转换应使用强制转换表达式来执行。

示例

C# 复制代码
class ClassA { }
class ClassB { }

class MainClass
{
    static void Main()
    {
        object[] objArray = new object[6];
        objArray[0] = new ClassA();
        objArray[1] = new ClassB();
        objArray[2] = "hello";
        objArray[3] = 123;
        objArray[4] = 123.4;
        objArray[5] = null;

        for (int i = 0; i < objArray.Length; ++i)
        {
            string s = objArray[i] as string;
            Console.Write("{0}:", i);
            if (s != null)
            {
                Console.WriteLine("'" + s + "'");
            }
            else
            {
                Console.WriteLine("not a string");
            }
        }
    }
}
/*
Output:
0:not a string
1:not a string
2:'hello'
3:not a string
4:not a string
5:not a string
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 6 转换

  • 7.9.11 as 运算符

请参见

概念

C# 编程指南

参考

C# 关键字
is（C# 参考）
?: 运算符（C# 参考）
运算符关键字（C# 参考）

其他资源

C# 参考


is（C# 参考）

[image]

C# 语言参考
is（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

检查对象是否与给定类型兼容。例如，下面的代码可以确定对象是否为 MyObject 类型的一个实例，或者对象是否为从 MyObject 派生的一个类型：

复制代码
if (obj is MyObject)
{
}

如果所提供的表达式非空，并且所提供的对象可以强制转换为所提供的类型而不会导致引发异常，则 is 表达式的计算结果将是 true。

如果已知表达式将始终是 true 或始终是 false，则 is 关键字将导致编译时警告，但是，通常在运行时才计算类型兼容性。

不能重载 is 运算符。

请注意，is 运算符只考虑引用转换、装箱转换和取消装箱转换。不考虑其他转换，如用户定义的转换。

在 is 运算符的左侧不允许使用匿名方法。lambda 表达式属于例外。

示例

C# 复制代码
class Class1 {}
class Class2 {}
class Class3 : Class2 { }

class IsTest
{
    static void Test(object o)
    {
        Class1 a;
        Class2 b;

        if (o is Class1)
        {
            Console.WriteLine("o is Class1");
            a = (Class1)o;
            // Do something with "a."
        }
        else if (o is Class2)
        {
            Console.WriteLine("o is Class2");
            b = (Class2)o;
            // Do something with "b."
        }

        else
        {
            Console.WriteLine("o is neither Class1 nor Class2.");
        }
    }
    static void Main()
    {
        Class1 c1 = new Class1();
        Class2 c2 = new Class2();
        Class3 c3 = new Class3();
        Test(c1);
        Test(c2);
        Test(c3);
        Test("a string");
    }
}
/*
Output:
o is Class1
o is Class2
o is Class2
o is neither Class1 nor Class2.
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 7.6.6 强制转换表达式

  • 7.9.10 is 运算符

请参见

概念

C# 编程指南

参考

C# 关键字
typeof（C# 参考）
as（C# 参考）
运算符关键字（C# 参考）

其他资源

C# 参考


new（C# 参考）

[image]

C# 语言参考
new（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 中，new 关键字可用作运算符、修饰符或约束。

new 运算符

用于创建对象和调用构造函数。

new 修饰符

用于向基类成员隐藏继承成员。

new 约束

用于在泛型声明中约束可能用作类型参数的参数的类型。

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


new 运算符（C# 参考）

[image]

C# 语言参考
new 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

用于创建对象和调用构造函数。例如：

复制代码
Class1 obj  = new Class1();

还可用于创建匿名类型的实例：

复制代码
var query = from cust in customers
            select new {Name = cust.Name, Address = cust.PrimaryAddress};

new 运算符还用于调用值类型的默认构造函数。例如：

复制代码
int i = new int();

在上一个语句中，i 初始化为 0，它是 int 类型的默认值。该语句的效果等同于：

复制代码
int i = 0;

有关默认值的完整列表，请参见默认值表（C# 参考）。

请记住，为结构声明默认的构造函数是错误的，因为每一个值类型都隐式具有一个公共的默认构造函数。可以在结构类型上声明参数化构造函数以设置其初始值，但是，只有在需要默认值之外的值时才必须这样做。

值类型对象（例如结构）是在堆栈上创建的，而引用类型对象（例如类）是在堆上创建的。两种类型的对象都是自动销毁的，但是，基于值类型的对象是在超出范围时销毁，而基于引用类型的对象则是在对该对象的最后一个引用被移除之后在某个不确定的时间销毁。对于占用固定资源（例如大量内存、文件句柄或网络连接）的引用类型，有时需要使用确定性终止以确保对象被尽快销毁。有关更多信息，请参见 using 语句（C# 参考）。

不能重载 new 运算符。

如果 new 运算符分配内存失败，将引发异常 OutOfMemoryException。

示例

在下面的示例中，通过使用 new 运算符创建并初始化一个 struct 对象和一个类对象，然后为它们赋值。显示了默认值和所赋的值。

C# 复制代码
struct SampleStruct
{
   public int x;
   public int y;

   public SampleStruct(int x, int y)
   {
      this.x = x;
      this.y = y;
   }
}

class SampleClass
{
   public string name;
   public int id;

   public SampleClass() {}

   public SampleClass(int id, string name)
   {
      this.id = id;
      this.name = name;
   }
}

class ProgramClass
{
   static void Main()
   {
      // Create objects using default constructors:
      SampleStruct Location1 = new SampleStruct();
      SampleClass Employee1 = new SampleClass();

      // Display values:
      Console.WriteLine("Default values:");
      Console.WriteLine("   Struct members: {0}, {1}",
             Location1.x, Location1.y);
      Console.WriteLine("   Class members: {0}, {1}",
             Employee1.name, Employee1.id);

      // Create objects using parameterized constructors:
      SampleStruct Location2 = new SampleStruct(10, 20);
      SampleClass Employee2 = new SampleClass(1234, "Luciana Ramos");

      // Display values:
      Console.WriteLine("Assigned values:");
      Console.WriteLine("   Struct members: {0}, {1}",
             Location2.x, Location2.y);
      Console.WriteLine("   Class members: {0}, {1}",
             Employee2.name, Employee2.id);
   }
}
/*
Output:
Default values:
   Struct members: 0, 0
   Class members: , 0
Assigned values:
   Struct members: 10, 20
   Class members: Luciana Ramos, 1234
*/

注意，在示例中字符串的默认值为 null，因此未显示它。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 7.5.10 new 运算符

请参见

概念

C# 编程指南

参考

C# 关键字
运算符关键字（C# 参考）
new（C# 参考）
匿名类型（C# 编程指南）

其他资源

C# 参考


new 修饰符（C# 参考）

[image]

C# 语言参考
new 修饰符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在用作修饰符时，new 关键字可以显式隐藏从基类继承的成员。隐藏继承的成员时，该成员的派生版本将替换基类版本。虽然可以在不使用 new 修饰符的情况下隐藏成员，但会生成警告。如果使用 new 显式隐藏成员，则会取消此警告，并记录要替换为派生版本这一事实。

若要隐藏继承的成员，请使用相同名称在派生类中声明该成员，并使用 new 修饰符修饰该成员。例如：

C# 复制代码
public class BaseC
{
    public int x;
    public void Invoke() { }
}
public class DerivedC : BaseC
{
    new public void Invoke() { }
}

在此示例中，DerivedC.Invoke 隐藏了 BaseC.Invoke。字段 x 不受影响，因为它没有被类似名称的字段隐藏。

通过继承隐藏名称采用下列形式之一：

  • 引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。

  • 引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。

  • 引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。

对同一成员同时使用 new 和 override 是错误的做法，因为这两个修饰符的含义互斥。new 修饰符会用同样的名称创建一个新成员并使原始成员变为隐藏的。override 修饰符会扩展继承成员的实现。

在不隐藏继承成员的声明中使用 new 修饰符将会生成警告。

示例

在该例中，基类 BaseC 和派生类 DerivedC 使用相同的字段名 x，从而隐藏了继承字段的值。该示例演示了 new 修饰符的用法。另外还演示了如何使用完全限定名访问基类的隐藏成员。

C# 复制代码
public class BaseC
{
    public static int x = 55;
    public static int y = 22;
}

public class DerivedC : BaseC
{
    // Hide field 'x'.
    new public static int x = 100;

    static void Main()
    {
        // Display the new value of x:
        Console.WriteLine(x);

        // Display the hidden value of x:
        Console.WriteLine(BaseC.x);

        // Display the unhidden member y:
        Console.WriteLine(y);
    }
}
/*
Output:
100
55
22
*/

在此示例中，嵌套类隐藏了基类中同名的类。此示例演示了如何使用 new 修饰符来消除警告消息，以及如何使用完全限定名来访问隐藏的类成员。

C# 复制代码
public class BaseC
{
    public class NestedC
    {
        public int x = 200;
        public int y;
    }
}

public class DerivedC : BaseC
{
    // Nested type hiding the base type members.
    new public class NestedC
    {
        public int x = 100;
        public int y;
        public int z;
    }

    static void Main()
    {
        // Creating an object from the overlapping class:
        NestedC c1  = new NestedC();

        // Creating an object from the hidden class:
        BaseC.NestedC c2 = new BaseC.NestedC();

        Console.WriteLine(c1.x);
        Console.WriteLine(c2.x);
    }
}
/*
Output:
100
200
*/

如果移除 new 修饰符，该程序仍可编译和运行，但您会收到以下警告：

复制代码
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.

如果嵌套类型隐藏了另一个类型，也可以使用 new 修饰符来修改该嵌套类型，如下面的示例所示。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.3.4 new 修饰符

请参见

概念

C# 编程指南

参考

C# 关键字
运算符关键字（C# 参考）
修饰符（C# 参考）

其他资源

C# 参考


new 约束（C# 参考）

[image]

C# 语言参考
new 约束（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

new 约束指定泛型类声明中的任何类型参数都必须有公共的无参数构造函数。如果要使用 new 约束，则该类型不能为抽象类型。

示例

当泛型类创建类型的新实例，请将 new 约束应用于类型参数，如下面的示例所示：

C# 复制代码
class ItemFactory<T> where T : new()
{
    public T GetNewItem()
    {
        return new T();
    }
}

当与其他约束一起使用时，new() 约束必须最后指定：

C# 复制代码
public class ItemFactory2<T>
    where T : IComparable, new()
{
}

有关更多信息，请参见类型参数的约束（C# 编程指南）。

C# 语言规范

有关更多信息，请参见 C# 语言规范。

  • 4.4.3 满足约束

  • 10.1.6 类型参数约束

请参见

概念

C# 编程指南

参考

C# 关键字
运算符关键字（C# 参考）
泛型（C# 编程指南）
System.Collections.Generic

其他资源

C# 参考


sizeof（C# 参考）

[image]

C# 语言参考
sizeof（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

用于获取 值类型 的字节大小。例如，可以如下所示检索 int 类型的大小：

复制代码
int intSize = sizeof(int);

备注

sizeof 运算符仅适用于值类型，而不适用于引用类型。

说明：

从 C# 2.0 版开始，将 sizeof 应用于基元类型不再要求使用不安全模式。

不能重载 sizeof 运算符。由 sizeof 运算符返回的值是 int 类型。下表显示了表示某些基元类型大小的常量值。

表达式 | 结果
sizeof(sbyte) | 1
sizeof(byte) | 1
sizeof(short) | 2
sizeof(ushort) | 2
sizeof(int) | 4
sizeof(uint) | 4
sizeof(long) | 8
sizeof(ulong) | 8
sizeof(char) | 2 (Unicode)
sizeof(float) | 4
sizeof(double) | 8
sizeof(bool) | 1

对于所有其他类型（包括 struct），sizeof 运算符只能在不安全代码块中使用。虽然可以使用 Marshal..::.SizeOf 方法，但该方法返回的值和 sizeof 返回的值并不总是相同的。Marshal..::.SizeOf 在已封送处理类型后返回大小，而 sizeof 返回公共语言运行时分配的大小（包括任何空白）。

示例

C# 复制代码
class MainClass
{
    // unsafe not required for primitive types
    static void Main()
    {
        Console.WriteLine("The size of short is {0}.", sizeof(short));
        Console.WriteLine("The size of int is {0}.", sizeof(int));
        Console.WriteLine("The size of long is {0}.", sizeof(long));
    }
}
/*
Output:
    The size of short is 2.
    The size of int is 4.
    The size of long is 8.
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 18.5.8 sizeof 运算符

请参见

概念

C# 编程指南

参考

C# 关键字
运算符关键字（C# 参考）

其他资源

C# 参考


typeof（C# 参考）

[image]

C# 语言参考
typeof（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

用于获取类型的 System.Type 对象。typeof 表达式采用以下形式：

复制代码
System.Type type = typeof(int);

备注

若要获取表达式的运行时类型，可以使用 .NET Framework 方法 GetType，如以下示例中所示：

复制代码
int i = 0;
System.Type type = i.GetType();

不能重载 typeof 运算符。

typeof 运算符也能用于公开的泛型类型。具有不止一个类型参数的类型的规范中必须有适当数量的逗号。下面的示例演示如何确定方法的返回类型是否是泛型 IEnumerable<(Of <(T>)>)。假定此方法是 MethodInfo 类型的实例：

复制代码
string s = method.ReturnType.GetInterface
    (typeof(System.Collections.Generic.IEnumerable<>).FullName

示例

C# 复制代码
public class SampleClass2
{
   public int sampleMember;
   public void SampleMethod() {}

   static void Main()
   {
      Type t = typeof(SampleClass);
      // Alternatively, you could use
      // SampleClass obj = new SampleClass();
      // Type t = obj.GetType();

      Console.WriteLine("Methods:");
      System.Reflection.MethodInfo[] methodInfo = t.GetMethods();

      foreach (System.Reflection.MethodInfo mInfo in methodInfo)
         Console.WriteLine(mInfo.ToString());

      Console.WriteLine("Members:");
      System.Reflection.MemberInfo[] memberInfo = t.GetMembers();

      foreach (System.Reflection.MemberInfo mInfo in memberInfo)
         Console.WriteLine(mInfo.ToString());
   }
}
/*
 Output:
    Methods:
    System.Type GetType()
    System.String ToString()
    Boolean Equals(System.Object)
    Int32 GetHashCode()
    Members:
    System.Type GetType()
    System.String ToString()
    Boolean Equals(System.Object)
    Int32 GetHashCode()
    Void .ctor()
    Void .ctor(Int32, System.String)
    System.String name
    Int32 id
*/

此示例使用 GetType 方法确定用来包含数值计算的结果的类型。这取决于结果数字的存储要求。

C# 复制代码
class GetTypeTest
{
    static void Main()
    {
        int radius = 3;
        Console.WriteLine("Area = {0}", radius * radius * Math.PI);
        Console.WriteLine("The type is {0}",
                          (radius * radius * Math.PI).GetType()
        );
    }
}
/*
Output:
Area = 28.2743338823081
The type is System.Double
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 7.5.11 typeof 运算符

请参见

概念

C# 编程指南

参考

C# 关键字
is（C# 参考）
运算符关键字（C# 参考）

其他资源

C# 参考


true（C# 参考）

[image]

C# 语言参考
true（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

用作重载运算符或文本：

true 运算符

true 标识符

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


true 运算符（C# 参考）

[image]

C# 语言参考
true 运算符（C# 参考）
另请参见 发送反馈意见

更新： 2008 年 7 月

返回布尔值 true 以指示操作数为 true，否则返回 false。

在 C# 2.0 以前，true 和 false 运算符一直用来创建用户定义的可以为 null 的值类型，这些值类型与 SqlBool 等类型兼容。然而，现在该语言提供对可为 null 的值类型的内置支持，并且应该尽可能地使用它们而不是重载 true 和 false 运算符。有关更多信息，请参见可空类型（C# 编程指南）。

对于可以为 null 的布尔值，表达式 a != b 不一定等同于 !(a == b)，因为两个值之一或者全部都有可能为 null。需要单独重载 true 和 false 运算符，以便正确标识表达式中的 null 值。下面的示例演示如何重载及使用 true 和 false 运算符。

C# 复制代码
// For example purposes only. Use the built-in nullable bool
// type (bool?) whenever possible.
public struct DBBool
{
    // The three possible DBBool values.
    public static readonly DBBool Null = new DBBool(0);
    public static readonly DBBool False = new DBBool(-1);
    public static readonly DBBool True = new DBBool(1);
    // Private field that stores –1, 0, 1 for False, Null, True.
    sbyte value;
    // Private instance constructor. The value parameter must be –1, 0, or 1.
    DBBool(int value)
    {
        this.value = (sbyte)value;
    }
    // Properties to examine the value of a DBBool. Return true if this
    // DBBool has the given value, false otherwise.
    public bool IsNull { get { return value == 0; } }
    public bool IsFalse { get { return value < 0; } }
    public bool IsTrue { get { return value > 0; } }
    // Implicit conversion from bool to DBBool. Maps true to DBBool.True and
    // false to DBBool.False.
    public static implicit operator DBBool(bool x)
    {
        return x ? True : False;
    }
    // Explicit conversion from DBBool to bool. Throws an exception if the
    // given DBBool is Null; otherwise returns true or false.
    public static explicit operator bool(DBBool x)
    {
        if (x.value == 0) throw new InvalidOperationException();
        return x.value > 0;
    }
    // Equality operator. Returns Null if either operand is Null; otherwise
    // returns True or False.
    public static DBBool operator ==(DBBool x, DBBool y)
    {
        if (x.value == 0 || y.value == 0) return Null;
        return x.value == y.value ? True : False;
    }
    // Inequality operator. Returns Null if either operand is Null; otherwise
    // returns True or False.
    public static DBBool operator !=(DBBool x, DBBool y)
    {
        if (x.value == 0 || y.value == 0) return Null;
        return x.value != y.value ? True : False;
    }
    // Logical negation operator. Returns True if the operand is False, Null
    // if the operand is Null, or False if the operand is True.
    public static DBBool operator !(DBBool x)
    {
        return new DBBool(-x.value);
    }
    // Logical AND operator. Returns False if either operand is False,
    // Null if either operand is Null, otherwise True.
    public static DBBool operator &(DBBool x, DBBool y)
    {
        return new DBBool(x.value < y.value ? x.value : y.value);
    }
    // Logical OR operator. Returns True if either operand is True,
    // Null if either operand is Null, otherwise False.
    public static DBBool operator |(DBBool x, DBBool y)
    {
        return new DBBool(x.value > y.value ? x.value : y.value);
    }
    // Definitely true operator. Returns true if the operand is True, false
    // otherwise.
    public static bool operator true(DBBool x)
    {
        return x.value > 0;
    }
    // Definitely false operator. Returns true if the operand is False, false
    // otherwise.
    public static bool operator false(DBBool x)
    {
        return x.value < 0;
    }
    public override bool Equals(object obj)
    {
        if (!(obj is DBBool)) return false;
        return value == ((DBBool)obj).value;
    }
    public override int GetHashCode()
    {
        return value;
    }
    public override string ToString()
    {
        if (value > 0) return "DBBool.True";
        if (value < 0) return "DBBool.False";
        return "DBBool.Null";
    }
}

重载 true 和 false 运算符的类型可以用于 if、do、while 和 for 语句以及条件表达式中的控制表达式。

如果类型定义了 true 运算符，它还必须定义 false 运算符。

类型不能直接重载条件逻辑运算符（&& 和 ||），但通过重载规则逻辑运算符和 true 与 false 运算符可以达到同样的效果。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各节：

  • 7.6 一元运算符

  • 7.11.2 用户定义的条件逻辑运算符

  • 7.19 布尔表达式

请参见

概念

C# 编程指南

参考

C# 关键字
C# 运算符
false（C# 参考）

其他资源

C# 参考

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 添加了有关重载 true 和 false 运算符的信息。 | 信息补充。


true 字面常数（C# 参考）

[image]

C# 语言参考
true 字面常数（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

表示布尔值 true。

示例

C# 复制代码
class TrueTest
{
    static void Main()
    {
        bool a = true;
        Console.WriteLine( a ? "yes" : "no" );
    }
}
/*
Output:
yes
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 2.4.4.1 布尔文本

请参见

概念

C# 编程指南

参考

C# 关键字
false（C# 参考）

其他资源

C# 参考


false（C# 参考）

[image]

C# 语言参考
false（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

用作重载运算符或文本：

  • false 运算符

  • false 标识符

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


false 运算符（C# 参考）

[image]

C# 语言参考
false 运算符（C# 参考）
另请参见 发送反馈意见

更新： 2008 年 7 月

返回布尔值 true 以指示操作数为 false，否则返回 false。

在 C# 2.0 以前，true 和 false 运算符一直用来创建用户定义的可以为 null 的值类型，这些值类型与 SqlBool 等类型兼容。然而，现在该语言提供对可为 null 的值类型的内置支持，并且应该尽可能地使用它们而不是重载 true 和 false 运算符。有关更多信息，请参见可空类型（C# 编程指南）。

对于可以为 null 的布尔值，表达式 a != b 不一定等同于 !(a == b)，因为两个值之一或者全部都有可能为 null。必须单独重载 true 和 false 运算符，以便正确处理表达式中的 null 值。下面的示例演示如何重载及使用 true 和 false 运算符。

C# 复制代码
// For example purposes only. Use the built-in nullable bool
// type (bool?) whenever possible.
public struct DBBool
{
    // The three possible DBBool values.
    public static readonly DBBool Null = new DBBool(0);
    public static readonly DBBool False = new DBBool(-1);
    public static readonly DBBool True = new DBBool(1);
    // Private field that stores –1, 0, 1 for False, Null, True.
    sbyte value;
    // Private instance constructor. The value parameter must be –1, 0, or 1.
    DBBool(int value)
    {
        this.value = (sbyte)value;
    }
    // Properties to examine the value of a DBBool. Return true if this
    // DBBool has the given value, false otherwise.
    public bool IsNull { get { return value == 0; } }
    public bool IsFalse { get { return value < 0; } }
    public bool IsTrue { get { return value > 0; } }
    // Implicit conversion from bool to DBBool. Maps true to DBBool.True and
    // false to DBBool.False.
    public static implicit operator DBBool(bool x)
    {
        return x ? True : False;
    }
    // Explicit conversion from DBBool to bool. Throws an exception if the
    // given DBBool is Null; otherwise returns true or false.
    public static explicit operator bool(DBBool x)
    {
        if (x.value == 0) throw new InvalidOperationException();
        return x.value > 0;
    }
    // Equality operator. Returns Null if either operand is Null; otherwise
    // returns True or False.
    public static DBBool operator ==(DBBool x, DBBool y)
    {
        if (x.value == 0 || y.value == 0) return Null;
        return x.value == y.value ? True : False;
    }
    // Inequality operator. Returns Null if either operand is Null; otherwise
    // returns True or False.
    public static DBBool operator !=(DBBool x, DBBool y)
    {
        if (x.value == 0 || y.value == 0) return Null;
        return x.value != y.value ? True : False;
    }
    // Logical negation operator. Returns True if the operand is False, Null
    // if the operand is Null, or False if the operand is True.
    public static DBBool operator !(DBBool x)
    {
        return new DBBool(-x.value);
    }
    // Logical AND operator. Returns False if either operand is False,
    // Null if either operand is Null, otherwise True.
    public static DBBool operator &(DBBool x, DBBool y)
    {
        return new DBBool(x.value < y.value ? x.value : y.value);
    }
    // Logical OR operator. Returns True if either operand is True,
    // Null if either operand is Null, otherwise False.
    public static DBBool operator |(DBBool x, DBBool y)
    {
        return new DBBool(x.value > y.value ? x.value : y.value);
    }
    // Definitely true operator. Returns true if the operand is True, false
    // otherwise.
    public static bool operator true(DBBool x)
    {
        return x.value > 0;
    }
    // Definitely false operator. Returns true if the operand is False, false
    // otherwise.
    public static bool operator false(DBBool x)
    {
        return x.value < 0;
    }
    public override bool Equals(object obj)
    {
        if (!(obj is DBBool)) return false;
        return value == ((DBBool)obj).value;
    }
    public override int GetHashCode()
    {
        return value;
    }
    public override string ToString()
    {
        if (value > 0) return "DBBool.True";
        if (value < 0) return "DBBool.False";
        return "DBBool.Null";
    }
}

重载 true 和 false 运算符的类型可以用于 if、do、while 和 for 语句以及条件表达式中的控制表达式。

如果类型定义了 false 运算符，则它还必须定义 true 运算符。

类型不能直接重载条件逻辑运算符 && 和 ||，但通过重载正则逻辑运算符以及运算符 true 与 false 可以达到同样的效果。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各节：

  • 10.10.1 一元运算符

  • 7.11.2 用户定义的条件逻辑运算符

  • 7.19 布尔表达式

请参见

概念

C# 编程指南

参考

C# 关键字
C# 运算符
true（C# 参考）

其他资源

C# 参考

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 添加了有关重载 true 和 false 运算符的信息。 | 信息补充。


false 字面常数（C# 参考）

[image]

C# 语言参考
false 字面常数（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

表示布尔值 false。

示例

C# 复制代码
class TestClass
{
    static void Main()
    {
        bool a = false;
        Console.WriteLine( a ? "yes" : "no" );
    }
}
// Output: no

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 2.4.4.1 布尔文本

请参见

概念

C# 编程指南

参考

C# 关键字
true（C# 参考）

其他资源

C# 参考


stackalloc（C# 参考）

[image]

C# 语言参考
stackalloc（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

stackalloc 关键字用于不安全的代码上下文中，以便在堆栈上分配内存块。

复制代码
int* fib = stackalloc int[100];

备注

下面的示例输出斐波那契数列的 100 个数字，其中每个数字都是前两个数字之和。在代码中，大小足够容纳 100 个 int 类型元素的内存块是在堆栈上分配的，而不是在堆上分配的。该块的地址存储在 fib 指针中。此内存不受垃圾回收的制约，因此不必将其钉住（通过使用 fixed）。内存块的生存期受限于定义它的方法的生存期。不能在方法返回之前释放内存。

stackalloc 仅在局部变量的初始值设定项中有效。

由于涉及指针类型，因此 stackalloc 要求不安全上下文。有关更多信息，请参见 不安全代码和指针（C# 编程指南）。

stackalloc 类似于 C 运行时中的 _alloca。

安全性

不安全代码的安全性低于安全替代代码。但是，通过使用 stackalloc 可以自动启用公共语言运行时 (CLR) 中的缓冲区溢出检测功能。如果检测到缓冲区溢出，进程将尽快终止，以最大限度地减小执行恶意代码的机会。

示例

C# 复制代码
class Test
{
    static unsafe void Main()
    {
        const int arraySize = 20;
        int* fib = stackalloc int[arraySize];
        int* p = fib;
        *p++ = 1;
        for (int i = 2; i < arraySize; ++i, ++p)
        {
            // Sum the previous two numbers.
            *p = p[-1] + p[-2];
        }
        for (int i = 0; i < arraySize - 1; ++i)
        {
            Console.WriteLine(fib[i]);
        }

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/*
Output
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 18.8 堆栈分配

请参见

概念

C# 编程指南

参考

C# 关键字
运算符关键字（C# 参考）
不安全代码和指针（C# 编程指南）

其他资源

C# 参考


转换关键字（C# 参考）

[image]

C# 语言参考
转换关键字（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

本节描述在类型转换中使用的关键字：

  • explicit

  • implicit

  • operator

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


explicit（C# 参考）

[image]

C# 语言参考
explicit（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

explicit 关键字用于声明必须使用强制转换来调用的用户定义的类型转换运算符。例如，在下面的示例中，此运算符将名为 Fahrenheit 的类转换为名为 Celsius 的类：

C# 复制代码
// Must be defined inside a class called Farenheit:
public static explicit operator Celsius(Fahrenheit f)
{
    return new Celsius((5.0f / 9.0f) * (f.degrees - 32));
}

可以如下所示调用此转换运算符：

C# 复制代码
Fahrenheit f = new Fahrenheit(100.0f);
Console.Write("{0} fahrenheit", f.Degrees);
Celsius c = (Celsius)f;

转换运算符将源类型转换为目标类型。源类型提供转换运算符。与隐式转换不同，必须通过强制转换的方式来调用显式转换运算符。如果转换操作可能导致异常或丢失信息，则应将其标记为 explicit。这可以防止编译器无提示地调用可能产生无法预见后果的转换操作。

省略此强制转换将导致编译时错误 编译器错误 CS0266。

有关更多信息，请参见 使用转换运算符（C# 编程指南）。

示例

下面的示例提供 Fahrenheit 和 Celsius 类，它们中的每一个都为另一个提供显式转换运算符。

C# 复制代码
class Celsius
{
    public Celsius(float temp)
    {
        degrees = temp;
    }
    public static explicit operator Fahrenheit(Celsius c)
    {
        return new Fahrenheit((9.0f / 5.0f) * c.degrees + 32);
    }
    public float Degrees
    {
        get { return degrees; }
    }
    private float degrees;
}

class Fahrenheit
{
    public Fahrenheit(float temp)
    {
        degrees = temp;
    }
    // Must be defined inside a class called Farenheit:
    public static explicit operator Celsius(Fahrenheit f)
    {
        return new Celsius((5.0f / 9.0f) * (f.degrees - 32));
    }
    public float Degrees
    {
        get { return degrees; }
    }
    private float degrees;
}

class MainClass
{
    static void Main()
    {
        Fahrenheit f = new Fahrenheit(100.0f);
        Console.Write("{0} fahrenheit", f.Degrees);
        Celsius c = (Celsius)f;

        Console.Write(" = {0} celsius", c.Degrees);
        Fahrenheit f2 = (Fahrenheit)c;
        Console.WriteLine(" = {0} fahrenheit", f2.Degrees);
    }
}
/*
Output:
100 fahrenheit = 37.77778 celsius = 100 fahrenheit
*/

下面的示例定义一个结构 Digit，该结构表示单个十进制数字。定义了一个运算符，用于将 byte 转换为 Digit，但因为并非所有字节都可以转换为 Digit，所以该转换是显式的。

C# 复制代码
struct Digit
{
    byte value;
    public Digit(byte value)
    {
        if (value > 9)
        {
            throw new ArgumentException();
        }
        this.value = value;
    }

    // Define explicit byte-to-Digit conversion operator:
    public static explicit operator Digit(byte b)
    {
        Digit d = new Digit(b);
        Console.WriteLine("conversion occurred");
        return d;
    }
}

class ExplicitTest
{
    static void Main()
    {
        try
        {
            byte b = 3;
            Digit d = (Digit)b; // explicit conversion
        }
        catch (Exception e)
        {
            Console.WriteLine("{0} Exception caught.", e);
        }
    }
}
/*
Output:
conversion occurred
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 6.2 显式转换

请参见

任务

如何：在结构间实现用户定义的转换（C# 编程指南）

概念

C# 编程指南

参考

C# 关键字
implicit（C# 参考）
operator（C# 参考）

其他资源

C# 参考
Chained user-defined explicit conversions in C#（C# 中关联的用户定义的显式转换）


implicit（C# 参考）

[image]

C# 语言参考
implicit（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

implicit 关键字用于声明隐式的用户定义类型转换运算符。如果可以确保转换过程不会造成数据丢失，则可使用该关键字在用户定义类型和其他类型之间进行隐式转换。

示例

C# 复制代码
    class Digit
    {
        public Digit(double d) { val = d; }
        public double val;
        // ...other members

        // User-defined conversion from Digit to double
        public static implicit operator double(Digit d)
        {
            return d.val;
        }
        //  User-defined conversion from double to Digit
        public static implicit operator Digit(double d)
        {
            return new Digit(d);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Digit dig = new Digit(7);
            //This call invokes the implicit "double" operator
            double num = dig;
            //This call invokes the implicit "Digit" operator
            Digit dig2 = 12;
            Console.WriteLine("num = {0} dig2 = {1}", num, dig2.val);
            Console.ReadLine();
        }
    }
}

隐式转换可以通过消除不必要的类型转换来提高源代码的可读性。但是，因为隐式转换不需要程序员将一种类型显式强制转换为另一种类型，所以使用隐式转换时必须格外小心，以免出现意外结果。一般情况下，隐式转换运算符应当从不引发异常并且从不丢失信息，以便可以在程序员不知晓的情况下安全使用它们。如果转换运算符不能满足那些条件，则应将其标记为 explicit。有关更多信息，请参见使用转换运算符。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 6.1 隐式转换

  • 10.9.3 转换运算符

请参见

任务

如何：在结构间实现用户定义的转换（C# 编程指南）

概念

C# 编程指南

参考

C# 关键字
explicit（C# 参考）
operator（C# 参考）

其他资源

C# 参考


operator（C# 参考）

[image]

C# 语言参考
operator（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

使用 operator 关键字来重载内置运算符，或提供类或结构声明中的用户定义转换。

示例

下面是一个非常简化的表示分数的类。该类重载了 + 和 * 运算符，以执行分数加法和乘法；同时提供了将 Fraction 类型转换为 double 类型的转换运算符。

C# 复制代码
class Fraction
{
    int num, den;
    public Fraction(int num, int den)
    {
        this.num = num;
        this.den = den;
    }

    // overload operator +
    public static Fraction operator +(Fraction a, Fraction b)
    {
        return new Fraction(a.num * b.den + b.num * a.den,
           a.den * b.den);
    }

    // overload operator *
    public static Fraction operator *(Fraction a, Fraction b)
    {
        return new Fraction(a.num * b.num, a.den * b.den);
    }

    // user-defined conversion from Fraction to double
    public static implicit operator double(Fraction f)
    {
        return (double)f.num / f.den;
    }
}

class Test
{
    static void Main()
    {
        Fraction a = new Fraction(1, 2);
        Fraction b = new Fraction(3, 7);
        Fraction c = new Fraction(2, 3);
        Console.WriteLine((double)(a * b + c));
    }
}
/*
Output
0.880952380952381
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 7.2.2 运算符重载

  • 7.2.3 一元运算符重载决策

  • 7.2.4 二元运算符重载决策

请参见

任务

如何：在结构间实现用户定义的转换（C# 编程指南）

概念

C# 编程指南

参考

C# 关键字
implicit（C# 参考）
explicit（C# 参考）

其他资源

C# 参考


访问关键字（C# 参考）

[image]

C# 语言参考
访问关键字（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

本节介绍下列访问关键字：

  • base

    访问基类的成员。

  • this

    引用类的当前实例。

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


base（C# 参考）

[image]

C# 语言参考
base（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

base 关键字用于从派生类中访问基类的成员：

  • 调用基类上已被其他方法重写的方法。

  • 指定创建派生类实例时应调用的基类构造函数。

基类访问只能在构造函数、实例方法或实例属性访问器中进行。

从静态方法中使用 base 关键字是错误的。

示例

在本例中，基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。通过使用 base 关键字，可以从派生类中调用基类的 Getinfo 方法。

C# 复制代码
public class Person
{
    protected string ssn = "444-55-6666";
    protected string name = "John L. Malgraine";

    public virtual void GetInfo()
    {
        Console.WriteLine("Name: {0}", name);
        Console.WriteLine("SSN: {0}", ssn);
    }
}
class Employee : Person
{
    public string id = "ABC567EFG";
    public override void GetInfo()
    {
        // Calling the base class GetInfo method:
        base.GetInfo();
        Console.WriteLine("Employee ID: {0}", id);
    }
}

class TestClass
{
    static void Main()
    {
        Employee E = new Employee();
        E.GetInfo();
    }
}
/*
Output
Name: John L. Malgraine
SSN: 444-55-6666
Employee ID: ABC567EFG
*/

有关其他示例，请参见 new、virtual 和 override。

本示例显示如何指定在创建派生类实例时调用的基类构造函数。

C# 复制代码
public class BaseClass
{
    int num;

    public BaseClass()
    {
        Console.WriteLine("in BaseClass()");
    }

    public BaseClass(int i)
    {
        num = i;
        Console.WriteLine("in BaseClass(int i)");
    }

    public int GetNum()
    {
        return num;
    }
}

public class DerivedClass : BaseClass
{
    // This constructor will call BaseClass.BaseClass()
    public DerivedClass() : base()
    {
    }

    // This constructor will call BaseClass.BaseClass(int i)
    public DerivedClass(int i) : base(i)
    {
    }

    static void Main()
    {
        DerivedClass md = new DerivedClass();
        DerivedClass md1 = new DerivedClass(1);
    }
}
/*
Output:
in BaseClass()
in BaseClass(int i)
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.3 基类

  • 7.5.8 基访问

请参见

概念

C# 编程指南

参考

C# 关键字
this（C# 参考）

其他资源

C# 参考


this（C# 参考）

[image]

C# 语言参考
this（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

this 关键字引用类的当前实例，还可用作扩展方法的第一个参数的修饰符。

说明：

本文讨论对类实例使用 this。有关其在扩展方法中使用的更多信息，请参见扩展方法（C# 编程指南）。

以下是 this 的常用用途：

  • 限定被相似的名称隐藏的成员，例如：

C# 复制代码
public Employee(string name, string alias)
{
    // Use this to qualify the fields, name and alias:
    this.name = name;
    this.alias = alias;
}

  • 将对象作为参数传递到其他方法，例如：

    复制代码
    CalcTax(this);

  • 声明索引器，例如：

C# 复制代码
public int this[int param]
{
    get { return array[param]; }
    set { array[param] = value; }
}

由于静态成员函数存在于类一级，并且不是对象的一部分，因此没有 this 指针。在静态方法中引用 this 是错误的。

示例

在本例中，this 用于限定 Employee 类成员 name 和 alias，它们都被相似的名称隐藏。this 还用于将对象传递到属于其他类的方法 CalcTax。

C# 复制代码
class Employee
{
    private string name;
    private string alias;
    private decimal salary = 3000.00m;

    // Constructor:
    public Employee(string name, string alias)
    {
        // Use this to qualify the fields, name and alias:
        this.name = name;
        this.alias = alias;
    }
    // Printing method:
    public void printEmployee()
    {
        Console.WriteLine("Name: {0}\nAlias: {1}", name, alias);
        // Passing the object to the CalcTax method by using this:
        Console.WriteLine("Taxes: {0:C}", Tax.CalcTax(this));
    }

    public decimal Salary
    {
        get { return salary; }
    }
}

class Tax
{
    public static decimal CalcTax(Employee E)
    {
        return 0.08m * E.Salary;
    }
}

class MainClass
{
    static void Main()
    {
        // Create objects:
        Employee E1 = new Employee("Mingda Pan", "mpan");

        // Display results:
        E1.printEmployee();
    }
}
/*
Output:
    Name: Mingda Pan
    Alias: mpan
    Taxes: $240.00
 */

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 7.5.7 this 访问

  • 10.3.8.4 this 访问

请参见

概念

C# 编程指南

参考

C# 关键字
base（C# 参考）
方法（C# 编程指南）

其他资源

C# 参考


文字关键字（C# 参考）

[image]

C# 语言参考
文字关键字（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 有下列文字关键字：

  • null

  • true

  • false

  • default

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


null（C# 参考）

[image]

C# 语言参考
null（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

null 关键字是表示不引用任何对象的 null 引用的文字值。null 是引用类型变量的默认值。普通值类型不能为 null。但是，C# 2.0 引入了可以为 null 值的类型。请参见可空类型（C# 编程指南）。

下面的示例演示 null 关键字的一些行为：

C# 复制代码
class Program
{
    class MyClass
    {
        public void MyMethod() { }
    }

    static void Main(string[] args)
    {
        // Set a breakpoint here to see that mc = null.
        // However, the compiler considers it "unassigned."
        // and generates a compiler error if you try to
        // use the variable.
        MyClass mc;

        // Now the variable can be used, but...
        mc = null;

        // ... a method call on a null object raises
        // a run-time NullReferenceException.
        // Uncomment the following line to see for yourself.
        // mc.MyMethod();

        // Now mc has a value.
        mc = new MyClass();

        // You can call its method.
        mc.MyMethod();

        // Set mc to null again. The object it referenced
        // is no longer accsessible and can now be garbage-collected.
        mc = null;

        // A null string is not the same as an empty string.
        string s = null;
        string t = String.Empty; // Logically the same as ""

        // Equals applied to any null object returns false.
        bool b = (t.Equals(s));
        Console.WriteLine(b);

        // Equality operator also returns false when one
        // operand is null.
        Console.WriteLine("Empty string {0} null string", s == t ? "equals": "does not equal");

        // Returns true.
        Console.WriteLine("null == null is {0}", null == null);

        // A value type cannot be null
        // int i = null; // Compiler error!

        // Use a nullable value type instead:
        int? i = null;

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();

    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 2.4.4.6 null 文本

请参见

概念

C# 编程指南

参考

C# 关键字
文字关键字（C# 参考）

其他资源

C# 参考
默认值表（C# 参考）

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 添加了代码示例。 | 内容 Bug 修复


default（C# 参考）

[image]

C# 语言参考
default（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

default 关键字。

default 关键字可在 switch 语句或泛型代码中使用。

  • switch 语句：指定默认标签。

  • 泛型代码：指定类型参数的默认值。这对于引用类型为 null，对于值类型为零。

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


上下文关键字（C# 参考）

[image]

C# 语言参考
上下文关键字（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

上下文关键字用于提供代码中的特定含义，但它不是 C# 中的保留字。本节介绍下面这些上下文关键字：

add

get 为属性或索引器定义访问器方法。

global

partial 在整个同一编译单元内定义分部类、结构和接口。

remove

set 为属性或索引器定义访问器方法。

where 向泛型声明中添加约束。（另请参见 where）。

yield 在迭代器块中使用，用于向枚举数对象返回值或发信号结束迭代。

value 用于设置访问器和添加或移除事件处理程序。

C# 3.0 中引入的所有查询关键字也都是上下文相关的。有关更多信息，请参见查询关键字（C# 参考）。

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


get（C# 参考）

[image]

C# 语言参考
get（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

get 关键字在属性或索引器中定义访问器方法，以检索该属性或该索引器元素的值。有关更多信息，请参见 属性（C# 编程指南）、自动实现的属性（C# 编程指南） 和 索引器（C# 编程指南）。

下面的示例是名为 Seconds 的属性中的 get 访问器：

C# 复制代码
class TimePeriod
{
    private double _seconds;
    public double Seconds
    {
        get { return _seconds; }
        set { _seconds = value; }
    }

}

下面的示例是自动实现的属性中的 get 访问器：

C# 复制代码
class TimePeriod2
{
    public double Hours { get; set; }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.2 属性

  • 10.7.2 访问器

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


分部（类型）（C# 参考）

[image]

C# 语言参考
分部（类型）（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

分部类型定义允许将类、结构或接口的定义拆分到多个文件中。

在 File1.cs 中：

C# 复制代码
namespace PC
{
    partial class A
    {
        int num = 0;
        void MethodA() { }
        partial void MethodC();
    }
}

在 File2.cs 中，声明：

C# 复制代码
namespace PC
{
    partial class A
    {
        void MethodB(){}
        partial void MethodC() { }
    }
}

备注

当使用大项目或自动生成的代码（如由 Windows 窗体设计器提供的代码）时，将一个类、结构或接口类型拆分到多个文件中的做法就很有用。分部类型可能包含分部方法。有关更多信息，请参见 分部类和方法（C# 编程指南）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 10.2 分部类型

请参见

概念

C# 编程指南

参考

修饰符（C# 参考）
泛型介绍（C# 编程指南）

其他资源

C# 参考


分部（方法）

[image]

C# 语言参考
分部（方法）
另请参见 发送反馈意见

更新：2007 年 11 月

分部方法在分部类型的一个部分中定义它的签名，并在该类型的另外一个部分中定义它的实现。类设计人员可以使用分部方法提供由开发人员决定是否实现的方法挂钩（类似于事件处理程序）。如果开发人员没有提供实现，则编译器会在编译时移除签名。下列条件适用于分部方法：

  • 分部类型的两个部分中的签名必须匹配。

  • 方法必须返回 void。

  • 不允许使用访问修饰符或属性。分部方法是隐式私有的。

下面的示例演示在分部类的两个部分中定义的分部方法：

C# 复制代码
namespace PM
{
    partial class A
    {
        partial void OnSomethingHappened(string s);
    }

    // This part can be in a separate file.
    partial class A
    {
        // Comment out this method and the program
        // will still compile.
        partial void OnSomethingHappened(String s)
        {
            Console.WriteLine("Something happened: {0}", s);
        }
    }
}

有关更多信息，请参见分部类和方法（C# 编程指南）。

请参见

参考

分部（类型）（C# 参考）

其他资源

C# 参考


set（C# 参考）

[image]

C# 语言参考
set（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

set 关键字在属性或索引器中定义为该属性或索引器元素赋值的“访问器”方法。有关更多信息，请参见属性（C# 编程指南）、自动实现的属性（C# 编程指南）和索引器（C# 编程指南）。

此例是名为 Seconds 的属性的 set 访问器：

C# 复制代码
class TimePeriod
{
    private double _seconds;
    public double Seconds
    {
        get { return _seconds; }
        set { _seconds = value; }
    }

}

下面的示例是自动实现的属性中的 set 访问器：

C# 复制代码
class TimePeriod2
{
    public double Hours { get; set; }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.2 属性

  • 10.7.2 访问器

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


value（C# 参考）

[image]

C# 语言参考
value（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

隐式参数 value 用于设置访问器以及添加或移除事件处理程序。

有关使用 value 的更多信息，请参见 event（C# 参考）和非对称访问器可访问性（C# 编程指南）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.2 属性

  • 10.7.2 访问器

请参见

概念

C# 编程指南

参考

C# 关键字

其他资源

C# 参考


yield（C# 参考）

[image]

C# 语言参考
yield（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。它的形式为下列之一：

复制代码
yield return <expression>;
yield break;

备注

计算表达式并以枚举数对象值的形式返回；expression 必须可以隐式转换为迭代器的 yield 类型。

yield 语句只能出现在 iterator 块中，该块可用作方法、运算符或访问器的体。这类方法、运算符或访问器的体受以下约束的控制：

  • 不允许不安全块。

  • 方法、运算符或访问器的参数不能是 ref 或 out。

yield 语句不能出现在匿名方法中。有关更多信息，请参见 匿名方法（C# 编程指南）。

当和 expression 一起使用时，yield return 语句不能出现在 catch 块中或含有一个或多个 catch 子句的 try 块中。有关更多信息，请参见 异常处理语句（C# 参考）。

示例

在下面的示例中，迭代器块（这里是方法 Power(int number, int power)）中使用了 yield 语句。当调用 Power 方法时，它返回一个包含数字幂的可枚举对象。注意 Power 方法的返回类型是 IEnumerable（一种迭代器接口类型）。

C# 复制代码
public class List
{
    //using System.Collections;
    public static IEnumerable Power(int number, int exponent)
    {
        int counter = 0;
        int result = 1;
        while (counter++ < exponent)
        {
            result = result * number;
            yield return result;
        }
    }

    static void Main()
    {
        // Display powers of 2 up to the exponent 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }
}
/*
Output:
2 4 8 16 32 64 128 256
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 19.3 迭代器

  • 22 迭代器

请参见

概念

C# 编程指南

参考

foreach，in（C# 参考）
使用迭代器（C# 编程指南）

其他资源

C# 参考


where（泛型类型约束）（C# 编程指南）

[image]

C# 语言参考
where（泛型类型约束）（C# 编程指南）
另请参见 发送反馈意见

更新：2007 年 11 月

在泛型类型定义中，where 子句用于指定对下列类型的约束：这些类型可用作泛型声明中定义的类型参数的实参。例如，可以声明一个泛型类 MyGenericClass，这样，类型参数 T 就可以实现 IComparable<(Of <(T>)>) 接口：

复制代码
public class MyGenericClass<T> where T:IComparable { }
说明：

有关查询表达式中的 where 子句的更多信息，请参见 where 子句（C# 参考）。

除了接口约束，where 子句还可以包括基类约束，以指出某个类型必须将指定的类作为基类（或者就是该类本身），才能用作该泛型类型的类型参数。这样的约束一经使用，就必须出现在该类型参数的所有其他约束之前。

C# 复制代码
class MyClass<T, U>
    where T : class
    where U : struct
{}

where 子句还可以包括构造函数约束。可以使用 new 运算符创建类型参数的实例；但类型参数为此必须受构造函数约束 new() 的约束。new() 约束可以让编译器知道：提供的任何类型参数都必须具有可访问的无参数（或默认）构造函数。例如：

C# 复制代码
public class MyGenericClass<T> where T : IComparable, new()
{
    // The following line is not possible without new() constraint:
    T item = new T();
}

new() 约束出现在 where 子句的最后。

对于多个类型参数，每个类型参数都使用一个 where 子句，例如：

C# 复制代码
interface IMyInterface
{
}

class Dictionary<TKey, TVal>
    where TKey : IComparable, IEnumerable
    where TVal : IMyInterface
{
    public void Add(TKey key, TVal val)
    {
    }
}

还可以将约束附加到泛型方法的类型参数，例如：

复制代码
public bool MyMethod<T>(T t) where T : IMyInterface { }

请注意，对于委托和方法两者来说，描述类型参数约束的语法是一样的：

复制代码
delegate T MyDelegate<T>() where T : new()

有关泛型委托的信息，请参见泛型委托。

有关约束的语法和用法的详细信息，请参见类型参数的约束。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 19.1.4 约束

请参见

概念

C# 编程指南

参考

泛型介绍（C# 编程指南）
new 约束（C# 参考）
类型参数的约束（C# 编程指南）

其他资源

C# 参考


add（C# 参考）

[image]

C# 语言参考
add（C# 参考）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

add 上下文关键字用于定义一个自定义事件访问器，当客户端代码订阅您的事件时将调用该访问器。如果提供自定义 add 访问器，还必须提供 remove 访问器。

示例

下面的示例演示一个具有自定义 add 和 remove 访问器的事件。有关完整的示例，请参见如何：实现接口事件（C# 编程指南）。

C# 复制代码
class Events : IDrawingObject
{
    event EventHandler PreDrawEvent;

    event EventHandler IDrawingObject.OnDraw
    {
        add
        {
            lock (PreDrawEvent)
            {
                PreDrawEvent += value;
            }
        }
        remove
        {
            lock (PreDrawEvent)
            {
                PreDrawEvent -= value;
            }
        }
    }

}

通常不需要提供自己的自定义事件访问器。在大多数情况下，使用在声明事件时由编译器自动生成的访问器就足够了。

请参见

参考

事件（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


remove（C# 参考）

[image]

C# 语言参考
remove（C# 参考）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

remove 上下文关键字用于定义一个自定义事件访问器，当客户端代码取消订阅事件时将调用该访问器。如果提供自定义 remove 访问器，还必须提供 add 访问器。

示例

下面的示例演示一个具有自定义 add 和 remove 访问器的事件。有关完整的示例，请参见如何：实现接口事件（C# 编程指南）。

C# 复制代码
class Events : IDrawingObject
{
    event EventHandler PreDrawEvent;

    event EventHandler IDrawingObject.OnDraw
    {
        add
        {
            lock (PreDrawEvent)
            {
                PreDrawEvent += value;
            }
        }
        remove
        {
            lock (PreDrawEvent)
            {
                PreDrawEvent -= value;
            }
        }
    }

}

通常不需要提供自己的自定义事件访问器。在大多数情况下，使用在声明事件时由编译器自动生成的访问器就足够了。

请参见

参考

事件（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


global（C# 参考）

[image]

C# 语言参考
global（C# 参考）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

在 :: 运算符前面使用的 global 上下文关键字引用全局命名空间，该命名空间是任何 C# 程序的默认命名空间，未以其他方式命名。有关更多信息，请参见如何：使用命名空间别名限定符（C# 编程指南）。

示例

下面的示例演示如何使用 global 上下文关键字指定在全局命名空间中定义 TestApp 类：

C# 复制代码
class TestClass : global::TestApp { }

请参见

概念

命名空间（Visual C# 速成版）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


查询关键字（C# 参考）

[image]

C# 语言参考
查询关键字（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

本节包含查询表达式中使用的上下文关键字。

本节内容

子句 | 说明
from | 指定数据源和范围变量（类似于迭代变量）。
where | 根据一个或多个由逻辑“与”和逻辑“或”运算符（&& 或 ||）分隔的布尔表达式筛选源元素。
select | 指定当执行查询时返回的序列中的元素将具有的类型和形式。
group | 按照指定的键值对查询结果进行分组。
into | 提供一个标识符，它可以充当对 join、group 或 select 子句的结果的引用。
orderby | 基于元素类型的默认比较器按升序或降序对查询结果进行排序。
join | 基于两个指定匹配条件之间的相等比较来联接两个数据源。
let | 引入一个用于存储查询表达式中的子表达式结果的范围变量。
in | join 子句中的上下文关键字。
on | join 子句中的上下文关键字。
equals | join 子句中的上下文关键字。
by | group 子句中的上下文关键字。
ascending | orderby 子句中的上下文关键字。
descending | orderby 子句中的上下文关键字。

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

C# 关键字

其他资源

语言集成查询 (LINQ)
C# 中的 LINQ 入门


from 子句（C# 参考）

[image]

C# 语言参考
from 子句（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

查询表达式必须以 from 子句开头。另外，查询表达式还可以包含子查询，子查询也是以 from 子句开头。from 子句指定以下内容：

  • 将对其运行查询或子查询的数据源。

  • 一个本地范围变量，表示源序列中的每个元素。

范围变量和数据源都是强类型。from 子句中引用的数据源的类型必须为 IEnumerable、IEnumerable<(Of <(T>)>) 或一种派生类型（如 IQueryable<(Of <(T>)>)）。

在下面的示例中，numbers 是数据源，而 num 是范围变量。请注意，这两个变量都是强类型，即使使用了 var 关键字也是如此。

C# 复制代码
class LowNums
{
    static void Main()
    {
        // A simple data source.
        int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

        // Create the query.
        // lowNums is an IEnumerable<int>
        var lowNums = from num in numbers
            where num < 5
            select num;

        // Execute the query.
        foreach (int i in lowNums)
        {
            Console.Write(i + " ");
        }
    }
}
// Output: 4 1 3 2 0

范围变量

如果数据源实现了 IEnumerable<(Of <(T>)>)，则编译器可以推断范围变量的类型。例如，如果数据源的类型为 IEnumerable<Customer>，则推断出范围变量的类型为 Customer。仅当数据源是非泛型 IEnumerable 类型（如 ArrayList）时，才必须显式指定数据源类型。有关更多信息，请参见 如何：使用 LINQ 查询 ArrayList。

在上一个示例中，num 被推断为 int 类型。由于范围变量是强类型，因此可以对其调用方法或者在其他操作中使用它。例如，可以不编写 select num，而编写 select num.ToString() 使查询表达式返回一个字符串序列而不是整数序列。或者，也可以编写 select n + 10 使表达式返回序列 14、11、13、12、10。有关更多信息，请参见 select 子句（C# 参考）。

范围变量类似于 foreach 语句中的迭代变量，只是两者之间有一个非常重要的区别：范围变量从不实际存储来自数据源的数据。范围变量只是提供了语法上的便利，使查询能够描述执行查询时将发生的事情。有关更多信息，请参见 LINQ 查询介绍。

复合 from 子句

在某些情况下，源序列中的每个元素本身可能是序列，也可能包含序列。例如，数据源可能是一个 IEnumerable<Student>，其中，序列中的每个 Student 对象都包含一个测验得分列表。若要访问每个 Student 元素中的内部列表，可以使用复合 from 子句。该技术类似于使用嵌套的 foreach 语句。可以向任一 from 子句中添加 where 或 orderby 子句来筛选结果。下面的示例演示了一个 Student 对象序列，其中每个对象都包含一个表示测验得分的内部整数 List。为了访问该内部列表，此示例使用了复合 from 子句。如有必要，可在两个 from 子句之间再插入子句。

C# 复制代码
class CompoundFrom
{
    // The element type of the data source.
    public class Student
    {
        public string LastName { get; set; }
        public List<int> Scores {get; set;}
    }

    static void Main()
    {

        // Use a collection initializer to create the data source. Note that
        // each element in the list contains an inner sequence of scores.
        List<Student> students = new List<Student>
        {
           new Student {LastName="Omelchenko", Scores= new List<int> {97, 72, 81, 60}},
           new Student {LastName="O'Donnell", Scores= new List<int> {75, 84, 91, 39}},
           new Student {LastName="Mortensen", Scores= new List<int> {88, 94, 65, 85}},
           new Student {LastName="Garcia", Scores= new List<int> {97, 89, 85, 82}},
           new Student {LastName="Beebe", Scores= new List<int> {35, 72, 91, 70}}
        };

        // Use a compound from to access the inner sequence within each element.
        // Note the similarity to a nested foreach statement.
        var scoreQuery = from student in students
                         from score in student.Scores
                            where score > 90
                            select new { Last = student.LastName, score };

        // Execute the queries.
        Console.WriteLine("scoreQuery:");
        foreach (var student in scoreQuery)
        {
            Console.WriteLine("{0} Score: {1}", student.Last, student.score);
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/*
scoreQuery:
Omelchenko Score: 97
O'Donnell Score: 91
Mortensen Score: 94
Garcia Score: 97
Beebe Score: 91
*/

使用多个 from 子句执行联接

复合 from 子句用于访问单个数据源中的内部集合。不过，查询还可以包含多个可从独立数据源生成补充查询的 from 子句。使用此技术可以执行某些类型的、无法通过使用 join 子句执行的联接操作。

下面的示例演示如何使用两个 from 子句构成两个数据源的完全交叉联接。

C# 复制代码
class CompoundFrom2
{
    static void Main()
    {
        char[] upperCase = { 'A', 'B', 'C'};
        char[] lowerCase = { 'x', 'y', 'z'};

        var joinQuery1 =
            from upper in upperCase
            from lower in lowerCase
                select new { upper, lower};

        var joinQuery2 =
            from lower in lowerCase
            where lower != 'x'
            from upper in upperCase
            select new { lower, upper };

        // Execute the queries.
        Console.WriteLine("Cross join:");
        foreach (var pair in joinQuery1)
        {
            Console.WriteLine("{0} is matched to {1}", pair.upper, pair.lower);
        }

        Console.WriteLine("Filtered non-equijoin:");
        foreach (var pair in joinQuery2)
        {
            Console.WriteLine("{0} is matched to {1}", pair.lower, pair.upper);
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
        Cross join:
        A is matched to x
        A is matched to y
        A is matched to z
        B is matched to x
        B is matched to y
        B is matched to z
        C is matched to x
        C is matched to y
        C is matched to z
        Filtered non-equijoin:
        y is matched to A
        y is matched to B
        y is matched to C
        z is matched to A
        z is matched to B
        z is matched to C
        */

有关使用多个 from 子句的联接操作的更多信息，请参见如何：执行自定义联接操作（C# 编程指南）。

请参见

概念

LINQ 查询表达式（C# 编程指南）

其他资源

查询关键字（C# 参考）


where 子句（C# 参考）

[image]

C# 语言参考
where 子句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

where 子句用在查询表达式中，用于指定将在查询表达式中返回数据源中的哪些元素。它将一个布尔条件（“谓词”）应用于每个源元素（由范围变量引用），并返回满足指定条件的元素。一个查询表达式可以包含多个 where 子句，一个子句可以包含多个谓词子表达式。

示例

在下面的示例中，where 子句筛选出除小于五的数字外的所有数字。如果移除 where 子句，则会返回数据源中的所有数字。表达式 num < 5 是应用于每个元素的谓词。

C# 复制代码
class WhereSample
{
    static void Main()
    {
        // Simple data source. Arrays support IEnumerable<T>.
        int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

        // Simple query with one predicate in where clause.
        var queryLowNums =
            from num in numbers
            where num < 5
            select num;

        // Execute the query.
        foreach (var s in queryLowNums)
        {
            Console.Write(s.ToString() + " ");
        }
    }
}
//Output: 4 1 3 2 0

在单一 where 子句内，可以使用 && 和 || 运算符根据需要指定任意多个谓词。在下面的示例中，查询将指定两个谓词，以便只选择小于五的偶数。

C# 复制代码
class WhereSample2
{
    static void Main()
    {
        // Data source.
        int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

        // Create the query with two predicates in where clause.
        var queryLowNums2 =
            from num in numbers
            where num < 5 && num % 2 == 0
            select num;

        // Execute the query
        foreach (var s in queryLowNums2)
        {
            Console.Write(s.ToString() + " ");
        }
    }
}
// Output: 4 2 0

where 子句可以包含一个或多个返回布尔值的方法。在下面的示例中，where 子句使用一个方法来确定范围变量的当前值是偶数还是奇数。

C# 复制代码
class WhereSample3
{
    static void Main()
    {
        // Data source
        int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

        // Create the query with a method call in the where clause.
        // Note: This won't work in LINQ to SQL unless you have a
        // stored procedure that is mapped to a method by this name.
        var queryEvenNums =
            from num in numbers
            where IsEven(num)
            select num;

         // Execute the query.
        foreach (var s in queryEvenNums)
        {
            Console.Write(s.ToString() + " ");
        }
    }

    // Method may be instance method or static method.
    static bool IsEven(int i)
    {
        return i % 2 == 0;
    }
}
//Output: 4 8 6 2 0

备注

where 子句是一种筛选机制。除了不能是第一个或最后一个子句外，它几乎可以放在查询表达式中的任何位置。where 子句可以出现在 group 子句的前面或后面，具体情况取决于是必须在对源元素进行分组之前还是之后来筛选源元素。

如果指定的谓词对于数据源中的元素无效，则会发生编译时错误。这是 LINQ 提供的强类型检查的一个优点。

编译时，where 关键字会被转换为对 Where 标准查询运算符方法的调用。

请参见

概念

筛选数据
LINQ 查询表达式（C# 编程指南）

参考

from 子句（C# 参考）
select 子句（C# 参考）

其他资源

查询关键字（C# 参考）
C# 中的 LINQ 入门


select 子句（C# 参考）

[image]

C# 语言参考
select 子句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在查询表达式中，select 子句可以指定将在执行查询时产生的值的类型。该子句的结果将基于前面所有子句的计算结果以及 select 子句本身中的所有表达式。查询表达式必须以 select 子句或 group 子句结束。

下面的示例演示了查询表达式中的简单 select 子句。

C# 复制代码
class SelectSample1
{
    static void Main()
    {
        //Create the data source
        List<int> Scores = new List<int>() { 97, 92, 81, 60 };

        // Create the query.
        IEnumerable<int> queryHighScores =
            from score in Scores
            where score > 80
            select score;

        // Execute the query.
        foreach (int i in queryHighScores)
        {
            Console.Write(i + " ");
        }
    }
}
//Output: 97 92 81

select 子句产生的序列的类型决定了查询变量 queryHighScores 的类型。在最简单的情况下，select 子句仅指定范围变量。这会使返回的序列包含与数据源具有相同类型的元素。有关更多信息，请参见查询操作中的类型关系 (LINQ)。不过，select 子句还提供了一种功能强大的机制，可用于将源数据转换（或投影）为新类型。有关更多信息，请参见使用 LINQ 进行数据转换。

示例

下面的示例演示了 select 子句可能采用的所有不同形式。在每个查询中，请注意 select 子句和查询变量（studentQuery1、studentQuery2 等）的类型之间的关系。

C# 复制代码
    class SelectSample2
    {
        // Define some classes
        public class Student
        {
            public string First { get; set; }
            public string Last { get; set; }
            public int ID { get; set; }
            public List<int> Scores;
            public ContactInfo GetContactInfo(SelectSample2 app, int id)
            {
                ContactInfo cInfo =
                    (from ci in app.contactList
                    where ci.ID == id
                    select ci)
                    .FirstOrDefault();

                return cInfo;
            }

            public override string ToString()
            {
                return First + " " + Last + ":" + ID;
            }
        }

        public class ContactInfo
        {
            public int ID { get; set; }
            public string Email { get; set; }
            public string Phone { get; set; }
            public override string ToString() { return Email + "," + Phone; }
        }

        public class ScoreInfo
        {
            public double Average { get; set; }
            public int ID { get; set; }
        }

        // The primary data source
        List<Student> students = new List<Student>()
        {
             new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int>() {97, 92, 81, 60}},
             new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int>() {75, 84, 91, 39}},
             new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int>() {88, 94, 65, 91}},
             new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int>() {97, 89, 85, 82}},
        };

        // Separate data source for contact info.
        List<ContactInfo> contactList = new List<ContactInfo>()
        {
            new ContactInfo {ID=111, Email="SvetlanO@Contoso.com", Phone="206-555-0108"},
            new ContactInfo {ID=112, Email="ClaireO@Contoso.com", Phone="206-555-0298"},
            new ContactInfo {ID=113, Email="SvenMort@Contoso.com", Phone="206-555-1130"},
            new ContactInfo {ID=114, Email="CesarGar@Contoso.com", Phone="206-555-0521"}
        };

        static void Main(string[] args)
        {
            SelectSample2 app = new SelectSample2();

            // Produce a filtered sequence of unmodified Students.
            IEnumerable<Student> studentQuery1 =
                from student in app.students
                where student.ID > 111
                select student;

            Console.WriteLine("Query1: select range_variable");
            foreach (Student s in studentQuery1)
            {
                Console.WriteLine(s.ToString());
            }

            // Produce a filtered sequence of elements that contain
            // only one property of each Student.
            IEnumerable<String> studentQuery2 =
                from student in app.students
                where student.ID > 111
                select student.Last;

            Console.WriteLine("\r\n studentQuery2: select range_variable.Property");
            foreach (string s in studentQuery2)
            {
                Console.WriteLine(s);
            }

            // Produce a filtered sequence of objects created by
            // a method call on each Student.
            IEnumerable<ContactInfo> studentQuery3 =
                from student in app.students
                where student.ID > 111
                select student.GetContactInfo(app, student.ID);

            Console.WriteLine("\r\n studentQuery3: select range_variable.Method");
            foreach (ContactInfo ci in studentQuery3)
            {
                Console.WriteLine(ci.ToString());
            }

            // Produce a filtered sequence of ints from
            // the internal array inside each Student.
            IEnumerable<int> studentQuery4 =
                from student in app.students
                where student.ID > 111
                select student.Scores[0];

            Console.WriteLine("\r\n studentQuery4: select range_variable[index]");
            foreach (int i in studentQuery4)
            {
                Console.WriteLine("First score = {0}", i);
            }

            // Produce a filtered sequence of doubles
            // that are the result of an expression.
            IEnumerable<double> studentQuery5 =
                from student in app.students
                where student.ID > 111
                select student.Scores[0] * 1.1;

            Console.WriteLine("\r\n studentQuery5: select expression");
            foreach (double d in studentQuery5)
            {
                Console.WriteLine("Adjusted first score = {0}", d);
            }

            // Produce a filtered sequence of doubles that are
            // the result of a method call.
            IEnumerable<double> studentQuery6 =
                from student in app.students
                where student.ID > 111
                select student.Scores.Average();

            Console.WriteLine("\r\n studentQuery6: select expression2");
            foreach (double d in studentQuery6)
            {
                Console.WriteLine("Average = {0}", d);
            }

            // Produce a filtered sequence of anonymous types
            // that contain only two properties from each Student.
            var studentQuery7 =
                from student in app.students
                where student.ID > 111
                select new { student.First, student.Last };

            Console.WriteLine("\r\n studentQuery7: select new anonymous type");
            foreach (var item in studentQuery7)
            {
                Console.WriteLine("{0}, {1}", item.Last, item.First);
            }

            // Produce a filtered sequence of named objects that contain
            // a method return value and a property from each Student.
            // Use named types if you need to pass the query variable
            // across a method boundary.
            IEnumerable<ScoreInfo> studentQuery8 =
                from student in app.students
                where student.ID > 111
                select new ScoreInfo
                {
                    Average = student.Scores.Average(),
                    ID = student.ID
                };

            Console.WriteLine("\r\n studentQuery8: select new named type");
            foreach (ScoreInfo si in studentQuery8)
            {
                Console.WriteLine("ID = {0}, Average = {1}", si.ID, si.Average);
            }

            // Produce a filtered sequence of students who appear on a contact list
            // and whose average is greater than 85.
            IEnumerable<ContactInfo> studentQuery9 =
                from student in app.students
                where student.Scores.Average() > 85
                join ci in app.contactList on student.ID equals ci.ID
                select ci;

            Console.WriteLine("\r\n studentQuery9: select result of join clause");
            foreach (ContactInfo ci in studentQuery9)
            {
                Console.WriteLine("ID = {0}, Email = {1}", ci.ID, ci.Email);
            }

            // Keep the console window open in debug mode
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
            }
        }
    /* Output
        Query1: select range_variable
        Claire O'Donnell:112
        Sven Mortensen:113
        Cesar Garcia:114

        studentQuery2: select range_variable.Property
        O'Donnell
        Mortensen
        Garcia

        studentQuery3: select range_variable.Method
        ClaireO@Contoso.com,206-555-0298
        SvenMort@Contoso.com,206-555-1130
        CesarGar@Contoso.com,206-555-0521

        studentQuery4: select range_variable[index]
        First score = 75
        First score = 88
        First score = 97

        studentQuery5: select expression
        Adjusted first score = 82.5
        Adjusted first score = 96.8
        Adjusted first score = 106.7

        studentQuery6: select expression2
        Average = 72.25
        Average = 84.5
        Average = 88.25

        studentQuery7: select new anonymous type
        O'Donnell, Claire
        Mortensen, Sven
        Garcia, Cesar

        studentQuery8: select new named type
        ID = 112, Average = 72.25
        ID = 113, Average = 84.5
        ID = 114, Average = 88.25

        studentQuery9: select result of join clause
        ID = 114, Email = CesarGar@Contoso.com
*/

如上一个示例中的 studentQuery8 所示，您有时可能希望所返回序列中的元素仅包含源元素的属性子集。通过使返回的序列尽可能地小一些，可以降低内存需求，并提高查询的执行速度。通过在 select 子句中创建一个匿名类型，并且借助于对象初始值设定项用源元素中的适当属性对该匿名类型进行初始化，可以达到此目的。有关如何执行此操作的示例，请参见对象和集合初始值设定项（C# 编程指南）。

备注

编译时，select 子句会被转换为对 Select 标准查询运算符的方法调用。

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

from 子句（C# 参考）
分部（方法）
匿名类型（C# 编程指南）

其他资源

C# 参考
查询关键字（C# 参考）
C# 中的 LINQ 入门


group 子句（C# 参考）

[image]

C# 语言参考
group 子句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

group 子句返回一个 IGrouping<(Of <(TKey, TElement>)>) 对象序列，这些对象包含零个或更多个与该组的键值匹配的项。例如，可以按照每个字符串中的第一个字母对字符串序列进行分组。在这种情况下，第一个字母是键且具有 char 类型，并且存储在每个 IGrouping<(Of <(TKey, TElement>)>) 对象的 Key 属性中。编译器可推断该键的类型。

可以用 group 子句结束查询表达式，如下面的示例所示：

C# 复制代码
// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery1 =
    from student in students
    group student by student.Last[0];

如果您想要对每个组执行附加查询操作，则可以使用 into 上下文关键字指定一个临时标识符。使用 into 时，必须继续编写该查询，并最终用一个 select 语句或另一个 group 子句结束该查询，如下面的代码摘录所示：

C# 复制代码
// Group students by the first letter of their last name
// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery2 =
    from student in students
    group student by student.Last[0] into g
    orderby g.Key
    select g;

本主题中的“示例”部分中提供了使用含有和不含 into 的 group 的更完整示例。

枚举组查询的结果

由于 group 查询产生的 IGrouping<(Of <(TKey, TElement>)>) 对象实质上是列表的列表，因此必须使用嵌套的 foreach 循环来访问每一组中的各个项。外部循环用于循环访问组键，内部循环用于循环访问组本身中的每个项。组可能具有键，但没有元素。以下是执行上述代码示例中的查询的 foreach 循环：

C# 复制代码
// Iterate group items with a nested foreach. This IGrouping encapsulates
// a sequence of Student objects, and a Key of type char.
// For convenience, var can also be used in the foreach statement.
foreach (IGrouping<char, Student> studentGroup in studentQuery2)
{
     Console.WriteLine(studentGroup.Key);
     // Explicit type for student could also be used here.
     foreach (var student in studentGroup)
     {
         Console.WriteLine("   {0}, {1}", student.Last, student.First);
     }
 }

键类型

组键可以是任何类型，如字符串、内置数值类型、用户定义的命名类型或匿名类型。

按字符串进行分组

上述代码示例使用的是 char。可以很容易地改为指定字符串键，如完整的姓氏：

C# 复制代码
// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
 var studentQuery3 =
     from student in students
     group student by student.Last;

按布尔进行分组

下面的示例演示使用布尔值作为键将结果划分成两个组。请注意，该值是由 group 子句中的子表达式产生的。

C# 复制代码
class GroupSample1
{
    // The element type of the data source.
    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
        public List<int> Scores;
    }

    public static List<Student> GetStudents()
    {
        // Use a collection initializer to create the data source. Note that each element
        //  in the list contains an inner sequence of scores.
        List<Student> students = new List<Student>
        {
           new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
        };

        return students;

    }

    static void Main()
    {
        // Obtain the data source.
        List<Student> students = GetStudents();

        // Group by true or false.
        // Query variable is an IEnumerable<IGrouping<bool, Student>>
        var booleanGroupQuery =
            from student in students
            group student by student.Scores.Average() >= 80; //pass or fail!

        // Execute the query and access items in each group
        foreach (var studentGroup in booleanGroupQuery)
        {
            Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
  Low averages
   Omelchenko, Svetlana:77.5
   O'Donnell, Claire:72.25
   Garcia, Cesar:75.5
  High averages
   Mortensen, Sven:93.5
   Garcia, Debra:88.25
*/

按数值范围进行分组

下一个示例使用表达式创建表示百分比范围的数值组键。请注意，该示例使用 let 作为方法调用结果的方便存储位置，从而无需在 group 子句中调用该方法两次。另请注意，在 group 子句中，为了避免发生“被零除”异常，代码进行了相应检查以确保学生的平均成绩不为零。有关如何在查询表达式中安全使用方法的更多信息，请参见如何：在查询表达式中处理异常（C# 编程指南）。

C# 复制代码
class GroupSample2
{
    // The element type of the data source.
    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
        public List<int> Scores;
    }

    public static List<Student> GetStudents()
    {
        // Use a collection initializer to create the data source. Note that each element
        //  in the list contains an inner sequence of scores.
        List<Student> students = new List<Student>
        {
           new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
           new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
           new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
           new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
           new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
        };

        return students;

    }

    // This method groups students into percentile ranges based on their
    // grade average. The Average method returns a double, so to produce a whole
    // number it is necessary to cast to int before dividing by 10.
    static void Main()
    {
        // Obtain the data source.
        List<Student> students = GetStudents();

        // Write the query.
        var studentQuery =
            from student in students
            let avg = (int)student.Scores.Average()
            group student by (avg == 0 ? 0 : avg / 10) into g
            orderby g.Key
            select g;

        // Execute the query.
        foreach (var studentGroup in studentQuery)
        {
            int temp = studentGroup.Key * 10;
            Console.WriteLine("Students with an average between {0} and {1}", temp, temp + 10);
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
            }
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
     Students with an average between 70 and 80
       Omelchenko, Svetlana:77.5
       O'Donnell, Claire:72.25
       Garcia, Cesar:75.5
     Students with an average between 80 and 90
       Garcia, Debra:88.25
     Students with an average between 90 and 100
       Mortensen, Sven:93.5
 */

按复合键进行分组

当您想要按照多个键对元素进行分组时，可使用复合键。通过使用匿名类型或命名类型来存储键元素，可以创建复合键。在下面的示例中，假定已经使用名为 surname 和 city 的两个成员声明了类 Person。group 子句使得为每组具有相同姓氏和相同城市的人员创建一个单独的组。

复制代码
group person by new {name = person.surname, city = person.city};

如果必须将查询变量传递给其他方法，请使用命名类型。使用自动实现的属性作为键来创建一个特殊类，然后重写 Equals 和 GetHashCode 方法。还可以使用结构；在此情况下，并不绝对需要重写这些方法。有关更多信息，请参见如何：使用自动实现的属性实现轻量类（C# 编程指南）和如何：在目录树中查询重复文件 (LINQ)。后一个主题包含一个代码示例，该示例演示如何将复合键与命名类型结合使用。

示例

下面的示例演示在没有向组应用附加查询逻辑时将源数据排序放入不同组中的标准模式。这称为不带延续的分组。字符串数组中的元素按照它们的第一个字母进行分组。查询结果是一个 IGrouping<(Of <(TKey, TElement>)>) 类型，其中包含一个 char 类型的公共 Key 属性以及一个包含分组中每个项的 IEnumerable<(Of <(T>)>) 集合。

group 子句的结果是序列的序列。因此，若要访问所返回的每个组中的单个元素，请在循环访问组键的循环内使用嵌套的 foreach 循环，如下面的示例所示。

C# 复制代码
class GroupExample1
{
    static void Main()
    {
        // Create a data source.
        string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese" };

        // Create the query.
        var wordGroups =
            from w in words
            group w by w[0];

        // Execute the query.
        foreach (var wordGroup in wordGroups)
        {
            Console.WriteLine("Words that start with the letter '{0}':", wordGroup.Key);
            foreach (var word in wordGroup)
            {
                Console.WriteLine(word);
            }
        }

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
      Words that start with the letter 'b':
        blueberry
        banana
      Words that start with the letter 'c':
        chimpanzee
        cheese
      Words that start with the letter 'a':
        abacus
        apple
     */

此示例演示在创建组之后，如何使用通过 into 实现的延续对这些组执行附加逻辑。有关更多信息，请参见 into（C# 参考）。下面的示例查询每个组以仅选择那些键值为元音的元素。

C# 复制代码
class GroupClauseExample2
{
    static void Main()
    {
        // Create the data source.
        string[] words2 = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese", "elephant", "umbrella", "anteater" };

        // Create the query.
        var wordGroups2 =
            from w in words2
            group w by w[0] into grps
            where (grps.Key == 'a' || grps.Key == 'e' || grps.Key == 'i'
                   || grps.Key == 'o' || grps.Key == 'u')
            select grps;

        // Execute the query.
        foreach (var wordGroup in wordGroups2)
        {
            Console.WriteLine("Groups that start with a vowel: {0}", wordGroup.Key);
            foreach (var word in wordGroup)
            {
                Console.WriteLine("   {0}", word);
            }
        }

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Groups that start with a vowel: a
        abacus
        apple
        anteater
    Groups that start with a vowel: e
        elephant
    Groups that start with a vowel: u
        umbrella
*/

备注

编译时，group 子句被转换为对 GroupBy 方法的调用。

请参见

任务

如何：对组进行分组（C# 编程指南）
如何：以各种方式对结果进行分组（C# 编程指南）
如何：对分组操作执行子查询（C# 编程指南）

概念

LINQ 查询表达式（C# 编程指南）

参考

IGrouping<(Of <(TKey, TElement>)>)
GroupBy
ThenBy
ThenByDescending

其他资源

查询关键字（C# 参考）


into（C# 参考）

[image]

C# 语言参考
into（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

可以使用 into 上下文关键字创建一个临时标识符，以便将 group、join 或 select 子句的结果存储到新的标识符中。此标识符本身可以是附加查询命令的生成器。在 group 或 select 子句中使用新标识符的用法有时称为“延续”。

示例

下面的示例演示使用 into 关键字来启用临时标识符 fruitGroup，该标识符具有推断类型 IGrouping。通过使用该标识符，可以对每个组调用 Count 方法，并且仅选择那些包含两个或更多个单词的组。

C# 复制代码
class IntoSample1
{
    static void Main()
    {

        // Create a data source.
        string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots"};

        // Create the query.
        var wordGroups1 =
            from w in words
            group w by w[0] into fruitGroup
            where fruitGroup.Count() >= 2
            select new { FirstLetter = fruitGroup.Key, Words = fruitGroup.Count() };

        // Execute the query. Note that we only iterate over the groups,
        // not the items in each group
        foreach (var item in wordGroups1)
        {
            Console.WriteLine(" {0} has {1} elements.", item.FirstLetter, item.Words);
        }

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
   a has 2 elements.
   b has 2 elements.
*/

仅当希望对每个组执行附加查询操作时，才需要在 group 子句中使用 into。有关更多信息，请参见 group 子句（C# 参考）。

有关在 join 子句中使用 into 的示例，请参见 join 子句（C# 参考）。

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

group 子句（C# 参考）

其他资源

查询关键字（C# 参考）


orderby 子句（C# 参考）

[image]

C# 语言参考
orderby 子句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在查询表达式中，orderby 子句可使返回的序列或子序列（组）按升序或降序排序。可以指定多个键，以便执行一个或多个次要排序操作。排序是由针对元素类型的默认比较器执行的。默认排序顺序为升序。您还可以指定自定义比较器。但是，只能通过基于方法的语法使用它。有关更多信息，请参见对数据进行排序。

示例

在下面的示例中，第一个查询按从 A 开始的字母顺序对单词进行排序，第二个查询按降序对相同的单词进行排序。（ascending 关键字是默认排序值，可以省略。）

C# 复制代码
class OrderbySample1
{
    static void Main()
    {
        // Create a delicious data source.
        string[] fruits = { "cherry", "apple", "blueberry" };

        // Query for ascending sort.
        IEnumerable<string> sortAscendingQuery =
            from fruit in fruits
            orderby fruit //"ascending" is default
            select fruit;

        // Query for descending sort.
        IEnumerable<string> sortDescendingQuery =
            from w in fruits
            orderby w descending
            select w;

        // Execute the query.
        Console.WriteLine("Ascending:");
        foreach (string s in sortAscendingQuery)
        {
            Console.WriteLine(s);
        }

        // Execute the query.
        Console.WriteLine(Environment.NewLine + "Descending:");
        foreach (string s in sortDescendingQuery)
        {
            Console.WriteLine(s);
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
Ascending:
apple
blueberry
cherry

Descending:
cherry
blueberry
apple
*/

下面的示例对学生的姓氏执行主要排序，然后对他们的名字执行次要排序。

C# 复制代码
class OrderbySample2
{
    // The element type of the data source.
    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
    }

    public static List<Student> GetStudents()
    {
        // Use a collection initializer to create the data source. Note that each element
        //  in the list contains an inner sequence of scores.
        List<Student> students = new List<Student>
        {
           new Student {First="Svetlana", Last="Omelchenko", ID=111},
           new Student {First="Claire", Last="O'Donnell", ID=112},
           new Student {First="Sven", Last="Mortensen", ID=113},
           new Student {First="Cesar", Last="Garcia", ID=114},
           new Student {First="Debra", Last="Garcia", ID=115}
        };

        return students;

    }
    static void Main(string[] args)
    {
        // Create the data source.
        List<Student> students = GetStudents();

        // Create the query.
        IEnumerable<Student> sortedStudents =
            from student in students
            orderby student.Last ascending, student.First ascending
            select student;

        // Execute the query.
        Console.WriteLine("sortedStudents:");
        foreach (Student student in sortedStudents)
            Console.WriteLine(student.Last + " " + student.First);

        // Now create groups and sort the groups. The query first sorts the names
        // of all students so that they will be in alphabetical order after they are
        // grouped. The second orderby sorts the group keys in alpha order.
        var sortedGroups =
            from student in students
            orderby student.Last, student.First
            group student by student.Last[0] into newGroup
            orderby newGroup.Key
            select newGroup;

        // Execute the query.
        Console.WriteLine(Environment.NewLine + "sortedGroups:");
        foreach (var studentGroup in sortedGroups)
        {
            Console.WriteLine(studentGroup.Key);
            foreach (var student in studentGroup)
            {
                Console.WriteLine("   {0}, {1}", student.Last, student.First);
            }
        }

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
sortedStudents:
Garcia Cesar
Garcia Debra
Mortensen Sven
O'Donnell Claire
Omelchenko Svetlana

sortedGroups:
G
   Garcia, Cesar
   Garcia, Debra
M
   Mortensen, Sven
O
   O'Donnell, Claire
   Omelchenko, Svetlana
*/

备注

编译时，orderby 子句被转换为对 OrderBy 方法的调用。orderby 子句中的多个键转换为 ThenBy 方法调用。

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

group 子句（C# 参考）

其他资源

C# 参考
查询关键字（C# 参考）
C# 中的 LINQ 入门


join 子句（C# 参考）

[image]

C# 语言参考
join 子句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

使用 join 子句可以将来自不同源序列并且在对象模型中没有直接关系的元素相关联。唯一的要求是每个源中的元素需要共享某个可以进行比较以判断是否相等的值。例如，食品经销商可能具有某种产品的供应商列表以及买主列表。例如，可以使用 join 子句创建该产品同一指定地区供应商和买主的列表。

join 子句接受两个源序列作为输入。每个序列中的元素都必须是可以与另一个序列中的相应属性进行比较的属性，或者包含一个这样的属性。join 子句使用特殊的 equals 关键字比较指定的键是否相等。join 子句执行的所有联接都是同等联接。join 子句的输出形式取决于所执行的联接的具体类型。以下是三种最常见的联接类型：

  • 内部联接

  • 分组联接

  • 左外部联接

内部联接

下面的示例演示一个简单的内部同等联接。此查询产生一个“产品名称/类别”对平面序列。同一类别字符串将出现在多个元素中。如果 categories 中的某个元素不具有匹配的 products，则该类别不会出现在结果中。

C# 复制代码
var innerJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID
    select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence

有关更多信息，请参见如何：执行内部联接（C# 编程指南）。

分组联接

含有 into 表达式的 join 子句称为分组联接。

C# 复制代码
var innerGroupJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    select new { CategoryName = category.Name, Products = prodGroup };

分组联接会产生一个分层的结果序列，该序列将左侧源序列中的元素与右侧源序列中的一个或多个匹配元素相关联。分组联接没有等效的关系术语；它本质上是一个对象数组序列。

如果在右侧源序列中找不到与左侧源中的元素相匹配的元素，则 join 子句会为该项产生一个空数组。因此，分组联接基本上仍然是一种内部同等联接，区别只在于分组联接将结果序列组织为多个组。

如果您只选择分组联接的结果，则可以访问各个项，但无法识别结果所匹配的键。因此，通常更为有用的做法是选择分组联接的结果并放入一个也具有该键名的新类型中，如上一个示例所示。

当然，还可以将分组联接的结果用作其他子查询的生成器：

C# 复制代码
var innerGroupJoinQuery2 =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    from prod2 in prodGroup
        where prod2.UnitPrice > 2.50M
        select prod2;

有关更多信息，请参见如何：执行分组联接（C# 编程指南）。

左外部联接

在左外部联接中，将返回左侧源序列中的所有元素，即使它们在右侧序列中没有匹配的元素也是如此。若要在 LINQ 中执行左外部联接，请将 DefaultIfEmpty 方法与分组联接结合起来，以指定要在某个左侧元素不具有匹配元素时产生的默认右侧元素。可以使用 null 作为任何引用类型的默认值，也可以指定用户定义的默认类型。下面的示例演示了用户定义的默认类型：

C# 复制代码
var leftOuterJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    from item in prodGroup.DefaultIfEmpty(new Product{Name = String.Empty, CategoryID = 0})
        select new { CatName = category.Name, ProdName = item.Name };

有关更多信息，请参见如何：执行左外部联接（C# 编程指南）。

equals 运算符

join 子句执行同等联接。换句话说，只能基于两个键之间的相等关系进行匹配。其他类型的比较（例如，“greater than”或“not equals”）不受支持。为了表明所有联接都是同等联接，join 子句使用 equals 关键字而不是 == 运算符。equals 关键字只能用在 join 子句中，并且它与 == 运算符之间存在一个重要区别。对于 equals，左键使用外部源序列，而右键使用内部源序列。外部源仅在 equals 的左侧位于范围内，而内部源序列仅在其右侧位于范围内。

非同等联接

通过使用多个 from 子句将新序列单独引入到查询中，可以执行非同等联接、交叉联接和其他自定义联接操作。有关更多信息，请参见 如何：执行自定义联接操作（C# 编程指南）。

对象集合联接与关系表联接

在 LINQ 查询表达式中，联接操作是在对象集合上执行的。不能使用与两个关系表完全相同的方式“联接”对象集合。在 LINQ 中，仅当两个源序列没有通过任何关系相互联系时，才需要使用显式 join 子句。使用 LINQ to SQL 时，外键表在对象模型中表示为主表的属性。例如，在 Northwind 数据库中，Customer 表与 Orders 表之间具有外键关系。在将这两个表映射到对象模型时，Customer 类具有一个 Orders 属性，该属性包含与该 Customer 相关联的 Orders 的集合。实际上，已经为您执行了联接。

有关在 LINQ to SQL 的上下文中跨相关表执行查询的更多信息，请参见如何：映射数据库关系 (LINQ to SQL)。

复合键

使用复合键可以测试多个值是否相等。有关更多信息，请参见 如何：使用复合键进行联接（C# 编程指南）。还可以在 group 子句中使用组合键。

示例

下面的示例比较了使用相同的匹配键对相同数据源执行内部联接、分组联接和左外部联接的结果。这些示例中添加了一些额外的代码，以便在控制台显示中阐明结果。

C# 复制代码
    class JoinDemonstration
    {
        #region Data

        class Product
        {
            public string Name { get; set; }
            public int CategoryID { get; set; }
        }

        class Category
        {
            public string Name { get; set; }
            public int ID { get; set; }
        }

        // Specify the first data source.
        List<Category> categories = new List<Category>()
        {
            new Category(){Name="Beverages", ID=001},
            new Category(){ Name="Condiments", ID=002},
            new Category(){ Name="Vegetables", ID=003},
            new Category() {  Name="Grains", ID=004},
            new Category() {  Name="Fruit", ID=005}
        };

        // Specify the second data source.
        List<Product> products = new List<Product>()
       {
          new Product{Name="Cola",  CategoryID=001},
          new Product{Name="Tea",  CategoryID=001},
          new Product{Name="Mustard", CategoryID=002},
          new Product{Name="Pickles", CategoryID=002},
          new Product{Name="Carrots", CategoryID=003},
          new Product{Name="Bok Choy", CategoryID=003},
          new Product{Name="Peaches", CategoryID=005},
          new Product{Name="Melons", CategoryID=005},
        };
        #endregion

        static void Main(string[] args)
        {
            JoinDemonstration app = new JoinDemonstration();

            app.InnerJoin();
            app.GroupJoin();
            app.GroupInnerJoin();
            app.GroupJoin3();
            app.LeftOuterJoin();
            app.LeftOuterJoin2();

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        void InnerJoin()
        {
            // Create the query that selects
            // a property from each element.
            var innerJoinQuery =
               from category in categories
               join prod in products on category.ID equals prod.CategoryID
               select new { Category = category.ID, Product = prod.Name };

            Console.WriteLine("InnerJoin:");
            // Execute the query. Access results
            // with a simple foreach statement.
            foreach (var item in innerJoinQuery)
            {
                Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
            }
            Console.WriteLine("InnerJoin: {0} items in 1 group.", innerJoinQuery.Count());
            Console.WriteLine(System.Environment.NewLine);

        }

        void GroupJoin()
        {
            // This is a demonstration query to show the output
            // of a "raw" group join. A more typical group join
            // is shown in the GroupInnerJoin method.
            var groupJoinQuery =
               from category in categories
               join prod in products on category.ID equals prod.CategoryID into prodGroup
               select prodGroup;

            // Store the count of total items (for demonstration only).
            int totalItems = 0;

            Console.WriteLine("Simple GroupJoin:");

            // A nested foreach statement is required to access group items.
            foreach (var prodGrouping in groupJoinQuery)
            {
                Console.WriteLine("Group:");
                foreach (var item in prodGrouping)
                {
                    totalItems++;
                    Console.WriteLine("   {0,-10}{1}", item.Name, item.CategoryID);
                }
            }
            Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed groups", totalItems, groupJoinQuery.Count());
            Console.WriteLine(System.Environment.NewLine);
        }

        void GroupInnerJoin()
        {
            var groupJoinQuery2 =
                from category in categories
                orderby category.ID
                join prod in products on category.ID equals prod.CategoryID into prodGroup
                select new
                {
                    Category = category.Name,
                    Products = from prod2 in prodGroup
                               orderby prod2.Name
                               select prod2
                };

            //Console.WriteLine("GroupInnerJoin:");
            int totalItems = 0;

            Console.WriteLine("GroupInnerJoin:");
            foreach (var productGroup in groupJoinQuery2)
            {
                Console.WriteLine(productGroup.Category);
                foreach (var prodItem in productGroup.Products)
                {
                    totalItems++;
                    Console.WriteLine("  {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
                }
            }
            Console.WriteLine("GroupInnerJoin: {0} items in {1} named groups", totalItems, groupJoinQuery2.Count());
            Console.WriteLine(System.Environment.NewLine);
        }

        void GroupJoin3()
        {

            var groupJoinQuery3 =
                from category in categories
                join product in products on category.ID equals product.CategoryID into prodGroup
                from prod in prodGroup
                orderby prod.CategoryID
                select new { Category = prod.CategoryID, ProductName = prod.Name };

            //Console.WriteLine("GroupInnerJoin:");
            int totalItems = 0;

            Console.WriteLine("GroupJoin3:");
            foreach (var item in groupJoinQuery3)
            {
                totalItems++;
                Console.WriteLine("   {0}:{1}", item.ProductName, item.Category);
            }

            Console.WriteLine("GroupJoin3: {0} items in 1 group", totalItems, groupJoinQuery3.Count());
            Console.WriteLine(System.Environment.NewLine);
        }

        void LeftOuterJoin()
        {
            // Create the query.
            var leftOuterQuery =
               from category in categories
               join prod in products on category.ID equals prod.CategoryID into prodGroup
               select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });

            // Store the count of total items (for demonstration only).
            int totalItems = 0;

            Console.WriteLine("Left Outer Join:");

            // A nested foreach statement  is required to access group items
            foreach (var prodGrouping in leftOuterQuery)
            {
                Console.WriteLine("Group:", prodGrouping.Count());
                foreach (var item in prodGrouping)
                {
                    totalItems++;
                    Console.WriteLine("  {0,-10}{1}", item.Name, item.CategoryID);
                }
            }
            Console.WriteLine("LeftOuterJoin: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
            Console.WriteLine(System.Environment.NewLine);
        }

        void LeftOuterJoin2()
        {
            // Create the query.
            var leftOuterQuery2 =
               from category in categories
               join prod in products on category.ID equals prod.CategoryID into prodGroup
               from item in prodGroup.DefaultIfEmpty()
               select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };

            Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", leftOuterQuery2.Count());
            // Store the count of total items
            int totalItems = 0;

            Console.WriteLine("Left Outer Join 2:");

            // Groups have been flattened.
            foreach (var item in leftOuterQuery2)
            {
                totalItems++;
                Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
            }
            Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", totalItems);

/*Output:

InnerJoin:
Cola      1
Tea       1
Mustard   2
Pickles   2
Carrots   3
Bok Choy  3
Peaches   5
Melons    5
InnerJoin: 8 items in 1 group.

Unshaped GroupJoin:
Group:
   Cola      1
   Tea       1
Group:
   Mustard   2
   Pickles   2
Group:
   Carrots   3
   Bok Choy  3
Group:
Group:
   Peaches   5
   Melons    5
Unshaped GroupJoin: 8 items in 5 unnamed groups

GroupInnerJoin:
Beverages
  Cola       1
  Tea        1
Condiments
  Mustard    2
  Pickles    2
Vegetables
  Bok Choy   3
  Carrots    3
Grains
Fruit
  Melons     5
  Peaches    5
GroupInnerJoin: 8 items in 5 named groups

GroupJoin3:
   Cola:1
   Tea:1
   Mustard:2
   Pickles:2
   Carrots:3
   Bok Choy:3
   Peaches:5
   Melons:5
GroupJoin3: 8 items in 1 group

Left Outer Join:
Group:
  Cola      1
  Tea       1
Group:
  Mustard   2
  Pickles   2
Group:
  Carrots   3
  Bok Choy  3
Group:
  Nothing!  4
Group:
  Peaches   5
  Melons    5
LeftOuterJoin: 9 items in 5 groups

LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola      1
Tea       1
Mustard   2
Pickles   2
Carrots   3
Bok Choy  3
Nothing!  4
Peaches   5
Melons    5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/

备注

后面未跟 into 的 join 子句被转换为 Join 方法调用；后面跟有 into 的 join 子句被转换为 GroupJoin 方法调用。

请参见

任务

如何：执行左外部联接（C# 编程指南）
如何：执行内部联接（C# 编程指南）
如何：执行分组联接（C# 编程指南）
如何：对 Join 子句的结果进行排序（C# 编程指南）
如何：使用复合键进行联接（C# 编程指南）

概念

LINQ 查询表达式（C# 编程指南）
联接运算

参考

group 子句（C# 参考）

其他资源

查询关键字（C# 参考）


let 子句（C# 参考）

[image]

C# 语言参考
let 子句（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在查询表达式中，存储子表达式的结果有时很有用，这样可以在随后的子句中使用。可以使用 let 关键字完成这一工作，该关键字可以创建一个新的范围变量，并且用您提供的表达式的结果初始化该变量。一旦用值初始化了该范围变量，它就不能用于存储其他值。但如果该范围变量存储的是可查询的类型，则可以对其进行查询。

示例

在下面的示例中，以两种方式使用了 let：

  1. 创建一个可以查询自身的可枚举类型。

  2. 使查询只能对范围变量 word 调用一次 ToLower。如果不使用 let，则必须在 where 子句的每个谓词中调用 ToLower。

C# 复制代码
class LetSample1
{
    static void Main()
    {
        string[] strings =
        {
            "A penny saved is a penny earned.",
            "The early bird catches the worm.",
            "The pen is mightier than the sword."
        };

        // Split the sentence into an array of words
        // and select those whose first letter is a vowel.
        var earlyBirdQuery =
            from sentence in strings
            let words = sentence.Split(' ')
            from word in words
            let w = word.ToLower()
            where w[0] == 'a' || w[0] == 'e'
                || w[0] == 'i' || w[0] == 'o'
                || w[0] == 'u'
            select word;

        // Execute the query.
        foreach (var v in earlyBirdQuery)
        {
            Console.WriteLine("\"{0}\" starts with a vowel", v);
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    "A" starts with a vowel
    "is" starts with a vowel
    "a" starts with a vowel
    "earned." starts with a vowel
    "early" starts with a vowel
    "is" starts with a vowel
*/

请参见

任务

如何：在查询表达式中处理异常（C# 编程指南）

概念

LINQ 查询表达式（C# 编程指南）

其他资源

C# 参考
查询关键字（C# 参考）
C# 中的 LINQ 入门


ascending（C# 参考）

[image]

C# 语言参考
ascending（C# 参考）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

ascending 上下文关键字用在查询表达式的 orderby 子句中，用于指定从最小到最大的排序顺序。因为 ascending 是默认排序顺序，所以您无须指定它。

示例

下面的示例演示 ascending 在 orderby 子句中的用法。

C# 复制代码
IEnumerable<string> sortAscendingQuery =
    from vegetable in vegetables
    orderby vegetable ascending
    select vegetable;

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

descending（C# 参考）

其他资源

C# 参考

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


descending（C# 参考）

[image]

C# 语言参考
descending（C# 参考）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

descending 上下文关键字用在查询表达式的 orderby 子句中，用于指定从最大到最小的排序顺序。

示例

下面的示例演示 descending 在 orderby 子句中的用法。

C# 复制代码
IEnumerable<string> sortDescendingQuery =
    from vegetable in vegetables
    orderby vegetable descending
    select vegetable;

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

ascending（C# 参考）

其他资源

C# 参考

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


on（C# 参考）

[image]

C# 语言参考
on（C# 参考）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

on 上下文关键字在查询表达式的 join 子句中用于指定联接条件。

示例

下面的示例演示 on 在 join 子句中的用法。

C# 复制代码
var innerJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID
    select new { ProductName = prod.Name, Category = category.Name };

请参见

概念

LINQ 查询表达式（C# 编程指南）

其他资源

C# 参考

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


equals（C# 参考）

[image]

C# 语言参考
equals（C# 参考）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

equals 上下文关键字用在查询表达式的 join 子句中，用于比较两个序列的元素。有关更多信息，请参见 join 子句（C# 参考）。

示例

下面的示例演示 equals 关键字在 join 子句中的用法。

C# 复制代码
var innerJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID
    select new { ProductName = prod.Name, Category = category.Name };

请参见

概念

LINQ 查询表达式（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


by（C# 参考）

[image]

C# 语言参考
by（C# 参考）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

by 上下文关键字用在查询表达式的 group 子句中，用于指定如何对返回的项进行分组。有关更多信息，请参见 group 子句（C# 参考）。

示例

下面的示例演示如何在 group 子句中使用 by 上下文关键字，以指定按照每个学生姓氏的第一个字母对学生进行分组。

C# 复制代码
var query = from student in students
            group student by student.LastName[0];

请参见

概念

LINQ 查询表达式（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


in（C# 参考）

[image]

C# 语言参考
in（C# 参考）
另请参见 发送反馈意见

更新： 2008 年 7 月

in 上下文关键字可在下面两种上下文中使用：

  • foreach 语句

  • 查询表达式中的 join 子句

请参见

其他资源

C# 参考

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 新增主题。 | 信息补充。


C# 运算符

[image]

C# 语言参考
C# 运算符
另请参见 发送反馈意见

更新：2007 年 11 月

C# 提供大量运算符，这些运算符是指定在表达式中执行哪些操作的符号。通常允许对枚举进行整型运算，例如 ==、==、==、==、==、==、==、==、==、==、==、==、==、== 和 ==。此外，很多运算符可被用户重载，由此在应用到用户定义的类型时更改这些运算符的含义。

下表列出了按优先级顺序分组的 C# 运算符。每个组中的运算符具有相同的优先级。

运算符类别 | 运算符
基本 | x.y f(x) a[x] x++ x-- new typeof checked unchecked ->
一元 | + - ! ~ ++x --x (T)x True False & sizeof
乘法 | * / %
加法 | + -
移位 | << >>
关系和类型检测 | < > <= >= is as
相等 | == !=
逻辑“与” | &
逻辑“异或” | ^
逻辑“或” | |
条件“与” | &&
条件“或” | ||
条件 | ?:
赋值 | = += -= *= /= %= &= |= ^= <<= >>=
null 合并 | ??
Lambda | => 运算符（C# 参考）

算术溢出

算术运算符（+、-、*、/）产生的结果可能会超出涉及的数值类型可能值的范围。详细信息应参考有关特定运算符的章节，而一般情况下：

  • 整数算术溢出或者引发 OverflowException，或者丢弃结果的最高有效位。整数被零除总是引发 DivideByZeroException。

  • 浮点算术溢出或被零除从不引发异常，因为浮点类型基于 IEEE 754，因此可以表示无穷和 NaN（不是数字）。

  • 小数算术溢出总是引发 OverflowException。小数被零除总是引发 DivideByZeroException。

当发生整数溢出时，产生的结果取决于执行上下文，该上下文可为 checked 或 unchecked。在 checked 上下文中引发 OverflowException。在未选中的上下文中，放弃结果的最高有效位并继续执行。因此，C# 使您有机会选择处理或忽略溢出。

除算术运算符以外，整型之间的强制转换也会导致溢出（例如，将 long 强制转换为 int）并受 checked 或 unchecked 执行的限制。然而，按位运算符和移位运算符永远不会导致溢出。

请参见

任务

“运算符重载”示例

概念

C# 编程指南

参考

可重载运算符（C# 编程指南）
C# 关键字

其他资源

C# 参考
Visual C#

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 根据客户建议将赋值类别中的 ??运算符归入其他类别。 | 内容 Bug 修复


[] 运算符（C# 参考）

[image]

C# 语言参考
[] 运算符（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

方括号 ([]) 用于数组、索引器和属性，也可用于指针。

备注

数组类型是一种后跟 [] 的类型：

C# 复制代码
int[] fib; // fib is of type int[], "array of int".
fib = new int[100]; // Create a 100-element int array.

若要访问数组的一个元素，则用方括号括起所需元素的索引：

C# 复制代码
fib[0] = fib[1] = 1;
for (int i = 2; i < 100; ++i) fib[i] = fib[i - 1] + fib[i - 2];

如果数组索引超出范围，则会引发异常。

不能重载数组索引运算符；但类型可以定义采用一个或多个参数的索引器和属性。索引器参数括在方括号中，这一点与数组索引类似；但可以将索引器参数声明为任何类型，这一点与数组索引不同，后者必须为整形。

例如，.NET Framework 定义 Hashtable 类型，该类型将键和任意类型的值关联在一起。

C# 复制代码
System.Collections.Hashtable h = new System.Collections.Hashtable();
h["a"] = 123; // Note: using a string as the index.

方括号还用于指定 属性（C# 编程指南）：

C# 复制代码
// using System.Diagnostics;
[Conditional("DEBUG")]
void TraceMethod() {}

可以使用方括号来指定指针索引：

C# 复制代码
unsafe void M()
{
    int[] nums = {0,1,2,3,4,5};
    fixed ( int* p = nums )
    {
        p[0] = p[1] = 1;
        for( int i=2; i<100; ++i ) p[i] = p[i-1] + p[i-2];
    }
}

不执行边界检查。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.5 运算符

  • 7.2 运算符

请参见

概念

C# 编程指南

参考

C# 运算符
数组（C# 编程指南）
索引器（C# 编程指南）
unsafe（C# 参考）
fixed 语句（C# 参考）

其他资源

C# 参考


() 运算符（C# 参考）

[image]

C# 语言参考
() 运算符（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

除了用于指定表达式中的运算顺序以外，圆括号还用于

  1. 指定强制转换或类型转换：

C# 复制代码
double x = 1234.7;
int a;
a = (int)x; // Cast double to int

  1. 调用方法或委托

C# 复制代码
TestMethod();

备注

强制转换显式调用从一种类型到另一种类型的转换运算符；如果未定义这样的转换运算符，则强制转换将失败。 若要定义转换运算符，请参见 explicit 和 implicit。

不能重载 () 运算符。

有关更多信息，请参见 强制转换和类型转换（C# 编程指南）。

强制转换表达式可能会使语法发生歧义。 例如，表达式 (x)–y 既可以解释为强制转换表达式（将 –y 强制转换为类型 x），也可以解释为结合了带括号表达式的相加表达式（计算 x – y 的值）。

有关方法调用的更多信息，请参见方法（C# 编程指南）。

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.5 运算符

  • 7.2 运算符

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


. 运算符（C# 参考）

[image]

C# 语言参考
. 运算符（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

点运算符 (.) 用于成员访问。点运算符指定类型或命名空间的成员。例如，点运算符用于访问 .NET Framework 类库中的特定方法：

C# 复制代码
// The class Console in namespace System:
System.Console.WriteLine("hello");

例如，请考虑以下类：

C# 复制代码
class Simple
{
    public int a;
    public void b()
    {
    }
}
C# 复制代码
Simple s = new Simple();

变量 s 有两个成员 a 和 b；若要访问这两个成员，请使用点运算符：

C# 复制代码
s.a = 6;   // assign to field a;
s.b();     // invoke member function b;

点还用于构造限定名，即指定其所属的命名空间或接口的名称。

C# 复制代码
// The class Console in namespace System:
System.Console.WriteLine("hello");

using 指令使某些名称限定可选：

C# 复制代码
namespace ExampleNS
{
    using System;
    class C
    {
        void M()
        {
            System.Console.WriteLine("hello");
            Console.WriteLine("hello");   // Same as previous line.
        }
    }
}

但是当某一标识符不确定时，必须限定它：

C# 复制代码
namespace Example2
{
    class Console
    {
        public static void WriteLine(string s){}
    }
}
namespace Example1
{
    using System;
    using Example2;
    class C
    {
        void M()
        {
            // Console.WriteLine("hello");   // Compiler error. Ambiguous reference.
            System.Console.WriteLine("hello"); //OK
            Example2.Console.WriteLine("hello"); //OK
        }
    }
}

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 7.5.4 成员访问

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


:: 运算符（C# 参考）

[image]

C# 语言参考
:: 运算符（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

命名空间别名限定符 (::) 用于查找标识符。它通常放置在两个标识符之间，例如：

C# 复制代码
global::System.Console.WriteLine("Hello World");

备注

命名空间别名限定符可以是 global。这将调用全局命名空间中的查找，而不是在别名命名空间中。

更多信息

有关如何使用 :: 运算符的示例，请参见下面的章节：

  • 如何：使用命名空间别名限定符（C# 编程指南）

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 7.5.2 简单名称

  • 9.7 命名空间别名限定符

请参见

概念

C# 编程指南

参考

C# 运算符
命名空间关键字（C# 参考）
. 运算符（C# 参考）
外部别名（C# 参考）

其他资源

C# 参考


+ 运算符（C# 参考）

[image]

C# 语言参考
+ 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

+ 运算符既可作为一元运算符也可作为二元运算符。

备注

一元 + 运算符是为所有数值类型预定义的。对数值类型进行一元 + 运算的结果就是操作数的值。

为数值类型和字符串类型预定义了二元 + 运算符。对于数值类型，+ 计算两个操作数之和。当其中的一个操作数是字符串类型或两个操作数都是字符串类型时，+ 将操作数的字符串表示形式串联在一起。

委托类型也提供二元 + 运算符，该运算符执行委托串联。

用户定义的类型可重载一元 + 运算符和二元 + 运算符。在枚举时通常允许整型运算。有关更多信息，请参见 operator（C# 参考）。

示例

C# 复制代码
class Plus
{
    static void Main()
    {
        Console.WriteLine(+5);        // unary plus
        Console.WriteLine(5 + 5);     // addition
        Console.WriteLine(5 + .5);    // addition
        Console.WriteLine("5" + "5"); // string concatenation
        Console.WriteLine(5.0 + "5"); // string concatenation
        // note automatic conversion from double to string
    }
}
/*
Output:
5
10
5.5
55
55
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 1.6.7.5 运算符

  • 7.2 运算符

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


- 运算符（C# 参考）

[image]

C# 语言参考
- 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

- 运算符既可作为一元运算符也可作为二元运算符。

备注

一元 - 运算符是为所有数值类型预定义的。数值类型的一元 - 运算的结果是操作数的反数。

二元 - 运算符是为所有数值类型和枚举类型预定义的，其功能是从第一个操作数中减去第二个操作数。

委托类型也提供二元 - 运算符，该运算符执行委托移除。

用户定义的类型可重载一元 - 运算符和二元 - 运算符。有关更多信息，请参见 operator（C# 参考）。

示例

C# 复制代码
class MinusLinus
{
    static void Main()
    {
        int a = 5;
        Console.WriteLine(-a);
        Console.WriteLine(a - 1);
        Console.WriteLine(a - .5);
    }
}
/*
Output:
-5
4
4.5
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


* 运算符（C# 参考）

[image]

C# 语言参考
* 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

乘法运算符 (*)，用于计算操作数的积。另外还用作取消引用运算符，允许读取和写入指针。

备注

所有数值类型都具有预定义的乘法运算符。

* 运算符还用来声明指针类型和取消引用指针。该运算符只能在不安全的上下文中使用，通过 unsafe 关键字的使用来表示，并且需要 /unsafe 编译器选项。取消引用运算符也称为间接寻址运算符。

用户定义的类型可重载二元 * 运算符（请参见 operator）。重载二元运算符时，也会隐式重载相应的赋值运算符（如果有）。

示例

C# 复制代码
class Multiply
{
    static void Main()
    {
        Console.WriteLine(5 * 2);
        Console.WriteLine(-.5 * .2);
        Console.WriteLine(-.5m * .2m); // decimal type
    }
}
    /*
Output
    10
    -0.1
    -0.10
*/
C# 复制代码
public class Pointer
{
    unsafe static void Main()
    {
        int i = 5;
        int* j = &i;
        System.Console.WriteLine(*j);
    }
}
/*
Output:
5
*/

请参见

概念

C# 编程指南

参考

不安全代码和指针（C# 编程指南）
C# 运算符

其他资源

C# 参考


/ 运算符（C# 参考）

[image]

C# 语言参考
/ 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

除法运算符 (/) 用第二个操作数除第一个操作数。所有数值类型都具有预定义的除法运算符。

备注

用户定义的类型可重载 / 运算符（请参见运算符）。对 / 运算符进行重载将隐式重载 /＝ 运算符。

两个整数相除的结果始终为一个整数。例如，5 除以 2 的结果为 2。若要确定 5 除以 2 的余数，请使用 modulo 运算符 (%)。若要获取作为有理数或分数的商，应将被除数或除数设置为 float 类型或 double 类型。可以通过在数字后添加一个小数点来隐式执行此操作，如以下示例中所示。

示例

C# 复制代码
class Division
{
    static void Main()
    {
        Console.WriteLine(5 / 2);
        Console.WriteLine(5 % 2);

        Console.WriteLine(5 / 2.1);
        Console.WriteLine(5.1 / 2);
        Console.WriteLine(-5 / 2);
    }
}
/*
Output:
2
1
2.38095238095238
2.55
-2
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


% 运算符（C# 参考）

[image]

C# 语言参考
% 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

模数运算符 (%) 计算第二个操作数除第一个操作数后的余数。所有数值类型都具有预定义的模数运算符。

备注

用户定义的类型可重载 % 运算符（请参见运算符）。重载二元运算符时，也会隐式重载相应的赋值运算符（如果有）。

示例

C# 复制代码
class MainClass6
{
    static void Main()
    {
        Console.WriteLine(5 % 2);       // int
        Console.WriteLine(-5 % 2);      // int
        Console.WriteLine(5.0 % 2.2);   // double
        Console.WriteLine(5.0m % 2.2m); // decimal
        Console.WriteLine(-5.2 % 2.0);  // double
    }
}
/*
Output:
1
-1
0.6
0.6
-1.2
*/

注释

请注意与双精度类型相关的舍入错误。

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


& 运算符（C# 参考）

[image]

C# 语言参考
& 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

&amp; 运算符既可作为一元运算符也可作为二元运算符。

备注

一元 &amp; 运算符返回操作数的地址（要求 unsafe 上下文）。

为整型和 bool 类型预定义了二进制 & 运算符。对于整型，& 计算操作数的逻辑按位“与”。对于 bool 操作数，&amp; 计算操作数的逻辑“与”；也就是说，当且仅当两个操作数均为 true 时，结果才为 true。

&amp; 运算符计算两个运算符，与第一个操作数的值无关。例如：

C# 复制代码
int i = 0;
if (false & ++i == 1)
{
    // i is incremented, but the conditional
    // expression evaluates to false, so
    // this block does not execute.
}

用户定义的类型可重载二元 &amp; 运算符（请参见 operator）。在枚举时通常允许整型运算。重载二元运算符时，也会隐式重载相应的赋值运算符（如果有）。

示例

C# 复制代码
class BitwiseAnd
{
    static void Main()
    {
        Console.WriteLine(true & false); // logical and
        Console.WriteLine(true & true);  // logical and
        Console.WriteLine("0x{0:x}", 0xf8 & 0x3f);  // bitwise and
    }
}
/*
Output:
False
True
0x38
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


| 运算符（C# 参考）

[image]

C# 语言参考
| 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

二元 | 运算符是为整型和 bool 类型预定义的。对于整型，| 计算操作数的按位“或”结果。对于 bool 操作数，| 计算操作数的逻辑“或”结果；也就是说，当且仅当两个操作数均为 false 时，结果才为 false。

备注

用户定义的类型可重载 | 运算符（请参见运算符）。

示例

C# 复制代码
class OR
{
    static void Main()
    {
        Console.WriteLine(true | false);  // logical or
        Console.WriteLine(false | false); // logical or
        Console.WriteLine("0x{0:x}", 0xf8 | 0x3f);   // bitwise or
    }
}
/*
Output:
True
False
0xff
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


^ 运算符（C# 参考）

[image]

C# 语言参考
^ 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

二元 ^ 运算符是为整型和 bool 类型预定义的。对于整型，^ 将计算操作数的按位“异或”。对于 bool 操作数，^ 将计算操作数的逻辑“异或”；也就是说，当且仅当只有一个操作数为 true 时，结果才为 true。

备注

用户定义的类型可重载 ^ 运算符（请参见运算符）。在枚举时通常允许整型运算。

示例

C# 复制代码
class XOR
{
    static void Main()
    {
        Console.WriteLine(true ^ false);  // logical exclusive-or
        Console.WriteLine(false ^ false); // logical exclusive-or
        // Bitwise exclusive-or:
        Console.WriteLine("0x{0:x}", 0xf8 ^ 0x3f);
    }
}
/*
Output:
True
False
0xc7
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


! 运算符（C# 参考）

[image]

C# 语言参考
! 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

逻辑非运算符 (!) 是对操作数求反的一元运算符。为 bool 定义了该运算符，当且仅当操作数为 false 时才返回 true。

备注

用户定义的类型可重载 ! 运算符（请参见运算符）。

示例

C# 复制代码
class MainClass4
{
    static void Main()
    {
        Console.WriteLine(!true);
        Console.WriteLine(!false);
    }
}
/*
Output:
False
True
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


~ 运算符（C# 参考）

[image]

C# 语言参考
~ 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

~ 运算符对操作数执行按位求补运算，其效果相当于反转每一位。按位求补运算符是为 int、uint、long 和 ulong 类型预定义的。

备注

用户定义的类型可重载 ~ 运算符。有关更多信息，请参见 operator。在枚举时通常允许整型运算。

示例

C# 复制代码
class BWC
{
    static void Main()
    {
        int[] values = { 0, 0x111, 0xfffff, 0x8888, 0x22000022 };
        foreach (int v in values)
        {
            Console.WriteLine("~0x{0:x8} = 0x{1:x8}", v, ~v);
        }
    }
}
/*
Output:
~0x00000000 = 0xffffffff
~0x00000111 = 0xfffffeee
~0x000fffff = 0xfff00000
~0x00008888 = 0xffff7777
~0x22000022 = 0xddffffdd
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


= 运算符（C# 参考）

[image]

C# 语言参考
= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

赋值运算符 (=) 将右操作数的值存储在左操作数表示的存储位置、属性或索引器中，并将值作为结果返回。操作数的类型必须相同（或右边的操作数必须可以隐式转换为左边操作数的类型）。

备注

不能重载赋值运算符。

示例

C# 复制代码
class Assignment
{
    static void Main()
    {
        double x;
        int i;
        i = 5; // int to int assignment
        x = i; // implicit conversion from int to double
        i = (int)x; // needs cast
        Console.WriteLine("i is {0}, x is {1}", i, x);
        object obj = i;
        Console.WriteLine("boxed value = {0}, type is {1}",
                  obj, obj.GetType());
        i = (int)obj;
        Console.WriteLine("unboxed: {0}", i);
    }
}
/*
Output:
i is 5, x is 5
boxed value = 5, type is System.Int32
unboxed: 5
 */

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


< 运算符（C# 参考）

[image]

C# 语言参考
< 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

所有数值和枚举类型都定义“小于”关系运算符 (<)，如果第一个操作数小于第二个操作数，该运算符返回 true，否则返回 false。

备注

用户定义的类型可重载 < 运算符（请参见运算符）。如果重载 <，则还必须重载 >。重载二元运算符时，也会隐式重载相应的赋值运算符（如果有）。

示例

C# 复制代码
class LT
{
    static void Main()
    {
        Console.WriteLine(1 < 1.1);
        Console.WriteLine(1.1 < 1.1);
    }
}
/*
Output:
True
False
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


> 运算符（C# 参考）

[image]

C# 语言参考
> 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

所有数值类型和枚举类型都定义“大于”关系运算符 >，如果第一个操作数大于第二个操作数，它将返回 true，否则返回 false。

备注

用户定义的类型可重载 > 运算符（请参见运算符）。如果重载 >，则还必须重载 <。重载二元运算符时，也会隐式重载相应的赋值运算符（如果有）。

示例

C# 复制代码
class GT
{
    static void Main()
    {
        Console.WriteLine(1.1 > 1);
        Console.WriteLine(1.1 > 1.1);
    }
}
/*
Output:
True
False
*/

请参见

概念

C# 编程指南

参考

C# 运算符
explicit（C# 参考）

其他资源

C# 参考


?: 运算符（C# 参考）

[image]

C# 语言参考
?: 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

条件运算符 (?:) 根据布尔型表达式的值返回两个值之一。条件运算符的格式如下

复制代码
condition ? first_expression : second_expression;

备注

如果条件为 true，则计算第一表达式并以它的计算结果为准；如果为 false，则计算第二表达式并以它的计算结果为准。只计算两个表达式之一。

使用条件运算符，可以更简洁、雅观地表达那些否则可能要求 if-else 结构的计算。例如，为在 sin 函数的计算中避免被零除，可编写为

复制代码
if(x != 0.0) s = Math.Sin(x)/x; else s = 1.0;

或使用条件运算符，

复制代码
s = x != 0.0 ? Math.Sin(x)/x : 1.0;

条件运算符为右联运算符，因此该形式的表达式

复制代码
a ? b : c ? d : e

按如下规则计算：

复制代码
a ? b : (c ? d : e)

而不是按照下面这样计算：

复制代码
(a ? b : c) ? d : e

不能重载条件运算符。

示例

C# 复制代码
class ConditionalOp
{
    static double sinc(double x)
    {
        return x != 0.0 ? Math.Sin(x) / x : 1.0;
    }

    static void Main()
    {
        Console.WriteLine(sinc(0.2));
        Console.WriteLine(sinc(0.1));
        Console.WriteLine(sinc(0.0));
    }
}
/*
Output:
0.993346653975306
0.998334166468282
1
*/

请参见

概念

C# 编程指南

参考

C# 运算符
if-else（C# 参考）

其他资源

C# 参考


++ 运算符（C# 参考）

[image]

C# 语言参考
++ 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

增量运算符 (++) 将操作数加 1。增量运算符可以出现在操作数之前或之后：

备注

第一种形式是前缀增量操作。该操作的结果是操作数加 1 之后的值。

第二种形式是后缀增量操作。该运算的结果是操作数增加之前的值。

数值类型和枚举类型具有预定义的增量运算符。用户定义的类型可重载 ++ 运算符。在枚举时通常允许整型运算。

示例

C# 复制代码
class MainClass
{
    static void Main()
    {
        double x;
        x = 1.5;
        Console.WriteLine(++x);
        x = 1.5;
        Console.WriteLine(x++);
        Console.WriteLine(x);
    }
}
/*
Output
2.5
1.5
2.5
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


-- 运算符（C# 参考）

[image]

C# 语言参考
-- 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

减量运算符 (--) 将操作数减 1。减量运算符可以出现在操作数之前或之后：--variable 和 variable--。第一种形式是前缀减量操作。该运算的结果是操作数减小“之后”的值。第二种形式是后缀减量操作。该运算的结果是操作数减小“之前”的值。

备注

数值类型和枚举类型具有预定义的增量运算符。

用户定义的类型可重载 -- 运算符（请参见运算符）。在枚举时通常允许整型运算。

示例

C# 复制代码
class MainClass5
{
    static void Main()
    {
        double x;
        x = 1.5;
        Console.WriteLine(--x);
        x = 1.5;
        Console.WriteLine(x--);
        Console.WriteLine(x);
    }
}
/*
Output:
0.5
1.5
0.5
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


&& 运算符（C# 参考）

[image]

C# 语言参考
&& 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

条件“与”运算符 (&&) 执行其 && 操作数的逻辑“与”运算，但仅在必要时才计算第二个操作数。

备注

操作

复制代码
x && y

对应于操作

复制代码
x & y

不同的是，如果 x 为 false，则不计算 y（因为不论 y 为何值，“与”操作的结果都为 false）。这被称作为“短路”计算。

不能重载条件“与”运算符，但常规逻辑运算符和运算符 true 与 false 的重载，在某些限制条件下也被视为条件逻辑运算符的重载。

示例

在下面的示例中，请观察使用 && 的表达式只计算第一个操作数。

C# 复制代码
class LogicalAnd
{
    static bool Method1()
    {
        Console.WriteLine("Method1 called");
        return false;
    }

    static bool Method2()
    {
        Console.WriteLine("Method2 called");
        return true;
    }

    static void Main()
    {
        Console.WriteLine("regular AND:");
        Console.WriteLine("result is {0}", Method1() & Method2());
        Console.WriteLine("short-circuit AND:");
        Console.WriteLine("result is {0}", Method1() && Method2());
    }
}
/*
Output:
regular AND:
Method1 called
Method2 called
result is False
short-circuit AND:
Method1 called
result is False
*/

C# 语言规范

有关更多信息，请参见 C# 语言规范中的以下各章节：

  • 7.11.2 用户定义的条件逻辑运算符

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


|| 运算符（C# 参考）

[image]

C# 语言参考
|| 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

条件“或”运算符 (||) 执行 bool 操作数的逻辑“或”运算，但仅在必要时才计算第二个操作数。

备注

操作

复制代码
x || y

对应于操作

复制代码
x | y

不同的是，如果 x 为 true，则不计算 y（因为不论 y 为何值，“或”操作的结果都为 true）。这被称作为“短路”计算。

不能重载条件“或”运算符，但规则逻辑运算符和运算符 true 与 false 的重载，在某些限制条件下也被视为条件逻辑运算符的重载。

示例

在下面的示例中，请观察使用 || 的表达式只计算第一个操作数。

C# 复制代码
class NullCoalesce
{
    static int? GetNullableInt()
    {
        return null;
    }

    static string GetStringValue()
    {
        return null;
    }

    static void Main()
    {
        // ?? operator example.
        int? x = null;

        // y = x, unless x is null, in which case y = -1.
        int y = x ?? -1;

        // Assign i to return value of method, unless
        // return value is null, in which case assign
        // default value of int to i.
        int i = GetNullableInt() ?? default(int);

        string s = GetStringValue();
        // ?? also works with reference types.
        // Display contents of s, unless s is null,
        // in which case display "Unspecified".
        Console.WriteLine(s ?? "Unspecified");
    }
}

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


<< 运算符（C# 参考）

[image]

C# 语言参考
<< 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

左移运算符 (<<) 将第一个操作数向左移动第二个操作数指定的位数。第二个操作数的类型必须是 int。

备注

如果第一个操作数是 int 或 uint（32 位数），则移位数由第二个操作数的低 5 位给出。

如果第一个操作数是 long 或 ulong（64 位数），则移位数由第二个操作数的低 6 位给出。

第一个操作数的高序位被放弃，低序空位用 0 填充。移位操作从不导致溢出。

用户定义的类型可重载 << 运算符（请参见 operator）；第一个操作数的类型必须为用户定义的类型，第二个操作数的类型必须为 <<。重载二元运算符时，也会隐式重载相应的赋值运算符（如果有）。

示例

C# 复制代码
class MainClass11
{
    static void Main()
    {
        int i = 1;
        long lg = 1;
        Console.WriteLine("0x{0:x}", i << 1);
        Console.WriteLine("0x{0:x}", i << 33);
        Console.WriteLine("0x{0:x}", lg << 33);
    }
}
/*
Output:
0x2
0x2
0x200000000
*/

注释

请注意，i<<1 和 i<<33 给出相同的结果，因为 1 和 33 的低序 5 个位相同。

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


>> 运算符（C# 参考）

[image]

C# 语言参考
>> 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

右移运算符 (>>) 将第一个操作数向右移动第二个操作数所指定的位数。

备注

如果第一个操作数为 int 或 uint（32 位数），则移位数由第二个操作数的低五位给出（第二个操作数 & 0x1f）。

如果第一个操作数为 long 或 ulong（64 位数），则移位数由第二个操作数的低六位给出（第二个操作数 & 0x3f）。

如果第一个操作数为 int 或 long，则右移位是算术移位（高序空位设置为符号位）。如果第一个操作数为 uint 或 ulong 类型，则右移位是逻辑移位（高位填充 0）。

用户定义的类型可重载 >> 运算符；第一个操作数的类型必须为用户定义的类型，第二个操作数的类型必须为 int。有关更多信息，请参见 operator。重载二元运算符时，也会隐式重载相应的赋值运算符（如果有）。

示例

C# 复制代码
class RightShift
{
    static void Main()
    {
        int i = -1000;
        Console.WriteLine(i >> 3);
    }
}
/*
Output:
-125
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


== 运算符（C# 参考）

[image]

C# 语言参考
== 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

对于预定义的值类型，如果操作数的值相等，则相等运算符 (==) 返回 true，否则返回 false。对于 string 以外的引用类型，如果两个操作数引用同一个对象，则 == 返回 true。对于 string 类型，== 比较字符串的值。

备注

用户定义的值类型可重载 == 运算符（请参见 operator）。用户定义的引用类型也可重载 == 运算符，尽管在默认情况下，无论对于预定义的引用类型还是用户定义的引用类型，== 的行为都与上面描述的相同。如果重载 ==，则还必须重载 !=。在枚举时通常允许整型运算。

示例

C# 复制代码
class Equality
{
    static void Main()
    {
        // Numeric equality: True
        Console.WriteLine((2 + 2) == 4);

        // Reference equality: different objects,
        // same boxed value: False.
        object s = 1;
        object t = 1;
        Console.WriteLine(s == t);

        // Define some strings:
        string a = "hello";
        string b = String.Copy(a);
        string c = "hello";

        // Compare string values of a constant and an instance: True
        Console.WriteLine(a == b);

        // Compare string references;
        // a is a constant but b is an instance: False.
        Console.WriteLine((object)a == (object)b);

        // Compare string references, both constants
        // have the same value, so string interning
        // points to same reference: True.
        Console.WriteLine((object)a == (object)c);
    }
}
/*
Output:
True
False
True
False
True
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


!= 运算符（C# 参考）

[image]

C# 语言参考
!= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

如果操作数相等，则不等运算符 (!=) 返回 false，否则，返回 true。为所有类型（包括字符串和对象）预定义了不等运算符。用户定义的类型可重载 != 运算符。

备注

对于预定义的值类型，如果操作数的值不同，则不等运算符 (!=) 返回 true，否则，返回 false。对于 string 以外的引用类型，如果两个操作数引用不同的对象，则 != 返回 true。对于 string 类型，!= 比较字符串的值。

用户定义的值类型可重载 != 运算符（请参见 operator）。用户定义的引用类型也可重载 != 运算符，尽管在默认情况下，无论对于预定义的引用类型还是用户定义的引用类型，!= 的行为都与上面描述的相同。如果重载 !=，则还必须重载 ==。在枚举时通常允许整型运算。

示例

C# 复制代码
class InequalityTest
{
    static void Main()
    {
        // Numeric inequality:
        Console.WriteLine((2 + 2) != 4);

        // Reference equality: two objects, same boxed value
        object s = 1;
        object t = 1;
        Console.WriteLine(s != t);

        // String equality: same string value, same string objects
        string a = "hello";
        string b = "hello";

        // compare string values
        Console.WriteLine(a != b);

        // compare string references
        Console.WriteLine((object)a != (object)b);
    }
}
/*
Output:
False
True
False
False
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


<= 运算符（C# 参考）

[image]

C# 语言参考
<= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

所有数值和枚举类型都定义了“小于等于”关系运算符 (<=)，如果第一个操作数小于或等于第二个操作数，则该运算符将返回 <=，否则返回 <=。

备注

用户定义的类型可重载 <=运算符。有关更多信息，请参见 operator。如果重载 <=，则还必须重载 >=。在枚举时通常允许整型运算。

示例

C# 复制代码
class LTE
{
    static void Main()
    {
        Console.WriteLine(1 <= 1.1);
        Console.WriteLine(1.1 <= 1.1);
    }
}
/*
Output:
True
True
*/

请参见

概念

C# 编程指南

参考

C# 运算符
explicit（C# 参考）

其他资源

C# 参考


>= 运算符（C# 参考）

[image]

C# 语言参考
>= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

所有数值类型和枚举类型都定义“大于等于”关系运算符 >=，如果第一个操作数大于或等于第二个操作数，该运算符将返回 >=，否则返回 >=。

备注

用户定义的类型可重载 >=运算符。有关更多信息，请参见 operator。如果重载 >=，则还必须重载 <=<。在枚举时通常允许整型运算。

示例

C# 复制代码
class GTE
{
    static void Main()
    {
        Console.WriteLine(1.1 >= 1);
        Console.WriteLine(1.1 >= 1.1);
    }
}
/*
Output:
True
True
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


+= 运算符（C# 参考）

[image]

C# 语言参考
+= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

加法赋值运算符。

备注

使用 += 赋值运算符的表达式，例如

复制代码
x += y

等效于

复制代码
x = x + y

不同的是 x 只计算一次。+ 运算符的含义取决于 x 和 y 的类型（对于数值操作数，其含义为相加；对于字符串操作数，其含义为串联，等等）。

不能直接重载 += 运算符，但用户定义的类型可重载 + 运算符（请参见 operator）。

+= 运算符还用于指定响应事件时要调用的方法；这类方法称为事件处理程序。因为事件处理程序封装在委托类型中，所以在此上下文中使用 += 运算符称为“委托串联”。有关更多信息，请参见event（C# 参考）和委托（C# 编程指南）。

示例

C# 复制代码
class AddAssigment
{
    static void Main()
    {
        //addition
        int a = 5;
        a += 6;
        Console.WriteLine(a);

        //string concatenation
        string s = "Hello";
        s += " world.";
        Console.WriteLine(s);
    }
}
/*
Output:
11
Hello world
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


-= 运算符（C# 参考）

[image]

C# 语言参考
-= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

减法赋值运算符。

备注

使用 -= 赋值运算符的表达式，如

复制代码
x -= y

等效于

复制代码
x = x - y

不同的是 x 只计算一次。- 运算符的含义取决于 x 和 y 的类型（例如，对于数值操作数，其含义为相减；对于委托操作数，其含义为移除）。

不能直接重载 -= 运算符，但用户定义的类型可重载 - 运算符（请参见 operator）。

示例

C# 复制代码
class MainClass3
{
    static void Main()
    {
        int a = 5;
        a -= 6;
        Console.WriteLine(a);
    }
}
/*
Output:
-1
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


*= 运算符（C# 参考）

[image]

C# 语言参考
*= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

二元乘法赋值运算符。

备注

使用 *= 赋值运算符的表达式，如

复制代码
x *= y

等效于

复制代码
x = x * y

不同的是 x 只计算一次。为数值类型预定义了 * 运算符以执行乘法操作。

不能直接重载 *= 运算符，但用户定义的类型可重载 * 运算符（请参见 operator）。

示例

C# 复制代码
class MainClass10
{
    static void Main()
    {
        int a = 5;
        a *= 6;
        Console.WriteLine(a);
    }
}
/*
Output:
30
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


/= 运算符（C# 参考）

[image]

C# 语言参考
/= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

除法赋值运算符。

备注

使用 /= 赋值运算符的表达式，如

复制代码
x /= y

等效于

复制代码
x = x / y

不同的是 x 只计算一次。为数值类型预定义了 / 运算符以执行除法操作。

不能直接重载 /= 运算符，但用户定义的类型可重载 / 运算符（请参见 operator）。对于所有复合赋值运算符，隐式重载二元运算符会重载等效的复合赋值。

示例

C# 复制代码
class MainClass2
{
    static void Main()
    {
        int a = 5;
        a /= 6;
        Console.WriteLine(a);
        double b = 5;
        b /= 6;
        Console.WriteLine(b);
    }
}
/*
Output:
0
0.833333333333333
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


%= 运算符（C# 参考）

[image]

C# 语言参考
%= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

模块赋值运算符。

备注

使用 %= 赋值运算符的表达式，如

复制代码
x %= y

等效于

复制代码
x = x % y

不同的是 x 只计算一次。为数值类型预定义了 % 运算符，以计算相除后的余数。

不能直接重载 %= 运算符，但用户定义的类型可重载 % 运算符（请参见 operator（C# 参考））。

示例

C# 复制代码
class Test2
{
    static void Main()
    {
        int a = 5;
        a %= 3;
        Console.WriteLine(a);
    }
}
// Output: 2

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


&= 运算符（C# 参考）

[image]

C# 语言参考
&= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

“与”赋值运算符。

备注

使用 &= 赋值运算符的表达式，例如

复制代码
x &= y

等效于

复制代码
x = x & y

不同的是 x 只计算一次。& 运算符对整数操作数执行按位逻辑“与”运算，对 bool 操作数执行逻辑“与”运算。

不能直接重载 &= 运算符，但用户定义的类型可重载二元 & 运算符（请参见 operator）。

示例

C# 复制代码
class AndAssignment
{
    static void Main()
    {
        int a = 0x0c;
        a &= 0x06;
        Console.WriteLine("0x{0:x8}", a);
        bool b = true;
        b &= false;
        Console.WriteLine(b);
    }
}
/*
Output:
0x00000004
False
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


|= 运算符（C# 参考）

[image]

C# 语言参考
|= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

“或”赋值运算符。

备注

使用 |= 赋值运算符的表达式，例如

复制代码
x |= y

等效于

复制代码
x = x | y

不同的是 x 只计算一次。| 运算符对整型操作数执行按位逻辑“或”运算，对布尔操作数执行逻辑“或”运算。

不能直接重载 |= 运算符，但用户定义的类型可以重载 | 运算符（请参见 operator）。

示例

C# 复制代码
class MainClass7
{
    static void Main()
    {
        int a = 0x0c;
        a |= 0x06;
        Console.WriteLine("0x{0:x8}", a);
        bool b = true;
        b |= false;
        Console.WriteLine(b);
    }
}
/*
Output:
0x0000000e
True
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


^= 运算符（C# 参考）

[image]

C# 语言参考
^= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

“异或”赋值运算符。

备注

下列形式的表达式

复制代码
x ^= y

按如下规则计算：

复制代码
x = x ^ y

不同的是 x 只计算一次。^ 运算符对整数操作数执行按位“异或”运算，对 bool 操作数执行逻辑“异或”运算。

不能直接重载 ^= 运算符，但用户定义的类型可重载 ! 运算符（请参见 operator）。

示例

C# 复制代码
class XORAssignment
{
    static void Main()
    {
        int a = 0x0c;
        a ^= 0x06;
        Console.WriteLine("0x{0:x8}", a);
        bool b = true;
        b ^= false;
        Console.WriteLine(b);
    }
}
/*
Output:
0x0000000a
True
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


<<= 运算符（C# 参考）

[image]

C# 语言参考
<<= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

左移赋值运算符。

备注

下列形式的表达式

复制代码
x <<= y

按如下规则计算：

复制代码
x = x << y

不同的是 x 只计算一次。<< 运算符将 x 向左移动 y 指定的位数。

不能直接重载 <<= 运算符，但用户定义的类型可重载 << 运算符（请参见 operator）。

示例

C# 复制代码
class MainClass9
{
    static void Main()
    {
        int a = 1000;
        a <<= 4;
        Console.WriteLine(a);
    }
}
/*
Output:
16000
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


>>= 运算符（C# 参考）

[image]

C# 语言参考
>>= 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

右移赋值运算符。

备注

下列形式的表达式

复制代码
x >>= y

按如下规则计算：

复制代码
x = x >> y

不同的是 x 只计算一次。>> 运算符根据 y 指定的量对 x 进行右移位。

不能直接重载 >>= 运算符，但用户定义的类型可重载 >> 运算符（请参见 operator）。

示例

C# 复制代码
class MainClass8
{
    static void Main()
    {
        int a = 1000;
        a >>= 4;
        Console.WriteLine(a);
    }
}
/*
Output:
62
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考


-> 运算符（C# 参考）

[image]

C# 语言参考
-> 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

-> 运算符将指针取消引用与成员访问组合在一起。

备注

以下形式的表达式

复制代码
x->y

（其中 x 为 T* 类型的指针，y 为 T 的成员）等效于

复制代码
(*x).y

只能在标记为不安全的代码中使用 -> 运算符。

不能重载 -> 运算符。

示例

C# 复制代码
// compile with: /unsafe

struct Point
{
    public int x, y;
}

class MainClass12
{
    unsafe static void Main()
    {
        Point pt = new Point();
        Point* pp = &pt;
        pp->x = 123;
        pp->y = 456;
        Console.WriteLine("{0} {1}", pt.x, pt.y);
    }
}
/*
Output:
123 456
*/

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 参考

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 将“非托管代码”改成了“标记为不安全的代码”。 | 内容 Bug 修复


?? 运算符（C# 参考）

[image]

C# 语言参考
?? 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

?? 运算符称为 null 合并运算符，用于定义可以为 null 值的类型和引用类型的默认值。如果此运算符的左操作数不为 null，则此运算符将返回左操作数；否则返回右操作数。

备注

可以为 null 的类型可以包含值，或者可以是未定义的。?? 运算符定义当可以为 null 的类型分配给非可以为 null 的类型时返回的默认值。如果在尝试将可以为 null 值的类型分配给不可以为 null 值的类型时没有使用 ?? 运算符，则会生成编译时错误。如果使用强制转换，且当前还未定义可以为 null 值的类型，则会引发 InvalidOperationException 异常。

有关更多信息，请参见可空类型（C# 编程指南）。

即使 ?? 运算符的两个参数都是常量，也不能将其结果视为常量。

示例

C# 复制代码
class NullCoalesce
{
    static int? GetNullableInt()
    {
        return null;
    }

    static string GetStringValue()
    {
        return null;
    }

    static void Main()
    {
        // ?? operator example.
        int? x = null;

        // y = x, unless x is null, in which case y = -1.
        int y = x ?? -1;

        // Assign i to return value of method, unless
        // return value is null, in which case assign
        // default value of int to i.
        int i = GetNullableInt() ?? default(int);

        string s = GetStringValue();
        // ?? also works with reference types.
        // Display contents of s, unless s is null,
        // in which case display "Unspecified".
        Console.WriteLine(s ?? "Unspecified");
    }
}

请参见

概念

C# 编程指南

参考

C# 运算符
可空类型（C# 编程指南）

其他资源

C# 参考
What Exactly Does 'Lifted' mean?（“提升”的准确含义是什么？）


=> 运算符（C# 参考）

[image]

C# 语言参考
=> 运算符（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

=> 标记称作 lambda 运算符。该标记在 lambda 表达式中用来将左侧的输入变量与右侧的 lambda 体分离。Lambda 表达式是与匿名方法类似的内联表达式，但更加灵活；在以方法语法表示的 LINQ 查询中广泛使用了 Lambda 表达式。有关更多信息，请参见 Lambda 表达式（C# 编程指南）。

复制代码
  string[] words = { "cherry", "apple", "blueberry" };
  int shortestWord = words.Min(w => w.Length);

备注

=> 运算符可解读为“goes to”。在上一示例中，该表达式可解读为“Min w goes to w dot Length”。

=> 运算符具有与赋值运算符 (=>) 相同的优先级，并且是右结合运算符。

可以显式指定输入变量的类型或让编译器进行推断；在任一情况下，此变量在编译时都是强类型的。当指定类型时，必须用括号将类型名称和变量名括起，如以下示例所示：

复制代码
int shortestWord = words.Min((string w) => w.Length);

用括号将多个输入变量括起，如本主题稍后的示例所示。

示例

下面的示例演示如何使用两个输入变量为标准查询运算符 Enumerable..::.Where 编写 lambda 表达式。此表达式将返回其长度小于其在数组中的索引位置的所有字符串。

复制代码
  string[] digits = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
  var shortDigits = digits.Where((digit, index) => digit.Length < index);

请参见

概念

C# 编程指南

参考

Lambda 表达式（C# 编程指南）

其他资源

C# 参考


C# 预处理器指令

[image]

C# 语言参考
C# 预处理器指令
另请参见 发送反馈意见

更新：2007 年 11 月

本节讨论 C# 语言的预处理器指令：

#if

#else

#elif

#endif

# define

#undef

#warning

#error

#line

#region

#endregion

#pragma

#pragma warning

#pragma checksum

虽然编译器没有单独的预处理器，但在处理该节中描述的指令时如同存在一个单独的预处理器；这些指令用于辅助条件编译。与 C 和 C++ 指令不同，不能使用这些指令创建宏。

预处理器指令必须是行上的唯一指令。

请参见

任务

“条件方法”示例

概念

C# 编程指南

其他资源

C# 参考


#if（C# 参考）

[image]

C# 语言参考
#if（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

#if 使您可以开始条件指令，测试一个或多个符号以查看它们是否计算为 true。如果它们的计算结果确实为 true，则编译器将计算位于 #if 与最近的 #endif 指令之间的所有代码。例如，

复制代码
#define DEBUG
// ...
#if DEBUG
    Console.WriteLine("Debug version");
#endif

可以使用运算符 ==（相等）、!=（不相等）、&&（与）及 ||（或）来计算多个符号。还可以用括号将符号和运算符分组。

备注

使用 #if 以及 #else、#elif、#endif、#define 和 #undef 指令，可以包括或排除基于由一个或多个符号组成的条件的代码。这在编译调试版本的代码或编译特定配置时最为有用。

以 #if 指令开始的条件指令必须用 #endif 指令显式终止。

#define 使您可以定义一个符号，通过将该符号用作传递给 #if 指令的表达式，使该表达式计算为 true。

也可以用 /define 编译器选项来定义符号。可以用 #undef 来取消定义符号。

用 /define 或 #define 定义的符号与具有同一名称的变量不冲突。即，不应将变量名传递到预处理器指令，并且只能用预处理器指令计算符号。

用 #define 创建的符号的范围是在其中定义该符号的文件。

示例

复制代码
// preprocessor_if.cs
#define DEBUG#define VC_V7
using System;
public class MyClass
{
    static void Main()
    {
#if (DEBUG && !VC_V7)
        Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && VC_V7)
        Console.WriteLine("VC_V7 is defined");
#elif (DEBUG && VC_V7)
        Console.WriteLine("DEBUG and VC_V7 are defined");
#else
        Console.WriteLine("DEBUG and VC_V7 are not defined");
#endif
    }
}
复制代码
DEBUG and VC_V7 are defined

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#else（C# 参考）

[image]

C# 语言参考
#else（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

#else 允许您创建复合条件指令，因此，如果前面的 #if 或（可选）#elif 指令中的任何表达式都不为 true，则编译器将计算 #else 与后面的 #endif 之间的所有代码。

备注

#endif 必须是 #else 之后的下一条预处理器指令。有关如何使用 #else 的示例，请参见 #if。

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#elif（C# 参考）

[image]

C# 语言参考
#elif（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

#elif 使您得以创建复合条件指令。如果前面的 #if（C# 参考） 和前面的任何 #elif（可选）指令表达式的计算结果都不是 #elif，则将计算 #elif 表达式。如果 #elif 表达式计算为 true，编译器将计算位于 #elif 和下一个条件指令之间的所有代码。例如：

复制代码
#define VC7
//...
#if debug
    Console.Writeline("Debug build");
#elif VC7
    Console.Writeline("Visual Studio 7");
#endif

可以使用运算符 ==（相等）、==（不相等）、==（与）及 ==（或）来计算多个符号。还可以用括号将符号和运算符分组。

备注

#elif 等效于使用：

复制代码
#else
#if

使用 #elif 更简单，因为每个 #elif 都需要一个 #endif（C# 参考），而 #elif 即使在没有匹配的 #elif 时也可以使用。

有关如何使用 #elif 的示例，请参见 #if（C# 参考）。

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#endif（C# 参考）

[image]

C# 语言参考
#endif（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

#endif 指定以 #if 指令开头的条件指令的结尾。例如，

复制代码
#define DEBUG
// ...
#if DEBUG
    Console.WriteLine("Debug version");
#endif

备注

以 #if 指令开始的条件指令必须用 #endif 指令显式终止。有关如何使用 #endif 的示例，请参见 #if（C# 参考）。

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#define（C# 参考）

[image]

C# 语言参考
#define（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

#define 可让您定义符号。当您将符号用作传递给 #if 指令的表达式时，此表达式的计算结果为 true。例如：

#define DEBUG

备注

说明：

不能像在 C 和 C++ 中的通常做法一样，使用 #define 指令来声明常数值。最好是将 C# 中的常数定义为类或结构的静态成员。如果具有多个像这样的常数，可以考虑创建一个单独的“Constants”类来保存这些常数。

符号可用于指定编译的条件。可以使用 #if 或 #elif 来测试符号。还可以使用 conditional 属性执行条件编译。

可以定义符号，但是无法对符号赋值。#define 指令必须在使用任何也不是指令的指令之前出现在文件中。

也可以用 /define 编译器选项来定义符号。可以用 #undef 来取消定义符号。

用 /define 或 #define 定义的符号与具有同一名称的变量不冲突。即，不应将变量名传递到预处理器指令，并且只能用预处理器指令计算符号。

用 #define 创建的符号的范围是在其中定义该符号的文件。

有关如何使用 #define 的示例，请参见 #if。

请参见

概念

C# 编程指南

参考

C# 预处理器指令
const（C# 参考）

其他资源

C# 参考


#undef（C# 参考）

[image]

C# 语言参考
#undef（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

#undef 使您可以取消符号的定义，以便通过将该符号用作 #if 指令中的表达式，使表达式的计算结果为 false。

可以使用 #define 指令或 /define 编译器选项定义符号。在使用任何不是指令的语句之前，必须在文件中使用 #undef 指令。

示例

复制代码
// preprocessor_undef.cs
// compile with: /d:DEBUG
#undef DEBUG
using System;
class MyClass
{
    static void Main()
    {
#if DEBUG
        Console.WriteLine("DEBUG is defined");
#else
        Console.WriteLine("DEBUG is not defined");
#endif
    }
}
复制代码
DEBUG is not defined

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#warning（C# 参考）

[image]

C# 语言参考
#warning（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

#warning 使您得以从代码的特定位置生成一级警告。例如：

复制代码
#warning Deprecated code in this method.

备注

#warning 通常用在条件指令中。也可以用 #error（C# 参考） 生成用户定义的错误。

示例

复制代码
// preprocessor_warning.cs
// CS1030 expected
#define DEBUG
class MainClass
{
    static void Main()
    {
#if DEBUG
#warning DEBUG is defined
#endif
    }
}

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#error（C# 参考）

[image]

C# 语言参考
#error（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

#error 使您可以从代码中的特定位置生成错误。例如：

复制代码
#error Deprecated code in this method.

备注

#error 通常用在条件指令中。

也可以用 #warning（C# 参考） 生成用户定义的警告。

示例

复制代码
// preprocessor_error.cs
// CS1029 expected
#define DEBUG
class MainClass
{
    static void Main()
    {
#if DEBUG
#error DEBUG is defined
#endif
    }
}

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#line（C# 参考）

[image]

C# 语言参考
#line（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

#line 使您可以修改编译器的行号以及（可选）错误和警告的文件名输出。下面的示例说明如何报告与行号关联的两个警告。#line 200 指令强迫行号为 200（尽管默认值为 #7）。另一行 (#9) 作为默认 #line 指令的结果跟在通常序列后。

复制代码
class MainClass
{
    static void Main()
    {
#line 200
        int i;    // CS0168 on line 200
#line default
        char c;   // CS0168 on line 9
    }
}

备注

#line 指令可能由生成过程中的自动中间步骤使用。例如，如果行从原始的源代码文件中移除，但是您仍希望编译器基于文件中的原始行号生成输出，则可以移除行，然后用 #line 模拟原始行号。

#line hidden 指令对调试器隐藏若干连续的行，这样当开发人员在逐句通过代码时，将会跳过 #line hidden 和下一个 #line 指令（假定它不是另一个 #line hidden 指令）之间的所有行。此选项也可用来使 ASP.NET 能够区分用户定义的代码和计算机生成的代码。尽管 ASP.NET 是此功能的主要使用者，但很可能将有更多的源生成器使用它。

#line hidden 指令不会影响错误报告中的文件名或行号。即，如果在隐藏块中遇到错误，编译器将报告当前文件名和错误的行号。

#line filename 指令指定您希望出现在编译器输出中的文件名。默认情况下，使用源代码文件的实际名称。文件名必须括在双引号 ("") 中。

源代码文件可以具有 #line 指令的任何编号。

示例 1

下面的示例说明调试器如何忽略代码中的隐藏行。运行此示例时，它将显示三行文本。但是，当设置如示例所示的断点并按 F10 键逐句通过代码时，您将看到调试器忽略了隐藏行。还请注意，即使在隐藏行上设置断点，调试器仍会忽略它。

复制代码
// preprocessor_linehidden.cs
using System;
class MainClass
{
    static void Main()
    {
        Console.WriteLine("Normal line #1."); // Set break point here.
#line hidden
        Console.WriteLine("Hidden line.");
#line default
        Console.WriteLine("Normal line #2.");
    }
}

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#region（C# 参考）

[image]

C# 语言参考
#region（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

#region 使您可以在使用 Visual Studio 代码编辑器的大纲显示功能时指定可展开或折叠的代码块。例如：

复制代码
#region MyClass definition
public class MyClass
{
    static void Main()
    {
    }
}
#endregion

备注

#region 块必须以 #endregion 指令终止。

#region 块不能与 #if 块重叠。但是，可以将 #region 块嵌套在 #if 块内，或将 #if 块嵌套在 #region 块内。

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#endregion（C# 参考）

[image]

C# 语言参考
#endregion（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

#endregion 标记 #region 块的结尾。例如：

复制代码
#region MyClass definition
class MyClass
{
    static void Main()
    {
    }
}
#endregion

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#pragma（C# 参考）

[image]

C# 语言参考
#pragma（C# 参考）
另请参见 发送反馈意见

更新：2007 年 11 月

#pragma 为编译器提供特殊的指令，以说明如何编译包含杂注的文件。这些指令必须是编译器支持的指令。也就是说，不能使用 #pragma 创建自定义预处理指令。Microsoft C# 编译器支持以下两个 #pragma 指令：

#pragma warning（C# 参考）

#pragma checksum（C# 参考）

#pragma pragma-name pragma-arguments

参数

pragma-name

可识别杂注的名称。

pragma-arguments

特定于杂注的参数。

请参见

概念

C# 编程指南

参考

C# 预处理器指令
#pragma warning（C# 参考）
#pragma checksum（C# 参考）

其他资源

C# 参考

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 根据客户建议更正了相应的文本。 | 内容 Bug 修复


#pragma warning（C# 参考）

[image]

C# 语言参考
#pragma warning（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

#pragma warning 可启用或禁用某些警告。

#pragma warning disable warning-list
#pragma warning restore warning-list

参数

warning-list

警告编号的逗号分隔列表。只输入数字，不包括前缀 "CS"。

当没有指定警告编号时，disable 禁用所有警告，而 restore 启用所有警告。

示例

复制代码
// pragma_warning.cs
using System;

#pragma warning disable 414, 3021
[CLSCompliant(false)]
public class C
{
    int i = 1;
    static void Main()
    {
    }
}
#pragma warning restore 3021
[CLSCompliant(false)]  // CS3021
public class D
{
    int i = 1;
    public static void F()
    {
    }
}

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


#pragma checksum（C# 参考）

[image]

C# 语言参考
#pragma checksum（C# 参考）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

生成源文件的校验和，以帮助调试 ASP.NET 页。

#pragma checksum "filename" "{guid}" "checksum bytes"

参数

"filename"

要求监视更改或更新的文件的名称。

"{guid}"

文件的全局唯一标识符 (GUID)。

"checksum_bytes"

十六进制数的字符串，表示校验和的字节。必须是偶数位的十六进制数。奇数位的十六进制数字会导致编译时警告，然后指令被忽略。

备注

Visual Studio 调试器使用校验和来确保找到的总是正确的源。编译器计算源文件的校验和，然后将输出发出到程序数据库 (PDB) 文件。最后，调试器使用 PDB 来比较它为源文件计算的校验和。

此解决方案不适用于 ASP.NET 项目，因为算出的校验和是生成的源文件的校验和，而不是 .aspx 文件的校验和。为解决此问题，#pragma checksum 为 ASP.NET 页提供了校验和支持。

在 Visual C# 中创建 ASP.NET 项目时，生成的源文件包含 .aspx 文件（从该文件生成源文件）的校验和。然后，编译器将此信息写入 PDB 文件。

如果编译器在该文件中没有遇到 #pragma checksum 指令，它将计算校验和，然后将算出的值写入 PDB 文件。

示例

复制代码
class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{3673e4ca-6098-4ec1-890f-8fceb2a794a2}" "{012345678AB}" // New checksum
    }
}

请参见

概念

C# 编程指南

参考

C# 预处理器指令

其他资源

C# 参考


C# 编译器选项

[image]

C# 语言参考
C# 编译器选项
发送反馈意见

更新：2007 年 11 月

编译器产生可执行 (.exe) 文件、动态链接库文件 (.dll) 或者代码模块 (.netmodule)。

每个编译器选项均以两种形式提供：-option 和 /option。本文档仅显示 /option 形式。

本节内容

命令行生成

有关从命令行生成 Visual C# 应用程序的信息。

如何：从命令行生成

提供运行 vsvars32.bat 以启用命令行编译的步骤。

部署 C# 应用程序

描述用于部署 C# 应用程序的选项。

按类别列出的 C# 编译器选项

编译器选项的分类列表。

按字母顺序列出的 C# 编译器选项

按字母顺序排序的编译器选项列表。

编译器错误

对 C# 编译器生成的错误的引用。

相关章节

如何：设置生成属性 (C#)。

设置控制项目的编译、生成和调试方法的属性。包含有关在 Visual C# 项目中自定义生成步骤的信息。

默认与自定义生成

有关生成类型和配置的信息。

准备和管理生成

在 Visual Studio 开发环境内生成的过程。


在命令行上使用 csc.exe 生成

[image]

Visual C# 参考：错误和警告
在命令行上使用 csc.exe 生成
另请参见 发送反馈意见

更新：2007 年 11 月

可以通过在命令行上键入 C# 编译器的可执行文件 (csc.exe) 的名称来调用 C# 编译器。如果您使用“Visual Studio 命令提示”（以“开始”菜单上“Visual Studio 工具”下的快捷方式提供），则系统已为您设置好所有必要的环境变量。否则，必须调整路径，以便使 csc.exe 可从计算机上的任何子目录中调用。如果不使用“Visual Studio 命令提示”，则必须运行 vsvars32.bat 以设置适当的环境变量来支持命令行生成。有关 vsvars32.bat 的更多信息，请参见如何：设置环境变量。

如果您使用的计算机只安装有 Windows 软件开发工具包 (SDK)，则可在使用“SDK 命令提示”（可通过“Microsoft .NET Framework SDK”菜单选项访问）时在命令行上使用 C# 编译器。

若要在开发环境中生成，请参见如何：准备和管理生成。

也可以使用 MSBuild 通过编程方式生成 C# 程序。有关更多信息，请参见 MSBuild。

csc.exe 可执行文件通常位于系统目录下的 Microsoft.NET\Framework\<version> 文件夹中。根据每台计算机上的确切配置，此位置可能有所不同。如果计算机上安装有 .NET Framework 的多个版本，则计算机上将存在此可执行文件的多个版本。有关此类安装的更多信息，请参见安装 .NET Framework 的多个版本。

本主题涵盖以下内容：

命令行语法规则

命令行示例

C# 编译器和 C++ 编译器输出之间的差异

C# 编译器的命令行语法规则

在解释操作系统命令行上给出的参数时，C# 编译器使用下列规则：

  • 参数用空白分隔，空白可以是一个空格或制表符。

  • ^ 字符 (^) 未被识别为转义符或者分隔符。该字符在被传递给程序中的 argv 数组前，由操作系统的命令行分析器进行处理。

  • 无论其中是否包含空白，带双引号的字符串 ("string") 均被解释为单个参数。带引号的字符串可以嵌入在参数内。

  • 前面有反斜杠的双引号 (\") 被解释为原义双引号字符 (")。

  • 反斜杠按其原义解释，除非它们紧位于双引号之前。

  • 如果偶数个反斜杠后跟双引号，则每对反斜杠中有一个反斜杠放置在 argv 数组中，而双引号被解释为字符串分隔符。

  • 如果奇数个反斜杠后跟双引号，则每对反斜杠中有一个反斜杠放置在 argv 数组中，而双引号由剩余那个反斜杠进行“转义”。这会将双引号字符 (") 添加到 argv 中。

C# 编译器的示例命令行

  • 编译 File.cs 以产生 File.exe：

    复制代码
    csc File.cs

  • 编译 File.cs 以产生 File.dll：

    复制代码
    csc /target:library File.cs

  • 编译 File.cs 并创建 My.exe：

    复制代码
    csc /out:My.exe File.cs

  • 通过使用优化和定义 DEBUG 符号，编译当前目录中的所有 C# 文件。输出为 File2.exe：

    复制代码
    csc /define:DEBUG /optimize /out:File2.exe *.cs

  • 编译当前目录中的所有 C# 文件，以生成 File2.dll 的调试版本。不显示任何徽标和警告：

    复制代码
    csc /target:library /out:File2.dll /warn:0 /nologo /debug *.cs

  • 将当前目录中的所有 C# 文件编译为 Something.xyz（一个 DLL）：

    复制代码
    csc /target:library /out:Something.xyz *.cs

C# 编译器和 C++ 编译器输出之间的差异

没有任何对象 (.obj) 文件是由于调用 C# 编译器而创建的；输出文件是直接创建的。因此，C# 编译器不需要链接器。

请参见

参考

按字母顺序列出的 C# 编译器选项
按类别列出的 C# 编译器选项

其他资源

C# 编译器选项


如何：设置环境变量

[image]

C# 语言参考
如何：设置环境变量
另请参见 发送反馈意见

更新：2007 年 11 月

vsvars32.bat 文件用于设置适当的环境变量以启用命令行生成。有关 vsvars32.bat 的更多信息，请参见知识库文章“Q248802: You receive the "Out of environment space" error message when you execute the Vcvars32.bat batch file”（Q248802：执行 Vcvars32.bat 批处理文件时收到“环境空间不足”错误消息）。

如果安装 Visual Studio 的当前版本的计算机上也安装有 Visual Studio 的早期版本，则不要在同一命令提示窗口中运行来自不同版本的 vsvars32.bat 或 vcvars32.bat。

运行 VSVARS32.BAT

  1. 在命令提示下，转到安装的 Common7\Tools 子目录。

  2. 键入 VSVARS32 以运行 VSVARS32.bat。

    警告：

    VSVARS32.bat 在不同的计算机中会有所不同。不要用另一计算机中的 VSVARS32.bat 替换缺少或损坏的 VSVARS32.bat 文件。重新运行安装程序以替换丢失的文件。

请参见

概念

在命令行上使用 csc.exe 生成


部署 C# 应用程序

[image]

C# 语言参考
部署 C# 应用程序
另请参见 发送反馈意见

更新：2007 年 11 月

当您完成生成您的 C# 应用程序之后，下一步是分发 C# 应用程序。C# 是一种 .NET 语言；因此，将任何 C# 可执行文件分发到其他计算机上要求在每台执行计算机上安装 .NET Framework（可能还包括特定于您的应用程序的其他依赖项）。您有各种可用于分发 .NET Framework 的选项。有关概述，请参见重新发布 .NET Framework。

将完成的应用程序移动到其他计算机上的过程通常称为“部署”。Microsoft 的开发环境提供了部署机制；有关更多信息，请参见部署应用程序和组件。

如果您主要是从命令行生成和分发，您可能需要考虑其他部署和再分发依赖项的方法。

请参见

概念

在命令行上使用 csc.exe 生成


按类别列出的 C# 编译器选项

[image]

C# 语言参考
按类别列出的 C# 编译器选项
另请参见 发送反馈意见

更新：2007 年 11 月

下列编译器选项按类别排序。有关按字母顺序排序的列表，请参见按字母顺序列出的 C# 编译器选项。

优化
选项 | 用途
/filealign | 指定输出文件中节的大小。
/optimize | 启用/禁用优化。
输出文件
选项 | 用途
/doc | 指定要将处理的文档注释写入到其中的 XML 文件。
/out | 指定输出文件。
/pdb | 指定 .pdb 文件的文件名和位置。
/platform | 指定输出平台。
/target | 使用下列四个选项之一指定输出文件的格式：/target:exe、/target:library、/target:module 或 /target:winexe
.NET Framework 程序集
选项 | 用途
/addmodule | 指定一个或多个模块作为此程序集的一部分。
/delaysign | 指示编译器添加公钥，但将此程序集保留为未签名状态。
/keycontainer | 指定加密密钥容器的名称。
/keyfile | 指定包含加密密钥的文件名。
/lib | 指定通过 /reference 引用的程序集的位置。
/nostdlib | 指示编译器不导入标准库 (mscorlib.dll)。
/reference | 从包含程序集的文件中导入元数据。
调试/错误检查
选项 | 用途
/bugreport | 创建一个文件，该文件包含有助于报告 bug 的信息。
/checked | 指定溢出数据类型边界的整数算法是否将在运行时导致异常。
/debug | 指示编译器发出调试信息。
/errorreport | 设置错误报告行为。
/fullpaths | 指定编译器输出中的文件的绝对路径。
/nowarn | 取消编译器生成指定警告的功能。
/warn | 设置警告等级。
/warnaserror | 将警告提升为错误。
预处理器
选项 | 用途
/define | 定义预处理器符号。
资源
选项 | 用途
/linkresource | 创建到托管资源的链接。
/resource | 将一个 .NET Framework 资源嵌入到输出文件中。
/win32icon | 指定插入到输出文件中的 .ico 文件。
/win32res | 指定插入到输出文件中的 Win32 资源。
杂项
选项 | 用途
@ | 指定响应文件。
/? | 将编译器选项列出到 stdout。
/baseaddress | 指定加载 DLL 的首选基址。
/codepage | 指定要用于编译中所有源代码文件的代码页。
/help | 将编译器选项列出到 stdout。
/langversion | 指定要使用的语言版本。
/main | 指定 Main 方法的位置。
/noconfig | 指示编译器不使用 csc.rsp 进行编译。
/nologo | 不显示编译器版权标志信息。
/recurse | 在子目录中搜索要编译的源文件。
/unsafe | 允许编译使用 unsafe 关键字的代码。
/utf8output | 使用 UTF-8 编码显示编译器输出。

已过时的选项

/incremental

启用增量编译。

请参见

任务

如何：设置环境变量

参考

按字母顺序列出的 C# 编译器选项

其他资源

C# 编译器选项


按字母顺序列出的 C# 编译器选项

[image]

C# 语言参考
按字母顺序列出的 C# 编译器选项
另请参见 发送反馈意见

更新：2007 年 11 月

下列编译器选项按字母顺序排序。有关分类列表，请参见按类别列出的 C# 编译器选项。

选项 | 用途
@ | 读取响应文件以获得更多选项。
/? | 将用法信息显示到 stdout。
/addmodule | 将指定的模块链接到此程序集中
/baseaddress | 指定要生成的库的基址。
/bugreport | 创建“Bug 报告”文件。如果与 /errorreport:prompt 或 /errorreport:send 一起使用，则将此文件与任意故障信息一起发送。
/checked | 使编译器生成溢出检查。
/codepage | 指定打开源文件时使用的代码页。
/debug | 发出调试信息。
/define | 定义条件编译符号。
/delaysign | 仅使用强名称密钥的公共部分对程序集进行延迟签名。
/doc | 指定要生成的 XML 文档文件。
/errorreport | 指定如何处理内部编译器错误：prompt、send 或 none。默认值为 none。
/filealign | 指定对输出文件节使用的对齐方式。
/fullpaths | 使编译器生成完全限定路径。
/help | 将用法信息显示到 stdout。
/incremental | 启用增量编译 [obsolete]。
/keycontainer | 指定强名称密钥容器。
/keyfile | 指定强名称密钥文件。
/langversion | 指定语言版本模式：ISO-1 或 Default。
/lib | 指定要在其中搜索引用的附加目录。
/linkresource | 将指定的资源链接到此程序集。
/main | 指定包含入口点的类型（忽略所有其他可能的入口点）。
/noconfig | 指示编译器不自动包含 CSC.RSP 文件。
/nologo | 取消显示编译器版权信息。
/nostdlib | 指示编译器不引用标准库 (mscorlib.dll)。
/nowarn | 禁用特定的警告消息
/nowin32manifest | 指示编译器不将应用程序清单嵌入到可执行文件中。
/optimize | 启用/禁用优化。
/out | 指定输出文件名（默认值：包含主类的文件或第一个文件的基名称）。
/pdb | 指定 .pdb 文件的文件名和位置。
/platform | 限定此代码可以在其上运行的平台：x86、Itanium、x64 或 anycpu。默认值为 anycpu。
/recurse | 按照通配符规范，包括当前目录和子目录中的所有文件。
/reference | 从指定的程序集文件引用元数据。
/resource | 嵌入指定的资源。
/target | 使用下列四个选项之一指定输出文件的格式：/target:exe、/target:library、/target:module 和 /target:winexe。
/unsafe | 允许使用不安全代码。
/utf8output | 以 UTF-8 编码格式输出编译器消息。
/warn | 设置警告等级 (0-4)。
/warnaserror | 将特定的警告报告为错误。
/win32icon | 对输出使用此图标。
/win32wanifest | 指定自定义 win32 清单文件。
/win32res | 指定 win32 资源文件 (.res)。

请参见

任务

如何：设置环境变量

参考

按类别列出的 C# 编译器选项

其他资源

C# 编译器选项


@（指定响应文件）（C# 编译器选项）

[image]

C# 语言参考
@（指定响应文件）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

@ 选项使您可以指定包含编译器选项和要编译的源代码文件的文件。

@response_file

参数

response_file

一个列出编译器选项或要编译的源代码文件的文件。

备注

编译器在处理编译器选项和源代码文件时将它们视为是在命令行上指定的。

若要在一次编译中指定多个响应文件，请指定多个响应文件选项。例如：

复制代码
@file1.rsp @file2.rsp

在响应文件中，多个编译器选项和源代码文件可以出现在同一行中。单个编译器选项的指定必须出现在同一行中（不能跨行）。响应文件可以带有以 # 符号开始的注释。

从响应文件内部指定编译器选项就如同在命令行发出这些命令。有关更多信息，请参见从命令行生成。

编译器在遇到命令选项时会进行处理。因此，命令行参数可以重写先前在响应文件中列出的选项。反之，响应文件中的选项也将重写先前在命令行或其他响应文件中列出的选项。

C# 提供 csc.rsp 文件，该文件与 csc.exe 文件位于同一目录中。有关 csc.rsp 的更多信息，请参见 /noconfig。

不能在 Visual Studio 开发环境中设置此编译器选项，也不能以编程方式对其进行更改。

示例

以下几行来自一个示例响应文件：

复制代码
# build the first output file
/target:exe /out:MyExe.exe source1.cs source2.cs

请参见

其他资源

C# 编译器选项


/addmodule（导入元数据）（C# 编译器选项）

[image]

C# 语言参考
/addmodule（导入元数据）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此选项将一个使用 target:module 开关创建的模块添加到当前的编译中。

/addmodule:file[;file2]

参数

file, file2

包含元数据的输出文件。该文件不能包含程序集清单。若要导入多个文件，请用逗号或分号分隔文件名。

备注

运行时，所有用 /addmodule 添加的模块必须与输出文件位于同一目录中。也就是说，编译时可以在任何目录中指定模块，但是在运行时模块必须在应用程序目录中。如果模块在运行时不在应用程序目录中，您将遇到 TypeLoadException。

file 不能包含程序集。例如，如果输出文件用 /target:module 创建，则其元数据可以用 /addmodule 导入。

如果输出文件用 /target 选项而不是 /target:module 创建，则其元数据无法用 /addmodule 导入，但是可以用 /reference 导入。

此编译器选项在 Visual Studio 中不可用；项目不能引用模块。另外，不能以编程方式更改此编译器选项。

示例

编译源文件 input.cs 并从 metad1.netmodule 和 metad2.netmodule 中添加元数据来产生 out.exe：

复制代码
csc /addmodule:metad1.netmodule;metad2.netmodule /out:out.exe input.cs

请参见

任务

如何：生成多文件程序集

概念

多文件程序集

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/baseaddress（指定 DLL 的基址）（C# 编译器选项）

[image]

C# 语言参考
/baseaddress（指定 DLL 的基址）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/baseaddress 选项使您可以指定加载 DLL 的首选基址。有关何时和为何使用此选项的更多信息，请参见 Improving Application Startup Time和 Larry Osterman's WebLog（Larry Osterman 的网络日志）。

/baseaddress:address

参数

address

DLL 的基址。可以将该地址指定为十进制、十六进制或八进制数。

备注

DLL 的默认基址由 .NET Framework 公共语言运行时设置。

请注意：该地址中低位的数将会被舍入。例如，如果指定 0x11110001，它将被舍入为 0x11110000。

若要完成 DLL 的签名过程，请结合使用 SN.EXE 和 -R 选项。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 单击“高级”按钮。

  4. 修改“DLL 基址”属性。

    若要以编程方式设置此编译器选项，请参见 BaseAddress。

请参见

参考

ProcessModule..::.BaseAddress

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/bugreport（报告问题）（C# 编译器选项）

[image]

C# 语言参考
/bugreport（报告问题）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

指定应将调试信息放在文件中，供以后分析使用。

/bugreport:file

参数

file

要包含 bug 报告的文件的名称。

备注

/bugreport 选项指定应将下面的信息放在 file 中：

  • 编译中所有源代码文件的副本。

  • 编译中使用的编译器选项列表。

  • 有关编译器、运行时和操作系统的版本信息。

  • 引用的程序集和模块（保存为十六进制数），.NET Framework 和 SDK 附带的程序集除外。

  • 编译器输出（如果有）。

  • 将会提示给您的问题的说明。

  • 关于考虑问题应如何解决（就此将会向您提示）的说明。

如果此选项与 /errorreport:prompt 或 /errorreport:send 一起使用，则文件中的信息将发送至 Microsoft Corporation。

由于所有源代码文件的副本都将放置在 file 中，因此可能需要在尽可能短的程序中重现所怀疑的代码缺陷。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

注意，生成文件的内容会公开源代码，可能导致意外的信息泄露。

请参见

参考

/errorreport（设置错误报告行为）（C# 编译器选项）

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/checked（检查整数算法）（C# 编译器选项）

[image]

C# 语言参考
/checked（检查整数算法）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/checked 选项指定，不在 checked 或 unchecked 关键字的范围内、并且产生的值超出数据类型范围的整数算法语句是否将导致运行时异常。

/checked[+ | <U>-</U>]

备注

checked 或 unchecked 关键字范围内的整数算法语句不受 /checked 选项的影响。

如果不在 checked 或 unchecked 关键字范围内的整数算法语句产生的值超出数据类型范围，并且编译中使用了 /checked+ (/checked)，则该语句将在运行时导致异常。如果编译中使用的是 /checked-，则该语句在运行时不会导致异常。

此选项的默认值是 /checked-，所以只需完全省略此选项，就可以获得相同的效果。使用 /checked- 的一个情况是在生成大应用程序时：有时使用自动化工具生成这类应用程序，这些工具可能自动将 /checked 设置为 +，这时您可以通过指定 /checked- 重写全局默认值。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。有关更多信息，请参见如何：设置生成属性 (C#)。

  2. 单击“生成”属性页。

  3. 单击“高级”按钮。

  4. 修改“检查算法上溢/下溢”属性。

若要以编程方式访问此编译器选项，请参见 CheckForOverflowUnderflow。

示例

编译 t2.cs，并指定不在 checked 或 unchecked 关键字范围内并且产生的值超出数据类型范围的整数算法语句将在运行时导致异常。

复制代码
csc t2.cs /checked

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/codepage（指定源代码文件的代码页）（C# 编译器选项）

[image]

C# 语言参考
/codepage（指定源代码文件的代码页）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

此选项指定在编译期间，当所需的页不是系统当前的默认代码页时使用的代码页。

/codepage:id

参数

id

编译中用于所有源代码文件的代码页的 ID。

备注

如果您编译的一个或多个源代码文件没有被创建为使用计算机上的默认代码页，您可以使用 /codepage 选项指定应使用哪个代码页。/codepage 适用于编译中的所有源代码文件。

如果源代码文件是用计算机中的同一有效代码页创建的，或者是用 UNICODE 或 UTF-8 创建的，则不需要使用 /codepage。

有关如何查找系统所支持的代码页的信息，请参见 GetCPInfo。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/debug（发出调试信息）（C# 编译器选项）

[image]

C# 语言参考
/debug（发出调试信息）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/debug 选项使编译器生成调试信息并将其放置在一个或多个输出文件中。

/debug[+ | <U>-</U>]
/debug:{<U>full</U> | pdbonly}

参数

+ | -

指定 + 或只指定 /debug 将使编译器生成调试信息，并将这些信息放置在一个程序数据库（.pdb 文件）中。指定 -（在不指定 /debug 时有效）将导致不创建任何调试信息。

full | pdbonly

指定编译器生成的调试信息类型。full 参数在没有指定 /debug:pdbonly 时有效，它允许将调试器附加到正在运行的程序。指定 pdbonly 允许在调试器中启动程序时进行源代码调试，但仅在正在运行的程序附加到调试器时才显示汇编程序。

备注

使用该选项创建调试版本。如果未指定 /debug、/debug+ 或 /debug:full，则无法调试程序的输出文件。

如果使用 /debug:full，请注意 /debug:full 可能会对 JIT 优化代码的速度和大小有一些影响，并且可能会稍微影响代码质量。我们建议在生成发布代码时使用 /debug:pdbonly 或不使用 PDB。

说明：

/debug:pdbonly 和 /debug:full 的一个区别是：使用 /debug:full 时，编译器会发出 DebuggableAttribute，用于通知 JIT 编译器有可用的调试信息。因此，如果使用 /debug:full，而代码中包含设置为 false 的 DebuggableAttribute，则会发生错误。

有关如何配置应用程序的调试性能的信息，请参见使映像更易于调试。

若要更改 .pdb 文件的位置，请参见 /pdb（指定调试符号文件）（C# 编译器选项）。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 单击“高级”按钮。

  4. 修改“调试信息”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 DebugSymbols。

示例

将调试信息放置在输出文件 app.pdb 中：

复制代码
csc /debug /out:app.pdb test.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/define（预处理器定义）（C# 编译器选项）

[image]

C# 语言参考
/define（预处理器定义）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

/define 选项将 name 定义为程序的所有源代码文件中的一个符号。

/define:name[;name2]

参数

name, name2

要定义的一个或多个符号的名称。

备注

使用 /define 选项与使用 #define 预处理器指令具有相同的效果，不同点在于编译器选项对项目中的所有文件都有效。在源文件中的 #undef 指令移除定义之前，符号在源文件中始终保持已定义状态。使用 /define 选项时，一个文件中的 #undef 指令不会影响到项目中的其他源代码文件。

可以将用该选项创建的符号与 #if、#else、#elif 和 #endif 一起使用，以按条件编译源文件。

/d 是 /define 的缩写形式。

通过使用分号或逗号分隔符号名称，可以使用 /define 定义多个符号。例如：

复制代码
/define:DEBUG;TUESDAY

C# 编译器自身没有定义可以在源代码中使用的符号或宏；所有符号定义必须是用户定义的。

说明：

与 C++ 这样的语言相同，C# #define 不允许给符号赋值。例如，不能使用 #define 创建宏或定义常数。如果您需要定义常数，请使用 enum 变量。如果您希望创建 C++ 样式的宏，请考虑其他方式，例如泛型。由于宏有容易出错的坏名声，因此 C# 不允许使用宏，而是提供了其他更安全的选择。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 在“生成”选项卡上，键入要在“条件编译符号”框中定义的符号。例如，如果要使用下面的代码示例，只需在文本框中键入 xx。

有关如何以编程方式设置此编译器选项的信息，请参见 DefineConstants。

示例

复制代码
// preprocessor_define.cs
// compile with: /define:xx
// or uncomment the next line
// #define xx
using System;
public class Test
{
    public static void Main()
    {
        #if (xx)
            Console.WriteLine("xx defined");
        #else
            Console.WriteLine("xx not defined");
        #endif
    }
}

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 修订了“备注”部分的信息，以指示在找到 #undef 指令之前该选项对所有文件都有效。 | 内容 Bug 修复


/delaysign（延迟为程序集签名）（C# 编译器选项）

[image]

C# 语言参考
/delaysign（延迟为程序集签名）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

此选项将使编译器在输出文件中保留空间，以便以后添加数字签名。

/delaysign[ + | - ]

参数

+ | -

如果需要完全签名的程序集，则使用 /delaysign-。如果只想将公钥放在程序集中，则使用 /delaysign+。默认值为 /delaysign-。

备注

如果不与 /keyfile 或 /keycontainer 一起使用，/delaysign 选项将无效。

如果要求完全签名的程序集，编译器将对包含清单（程序集元数据）的文件进行散列处理，并用私钥对该散列数据进行签名。产生的数字签名存储在包含清单的文件中。当果程序集的签名延迟时，编译器将不会计算和存储签名，但会在文件中保留空间以便以后添加签名。

例如，使用 /delaysign+ 允许测试人员将程序集放入全局缓存中。测试完成后，可以通过使用程序集链接器实用工具将私钥放入程序集中对程序集进行完全签名。

有关更多信息，请参见创建和使用具有强名称的程序集和延迟为程序集签名。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 修改“仅延迟签名”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 DelaySign。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/doc（处理文档注释）（C# 编译器选项）

[image]

C# 语言参考
/doc（处理文档注释）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/doc 选项允许在 XML 文件中放置文档注释。

/doc:file

参数

file

XML 的输出文件，由编译的源代码文件中的注释填充。

备注

在源代码文件中，可处理以下内容之前的文档注释，并将其添加到 XML 文件中：

  • 用户定义的类型，如 class、delegate 或 interface

  • 成员，如字段、事件、属性或方法

包含 Main 的源代码文件首先输出到 XML。

若要将生成的 .xml 文件用于Intellisense功能，请使该 .xml 文件的文件名与要支持的程序集同名，然后确保该 .xml 文件放入与该程序集相同的目录中。这样，在 Visual Studio 项目中引用程序集时，也可找到该 .xml 文件。有关更多信息，请参见提供代码注释。

除非用 /target:module 进行编译，否则 file 将包含 标记，以指定包含编译输出文件的程序集清单的文件名。

说明：

/doc 选项适用于所有输入文件；或者，如果此选项是在“项目设置”中设置的，则适用于项目中的所有文件。若要禁用与特定的文件或代码段的文档注释相关的警告，请使用 #pragma warning。

有关从代码中的注释生成文档的方法，请参见建议的文档注释标记。

有关示例，请参见 “XML 文档”示例。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 修改“XML 文档文件”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 DocumentationFile。

请参见

任务

“XML 文档”示例

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/errorreport（设置错误报告行为）（C# 编译器选项）

[image]

C# 语言参考
/errorreport（设置错误报告行为）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

使用此选项可以方便地向 Microsoft 报告 C# 内部编译器错误。

说明：

在 Windows Vista 和 Windows Server 2008 上，为 Visual Studio 做出的错误报告设置不会替代通过 Windows 错误报告 (WER) 做出的设置。WER 设置始终优先于 Visual Studio 的错误报告设置。

/errorreport:{ none | prompt | queue | send }

参数

none

不收集或向 Microsoft 发送有关内部编译器错误的报告。

prompt

提示您在收到内部编译器错误时发送报告。在开发环境中编译应用程序时，prompt 是默认值。

queue

将错误报告加入队列。当使用管理凭据登录时，将打开一个弹出窗口并允许您报告自上次登录以来的任何失败。将不会提示您每三天发送失败报告不超过一次。当从命令行编译应用程序时，queue 是默认值。

send

自动将内部编译器错误报告发送到 Microsoft。若要启用此选项，必须首先同意 Microsoft 的数据收集策略。首次在计算机上指定 /errorreport:send 时，编译器消息将引导您访问包含 Microsoft 的数据收集策略的网站。

备注

当编译器无法处理源代码文件时，将导致内部编译器错误 (ICE)。当发生 ICE 时，编译器不生成输出文件或可用来修复代码的任何有用的诊断。

在以前的版本中，当收到 ICE 时，最好联系 Microsoft 技术支持部门以报告问题。现在通过使用 /errorreport，您可以向 Visual C# 团队提供 ICE 信息。错误报告有助于改进将来的编译器版本。

用户发送报告的能力取决于计算机和用户策略权限。

有关错误调试器的更多信息，请参见 Description of the Dr. Watson for Windows (Drwtsn32.exe) Tool（Dr. Watson for Windows (Drwtsn32.exe) 工具的说明）。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。有关更多信息，请参见如何：设置生成属性 (C#)。

  2. 单击“生成”属性页。

  3. 单击“高级”按钮。

  4. 修改“内部编译器错误报告”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 ErrorReport。

请参见

其他资源

C# 编译器选项


/filealign（指定节对齐）（C# 编译器选项）

[image]

C# 语言参考
/filealign（指定节对齐）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/filealign 选项使您可以指定输出文件中的节大小。

/filealign:number

参数

number

指定输出文件中节大小的值。有效值为 512、1024、2048、4096 和 8192。这些值以字节为单位。

备注

每个节将在是 /filealign 值的倍数的边界上对齐。没有固定的默认值。如果未指定 /filealign，则公共语言运行时在编译时将选取一个默认值。

通过指定节的大小，可以影响输出文件的大小。修改节的大小可能对将在较小设备上运行的程序有用。

使用 DUMPBIN 查看有关输出文件中的节信息。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 单击“高级”按钮。

  4. 修改“文件对齐”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 FileAlignment。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/fullpaths（在编译器输出中指定完全限定路径）（C# 编译器选项）

[image]

C# 语言参考
/fullpaths（在编译器输出中指定完全限定路径）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/fullpaths 选项可使编译器在列出编译错误和警告时指定文件的完整路径。

/fullpaths

备注

默认情况下，编译所产生的错误和警告指定包含错误的文件名。 /fullpaths 选项使编译器指定文件的完整路径。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

请参见

其他资源

C# 编译器选项


/help，/? （编译器命令行帮助）（C# 编译器选项）

[image]

C# 语言参考
/help，/? （编译器命令行帮助）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

该选项将编译器选项的列表和每个选项的简短说明发送到 stdout。

/help
/?

备注

如果编译中包含该选项，将不会创建输出文件，也不会进行编译。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/keycontainer（指定强名称密钥容器）（C# 编译器选项）

[image]

C# 语言参考
/keycontainer（指定强名称密钥容器）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

指定加密密钥容器的名称。

/keycontainer:string

参数

string

强名称密钥容器的名称。

备注

使用 /keycontainer 选项时，通过将来自所指定容器的公钥插入到程序集清单中并且用私钥签名最终程序集，编译器可创建可共享的组件。若要生成密钥文件，请在命令行上键入 sn -k file。使用 sn -i 命令可将密钥对安装到容器中。

如果使用 /target:module 进行编译，则将密钥文件的名称保存在模块中，并在使用 /addmodule 编译程序集时将其包含到该程序集中。

还可以将此选项指定为任何 Microsoft 中间语言 (MSIL) 模块的源代码中的自定义属性 (System.Reflection..::.AssemblyKeyNameAttribute)。

也可以通过 /keyfile 将加密信息传递给编译器。如果希望将公钥添加到程序集清单中，但将程序集的签名延迟到该程序集通过测试，则请使用 /delaysign。

有关更多信息，请参见创建和使用具有强名称的程序集和延迟为程序集签名。

在 Visual Studio 开发环境中设置此编译器选项

  • 此编译器选项在 Visual Studio 开发环境中不可用。

您可以使用 AssemblyKeyContainerName 以编程方式访问此编译器选项。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/keyfile（指定强名称密钥文件）（C# 编译器选项）

[image]

C# 语言参考
/keyfile（指定强名称密钥文件）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

指定包含加密密钥的文件名。

/keyfile:file

参数

术语 | 定义
file | 包含强名称密钥的文件的名称。

备注

使用此选项时，编译器将从指定的文件将公钥插入程序集清单中，然后使用私钥对最终程序集进行签名。若要生成密钥文件，请在命令行上键入 sn -k file。

如果使用 /target:module 进行编译，则将密钥文件的名称保存在模块中，并在使用 /addmodule 编译程序集时将其包含到创建的程序集中。

也可以使用 /keycontainer 将加密信息传递给编译器。如果需要部分签名的程序集，则使用 /delaysign。

如果在相同的编译中指定了 /keyfile 和 /keycontainer（通过命令行选项或自定义属性），编译器将首先尝试密钥容器。如果成功，则使用密钥容器中的信息对程序集进行签名。如果编译器没有找到密钥容器，则将尝试用 /keyfile 指定的文件。如果成功，则使用密钥文件中的信息对程序集进行签名，并且将把密钥信息安装到密钥容器中（类似 sn -i），这样，下次编译时密钥容器将是有效的。

请注意，密钥文件可能只包含公钥。

有关更多信息，请参见创建和使用具有强名称的程序集和延迟为程序集签名。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“签名”属性页。

  3. 修改“选择强名称密钥文件”属性。

可以使用 AssemblyOriginatorKeyFile 以编程方式访问此编译器选项。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/langversion（符合规范的语法）（C# 编译器选项）

[image]

C# 语言参考
/langversion（符合规范的语法）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

导致编译器只接受 ISO/IEC 23270:2003 C# 语言规范中包含的语法。

/langversion:option

参数

option

如果 option 为 ISO-1，则对于未在 ISO/IEC 23270:2003 C# 语言规范中的任何语法，编译器将生成一个错误。

如果 option 为 ISO-2，则对于未在 ISO/IEC 23270:2006 C# 语言规范中的任何语法，编译器将生成一个错误。

如果 option 为 default，则编译器将接受所有有效的语言语法。/langversion:default 是默认值。

备注

C# 规范的版本 1.0 表示可用于 /langversion:ISO-1 的功能。Visual Studio 开发人员中心包含 Microsoft Word 文件格式的所有规范。ISO/IEC 23270:2006 规范可以从以下网址获取：http://standards.iso.org/ittf/PubliclyAvailableStandards/c042926_ISO_IEC_23270_2006(E).zip。

C# 应用程序所引用的元数据不受 /langversion 编译器选项的影响。

由于 C# 编译器的每个版本都包含语言规范的扩展，因此 /langversion 不提供该编译器早期版本的等效功能。

无论您使用的是何种 /langversion 设置，都将使用当前版本的公共语言运行时来创建 .exe 或 .dll。这种情况的一个例外是友元程序集和 /moduleassemblyname（指定模块的友元程序集）（C# 编译器选项），它们用于 /langversion:ISO-1。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。有关详细信息，请参见如何：设置生成属性 (C#)。

  2. 单击“生成”属性页。

  3. 单击“高级”按钮。

  4. 修改“语言版本”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 LanguageVersion。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/lib（指定程序集引用位置）（C# 编译器选项）

[image]

C# 语言参考
/lib（指定程序集引用位置）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/lib 选项指定通过 /reference（导入元数据）（C# 编译器选项） 选项引用的程序集的位置。

/lib:dir1[,dir2]

参数

dir1

在当前工作目录（调用编译器的目录）或公共语言运行时的系统目录中未找到引用的程序集时，编译器将在其中进行查找的目录。

dir2

要在其中搜索程序集引用的一个或多个附加目录。用逗号分隔每个附加目录的名称，中间不要有空格。

备注

编译器按以下顺序搜索未完全限定的程序集引用：

  1. 当前工作目录。该目录为从其调用编译器的目录。

  2. 公共语言运行时系统目录。

  3. 由 /lib 指定的目录。

  4. 由 LIB 环境变量指定的目录。

使用 /reference 指定程序集引用。

/lib 是累加的；每一次指定的值都追加到以前的值中。

另一种使用 /lib 的方法是，将任何所需的程序集复制到工作目录中；这使您可以仅将程序集名称传递给 /reference。然后可以从工作目录中删除这些程序集。由于程序集清单中未指定依赖程序集的路径，因此应用程序可以在目标计算机上启动，然后查找并使用全局程序集缓存中的程序集。

编译器可以引用程序集并不表示公共语言运行时可以在运行时找到并加载程序集。有关运行时如何搜索引用的程序集的详细信息，请参见 运行库如何定位程序集。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性页”对话框。

  2. 单击“引用路径”属性页。

  3. 修改列表框的内容。

有关如何以编程方式设置此编译器选项的信息，请参见 ReferencePath。

示例

编译 t2.cs 以创建 .exe 文件。编译器将在工作目录和驱动器 C 上根目录中查找程序集引用。

复制代码
csc /lib:c:\ /reference:t2.dll t2.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/linkresource（链接到 .NET Framework 资源）（C# 编译器选项）

[image]

C# 语言参考
/linkresource（链接到 .NET Framework 资源）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

在输出文件中创建指向 .NET Framework 资源的链接。该资源文件未添加到输出文件中。这与 /resource 选项不同，后者会将资源文件嵌入到输出文件中。

/linkresource:filename[,identifier[,accessibility-modifier]]

参数

filename

要从程序集链接的 .NET Framework 资源文件。

identifier（可选）

资源的逻辑名称；用于加载资源的名称。默认为文件的名称。

accessibility-modifier（可选）

资源的可访问性：公共或私有。默认为公共。

备注

默认情况下，如果链接的资源是用 C# 编译器创建的，则链接资源在程序集中就是公共的。若要使这些资源成为私有的，请将 private 指定为可访问性修饰符。不允许使用 public 或 private 以外的其他修饰符。

/linkresource 需要除 /target:module 选项之外的 /target 选项之一。

如果 filename 是由例如 Resgen.exe 创建或在开发环境中创建的 .NET Framework 资源文件，则可以通过 System.Resources 命名空间中的成员访问该文件。有关更多信息，请参见 System.Resources..::.ResourceManager。对于所有其他资源，请使用 Assembly 类中的 GetManifestResource* 方法在运行时访问资源。

在 filename 中指定的文件可以为任何格式。例如，您可能想将本机 DLL 设置为程序集的一部分，以便可将其安装到全局程序集缓存中，并且可从程序集中的托管代码访问它。下面的第二个示例演示如何执行此操作。您可以在程序集链接器中执行相同的操作。下面的第三个示例演示如何执行此操作。有关更多信息，请参见程序集链接器 (Al.exe)和使用程序集和全局程序集缓存。

/linkres 是 /linkresource 的缩写。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

示例

编译 in.cs 并链接到资源文件 rf.resource：

复制代码
csc /linkresource:rf.resource in.cs

将 A.cs 编译为 DLL，链接到本机 DLL N.dll，并将输出放置在全局程序集缓存 (GAC) 中。在此示例中，A.dll 和 N.dll 都驻留在 GAC 中。

复制代码
csc /linkresource:N.dll /t:library A.cs
gacutil -i A.dll

此示例和前一个示例执行的是同样的操作，但使用的是程序集链接器选项。

复制代码
csc /t:module A.cs
al /out:A.dll A.netmodule /link:N.dll
gacutil -i A.dll

请参见

参考

程序集链接器 (Al.exe)

其他资源

C# 编译器选项
使用程序集和全局程序集缓存
项目属性 (Visual Studio)


/main（指定 Main 方法的位置）（C# 编译器选项）

[image]

C# 语言参考
/main（指定 Main 方法的位置）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

如果有多个类包含 Main 方法，此选项指定包含程序入口点的类。

/main:class

参数

class

包含 Main 方法的类型。

备注

如果编译包括多个具有 Main 方法的类型，可以指定哪个类型包含要用作程序入口点的 Main 方法。

此选项用在编译 .exe 文件时。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“应用程序”属性页。

  3. 修改“启动对象”属性。

    若要以编程方式设置此编译器选项，请参见 StartupObject。

示例

编译 t2.cs 和 t3.cs，并指定将在 Test2 中找到 Main 方法：

复制代码
csc t2.cs t3.cs /main:Test2

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/moduleassemblyname（指定模块的友元程序集）（C# 编译器选项）

[image]

C# 语言参考
/moduleassemblyname（指定模块的友元程序集）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

指定 .netmodule 可访问其非公共类型的程序集。

/moduleassemblyname:assembly_name

参数

assembly_name

.netmodule 可以访问其非公共类型的程序集的名称。

备注

生成 .netmodule 并满足以下条件时，应使用 /moduleassemblyname：

  • .netmodule 需要具有访问现有程序集中非公共类型的权限。

  • 您知道 .netmodule 将生成到的程序集的名称。

  • 现有程序集已经授予 .netmodule 将生成到的程序集友元程序集访问权限。

有关生成 .netmodule 的更多信息，请参见 /target:module（创建模块以添加到程序集）（C# 编译器选项）。

有关友元程序集的更多信息，请参见 友元程序集（C# 编程指南）。

此选项在开发环境中不可用；它仅在从命令行编译时才可用。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

示例

此示例生成具有私有类型的程序集，并且该程序集授予称为 csman_an_assembly 的程序集友元程序集访问权限。

复制代码
// moduleassemblyname_1.cs
// compile with: /target:library
using System;
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo ("csman_an_assembly")]

class An_Internal_Class
{
    public void Test()
    {
        Console.WriteLine("An_Internal_Class.Test called");
    }
}

此示例生成可访问程序集 moduleassemblyname_1.dll 中非公共类型的 .netmodule。知道该 .netmodule 将生成到称为 csman_an_assembly 的程序集后，我们就可以指定 /moduleassemblyname，允许 .netmodule 访问已授予 csman_an_assembly 友元程序集访问权限的程序集中的非公共类型。

复制代码
// moduleassemblyname_2.cs
// compile with: /moduleassemblyname:csman_an_assembly /target:module /reference:moduleassemblyname_1.dll
class B {
    public void Test() {
        An_Internal_Class x = new An_Internal_Class();
        x.Test();
    }
}

此代码示例通过引用以前生成的程序集和 .netmodule，生成程序集 csman_an_assembly。

复制代码
// csman_an_assembly.cs
// compile with: /addmodule:moduleassemblyname_2.netmodule /reference:moduleassemblyname_1.dll
class A {
    public static void Main() {
        B bb = new B();
        bb.Test();
    }
}
复制代码
An_Internal_Class.Test called

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/noconfig（忽略 csc.rsp）（C# 编译器选项）

[image]

C# 语言参考
/noconfig（忽略 csc.rsp）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/noconfig 选项通知编译器不要使用 csc.rsp 文件进行编译，该文件与 csc.exe 文件位于同一目录中，并从该目录中加载。

/noconfig

备注

csc.rsp 文件引用 .NET Framework 附带的所有程序集。Visual Studio .NET 开发环境包括的实际引用具体取决于项目类型。

可以修改 csc.rsp 文件并指定其他编译器选项，而这些选项是那些应该包括在使用 csc.exe 的来自命令行的每次编译中的选项（/noconfig 选项除外）。

编译器会保留上次传递给 csc 命令的选项。因此，命令行上的任何选项都会重写 csc.rsp 文件中同一选项的设置。

如果不希望编译器查找并使用 csc.rsp 文件中的设置，请指定 /noconfig。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/nologo（取消显示版权标志信息）（C# 编译器选项）

[image]

C# 语言参考
/nologo（取消显示版权标志信息）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/nologo 选项在编译器启动时取消显示登录版权标志，并在编译期间取消显示信息性消息。

/nologo

备注

此选项在开发环境中不可用；它仅在从命令行编译时才可用。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/nowin32manifest（不包含 Win32 清单)（C# 编辑器选项）

[image]

C# 语言参考
/nowin32manifest（不包含 Win32 清单)（C# 编辑器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

使用 /nowin32manifest 选项可指示编译器不将任何应用程序清单嵌入到可执行文件中。

/nowin32manifest

备注

使用此选项时，除非在 Win32 资源文件或以后的生成步骤中提供应用程序清单，否则应用程序会受到 Windows Vista 上虚拟化的影响。有关虚拟化的更多信息，请参见 Windows Vista Developer Story：针对用户帐户控制的 Windows Vista 应用程序开发要求。

在 Visual Studio 的“应用程序属性”页中，通过在“清单”下拉列表中选择“创建不带清单的应用程序”选项来设置此选项。有关更多信息，请参见“项目设计器”->“应用程序”页 (C#)。

有关创建清单的更多信息，请参见 /win32manifest（导入自定义 Win32 清单文件）（C# 编译器选项）。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/nostdlib（不导入标准库）（C# 编译器选项）

[image]

C# 语言参考
/nostdlib（不导入标准库）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/nostdlib 禁止导入定义整个 System 命名空间的 mscorlib.dll。

/nostdlib[<U>+</U> | -]

备注

如果您希望定义或创建自己的 System 命名空间和对象，请使用该选项。

如果不指定 /nostdlib，mscorlib.dll 将导入程序（与指定 /nostdlib- 相同）。指定 /nostdlib 与指定 /nostdlib+ 的作用相同。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 单击“高级”按钮。

  4. 修改“不引用 mscorlib.dll”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 NoStdLib。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/nowarn（取消显示指定警告）（C# 编译器选项）

[image]

C# 语言参考
/nowarn（取消显示指定警告）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/nowarn 选项允许您禁止编译器显示一个或多个警告。用逗号分隔多个警告编号。

/nowarn:number1[,number2,...]

参数

number1, number2

您希望编译器取消的警告编号。

备注

只应指定警告标识符的数值部分。例如，若要取消 CS0028，可以指定 /nowarn:28。

编译器在无人参与的模式下忽略传递给 /nowarn 的警告编号，这些警告编号在先前版本中有效但已从编译器中移除。例如，CS0679 在 Visual Studio .NET 2002 中有效但后来被删除。

/nowarn 选项无法禁止显示以下警告：

编译器警告（等级 1）CS2002

编译器警告（等级 1）CS2023

编译器警告（等级 1）CS2029

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。有关详细信息，请参见如何：设置生成属性 (C#)。

  2. 单击“生成”属性页。

  3. 修改“取消警告”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 DelaySign。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/optimize（启用/禁用优化）（C# 编译器选项）

[image]

C# 语言参考
/optimize（启用/禁用优化）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/optimize 选项启用或禁用由编译器执行以使输出文件更小、更快和更有效的优化。

/optimize[+ | <U>-</U>]

备注

/optimize 选项还通知公共语言运行时在运行时优化代码。

默认情况下，将禁用优化。指定 /optimize+ 以启用优化。

在生成要由程序集使用的模块时，请使用与该程序集的设置相同的 /optimize 设置。

/o 是 /optimize 的缩写形式。

可以组合 /optimize 和 /debug 选项。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 修改“优化代码”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 Optimize。

示例

编译 t2.cs 并启用编译器优化：

复制代码
csc t2.cs /optimize

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/out（设置输出文件名）（C# 编译器选项）

[image]

C# 语言参考
/out（设置输出文件名）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/out 选项指定输出文件的名称。

/out:filename

参数

filename

由编译器创建的输出文件的名称。

备注

在命令行中，可以为编译指定多个输出文件。编译器希望在 /out 选项之后找到一个或多个源代码文件。这样一来，所有的源代码文件都将被编译到 /out 选项所指定的那个输出文件中。

指定要创建的文件的完整名称和扩展名。

如果不指定输出文件的名称：

  • .exe 文件将从包含 Main 方法的源代码文件中获取其名称。

  • .dll 或 .netmodule 文件将从第一个源代码文件中获取其名称。

用于编译一个输出文件的源代码文件不能在相同的编译中用作另一个输出文件的编译。

当在命令行编译中产生多个输出文件时，请记住其中只有一个输出文件可以是程序集并且只有用 /out 隐式或显式指定的第一个输出文件才是程序集。

作为编译的一部分产生的任何模块都变成与编译中产生的所有程序集相关的文件。使用 ildasm.exe 浏览程序集清单以查看相关的文件。

为使 EXE 成为友元程序集的目标，需要 /out 编译器选项。有关更多信息，请参见友元程序集（C# 编程指南）。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“应用程序”属性页。

  3. 修改“程序集名称”属性。

    若要以编程方式设置此编译器选项：OutputFileName 是只读属性，它由项目类型（exe、库等）和程序集名称的组合确定。必须修改这两个属性中的一个或全部才能设置输出文件名。

示例

编译 t.cs 并创建输出文件 t.exe，同时又生成 t2.cs 并创建模块输出文件 mymodule.netmodule：

复制代码
csc t.cs /out:mymodule.netmodule /target:module t2.cs

请参见

参考

友元程序集（C# 编程指南）

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/pdb（指定调试符号文件）（C# 编译器选项）

[image]

C# 语言参考
/pdb（指定调试符号文件）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/pdb 编译器选项指定调试符号文件的名称和位置。

/pdb:filename

参数

filename

调试符号文件的名称和位置。

备注

当您指定 /debug（发出调试信息）（C# 编译器选项） 时，编译器将在创建输出文件（.exe 或 .dll）的同一目录下创建一个与输出文件同名的 .pdb 文件。

/pdb 允许您为 .pdb 文件指定非默认的文件名和位置。

不能在 Visual Studio 开发环境中设置此编译器选项，也不能以编程方式对其进行更改。

示例

编译 t.cs 并创建一个名为 tt.pdb 的 .pdb 文件：

复制代码
csc /debug /pdb:tt t.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/platform（指定输出平台）（C# 编译器选项）

[image]

C# 语言参考
/platform（指定输出平台）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

指定哪个版本的公共语言运行时 (CLR) 可以运行程序集。

/platform:string

参数

string

x86、Itanium、x64 或 anycpu（默认值）。

备注

  • x86 将程序集编译为由兼容 x86 的 32 位公共语言运行时运行。

  • Itanium 将程序集编译为由采用 Itanium 处理器的计算机上的 64 位公共语言运行时运行。

  • x64 将程序集编译为由支持 AMD64 或 EM64T 指令集的计算机上的 64 位公共语言运行时运行。

  • anycpu（默认值）将程序集编译为在任意平台上运行。

在 64 位 Windows 操作系统上：

  • 用 /platform:x86 编译的程序集将在 WOW64 下运行的 32 位 CLR 上执行。

  • 用 /platform:anycpu 编译的可执行文件将在 64 位 CLR 上执行。

  • 用 /platform:anycpu 编译的 DLL 将在与加载它的进程相同的 CLR 上执行。

有关开发在 Windows 64 位操作系统上运行的应用程序的更多信息，请参见 64 位应用程序。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 修改“目标平台”属性。

注意 /platform 在 Visual C# 速成版开发环境中不可用。

有关如何以编程方式设置此编译器选项的信息，请参见 PlatformTarget。

示例

下面的示例演示如何使用 /platform 选项来指定应用程序只应由用于 Itanium 的 64 位 Windows 操作系统上的 64 位 CLR 运行。

复制代码
csc /platform:Itanium myItanium.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/recurse（在子目录中查找源文件）（C# 编译器选项）

[image]

C# 语言参考
/recurse（在子目录中查找源文件）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/recurse 选项使您可以编译指定目录 (dir) 或项目目录的所有子目录中的源代码文件。

/recurse:[dir\]file

参数

dir（可选）

搜索开始的目录。如果未指定此目录，则搜索从项目目录开始。

file

要搜索的文件。允许使用通配符字符。

备注

/recurse 选项使您可以编译指定目录 (dir) 或项目目录的所有子目录中的源代码文件。

可以在文件名中使用通配符来编译项目目录中所有匹配的文件，而不需使用 /recurse。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

示例

编译当前目录中的所有 C# 文件：

复制代码
csc *.cs

编译 dir1\dir2 目录及其任何子目录中的所有 C# 文件，并生成 dir2.dll：

复制代码
csc /target:library /out:dir2.dll /recurse:dir1\dir2\*.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/reference（导入元数据）（C# 编译器选项）

[image]

C# 语言参考
/reference（导入元数据）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/reference 选项导致编译器将指定文件中的 public（C# 参考） 类型信息导入到当前项目中，从而使您可以从指定的程序集文件引用元数据。

/reference:[alias=]filename
/reference:filename

参数

filename

包含程序集清单的文件的名称。若要导入多个文件，请为每个文件包括一个单独的 /reference 选项。

alias

一个有效的 C# 标识符，表示将包含程序集中所有命名空间的根命名空间。

备注

若要从多个文件导入，请为每个文件包括一个 /reference 选项。

导入的文件必须包含一个清单；输出文件必须已使用 /target:module（创建模块以添加到程序集）（C# 编译器选项） 以外的 /target（指定输出文件格式）（C# 编译器选项） 选项之一编译过。

/r 是 /reference 的缩写形式。

使用 /addmodule（导入元数据）（C# 编译器选项） 从不包含程序集清单的输出文件导入元数据。

如果您引用了一个程序集（程序集 A），其本身又引用了另一个程序集（程序集 B），则在下列情况下需要引用程序集 B：

  • 使用来自程序集 A 的类型继承自程序集 B 中的类型或实现程序集 B 中的接口。

  • 调用具有程序集 B 中的返回类型或参数类型的字段、属性、事件或方法。

使用 /lib（指定程序集引用位置）（C# 编译器选项） 指定一个或多个程序集引用所在的目录。/lib 主题还讨论了编译器在哪些目录中搜索程序集。

为使编译器可以识别程序集（而不是模块）中的某个类型，需要强制解析此类型，这可以通过定义此类型的实例来完成。还有其他方法可为编译器解析程序集中的类型名称：例如，如果您从程序集中的类型继承，编译器就能识别类型名称。

有时必须从一个程序集内部引用同一组件的两个不同版本。为此，请在每个文件的 /reference 开关上使用 alias 子选项，以区分两个文件。此别名将用作组件名称的限定符，并解析为其中一个文件中的组件。

默认情况下使用 csc 响应 (.rsp) 文件，该文件引用常用的 .NET Framework 程序集。如果不希望编译器使用 csc.rsp，请使用 /noconfig（忽略 csc.rsp）（C# 编译器选项）。

有关更多信息，请参见 “添加引用”对话框。

示例

此示例演示如何使用 外部别名（C# 参考） 功能。

编译源文件，并从先前已编译的 grid.dll 和 grid20.dll 中导入元数据。这两个 DLL 包含同一组件的不同版本，您将使用两个带有别名选项的 /reference 编译源文件。这两个选项如下所示：

/reference:GridV1=grid.dll 和 /reference:GridV2=grid20.dll

这将设置外部别名“GridV1”和“GridV2”，您将在程序中通过外部语句使用它们：

复制代码
extern GridV1;
extern GridV2;
// Using statements go here.

完成此操作后，您可以通过在控件名称前添加 GridV1 前缀来引用 grid.dll 中的网格控件，如下所示：

复制代码
GridV1::Grid

此外，您可以通过在控件名称前添加 GridV2 前缀来引用 grid20.dll 中的网格控件，如下所示：

复制代码
GridV2::Grid

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/resource（将资源文件嵌入输出文件中）（C# 编译器选项）

[image]

C# 语言参考
/resource（将资源文件嵌入输出文件中）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

将指定的资源嵌入到输出文件中。

/resource:filename[,identifier[,accessibility-modifier]]

参数

filename

要在输出文件中嵌入的 .NET Framework 资源文件。

identifier（可选）

资源的逻辑名称；用于加载资源的名称。默认为文件的名称。

accessibility-modifier（可选）

资源的可访问性：公共或私有。默认为公共。

备注

使用 /linkresource 将资源链接到程序集，但不将资源文件添加到输出文件中。

默认情况下，如果资源是用 C# 编译器创建的，则该资源在程序集中就是公共的。若要使这些资源成为私有的，请将 private 指定为可访问性修饰符。不允许使用 public 或 private 以外的可访问性。

如果 filename 是由例如 Resgen.exe 创建或在开发环境中创建的 .NET Framework 资源文件，则可以通过 System.Resources 命名空间中的成员访问该文件。有关更多信息，请参见 System.Resources..::.ResourceManager。对于所有其他资源，请使用 Assembly 类中的 GetManifestResource* 方法在运行时访问资源。

/res 是 /resource 的缩写形式。

资源在输出文件中的顺序是由命令行上指定的顺序决定的。

在 Visual Studio 开发环境中设置此编译器选项

  1. 将资源文件添加到项目中。

  2. 在“解决方案资源管理器”中，选择要嵌入的文件。

  3. 在“属性”窗口中，为该文件选择“生成操作”。

  4. 将“生成操作”设置为“嵌入的资源”。

有关如何以编程方式设置此编译器选项的信息，请参见 BuildAction。

示例

编译 in.cs 并附加资源文件 rf.resource：

复制代码
csc /resource:rf.resource in.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/target（指定输出文件格式）（C# 编译器选项）

[image]

C# 语言参考
/target（指定输出文件格式）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/target 编译器选项可以指定为以下四种形式之一：

/target:exe

创建 .exe 文件。

/target:library

创建代码库。

/target:module

创建模块。

/target:winexe

创建 Windows 程序。

如果不指定 /target:module，/target 会将 .NET Framework 程序集清单放入输出文件中。有关更多信息，请参见 公共语言运行库中的程序集 和 全局属性。

程序集清单放置在编译中的第一个 .exe 输出文件中，如果没有 .exe 输出文件，会放置在第一个 DLL 中。例如，在以下的命令行中，清单将放置在 1.exe 中：

复制代码
csc /out:1.exe t1.cs /out:2.netmodule t2.cs

编译器每次编译只创建一个程序集清单。关于编译中所有文件的信息全放在程序集清单中。除用 /target:module 创建的文件之外，所有输出文件都可以包含程序集清单。在命令行生成多个输出文件时，只能创建一个程序集清单，且必须放置在命令行上指定的第一个输出文件中。无论第一个输出文件是什么（/target:exe、/target:winexe、/target:library 或 /target:module），在同一编译中生成的任何其他输出文件都必须是模块 (/target:module)。

如果创建了一个程序集，则可以用 CLSCompliant 属性指示全部或部分代码是符合 CLS 的。

复制代码
// target_clscompliant.cs
[assembly:System.CLSCompliant(true)]   // specify assembly compliance

[System.CLSCompliant(false)]   // specify compliance for an element
public class TestClass
{
    public static void Main() {}
}

有关以编程方式设置此编译器选项的更多信息，请参见 OutputType。

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/target:exe（创建控制台应用程序）（C# 编译器选项）

[image]

C# 语言参考
/target:exe（创建控制台应用程序）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/target:exe 选项使编译器创建可执行 (EXE) 的控制台应用程序。

/target:exe

备注

默认情况下，/target:exe 选项有效。将创建带扩展名 .exe 的可执行文件。

使用 /target:winexe 创建可执行的 Windows 程序。

除非另外使用 /out 选项指定，否则输出文件名采用包含 Main 方法的输入文件名。

在命令行上指定该选项时，下一个 /out 或 /target:module 选项之前的所有文件都将用于创建 .exe 文件

在编译到 .exe 文件中的源代码文件中要求有且仅有一个 main 方法。/main 编译器选项使您可以在代码中包含多个具有 main 方法的类的情况下，指定包含 main 方法的类。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“应用程序”属性页。

  3. 修改“输出类型”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 OutputType。

示例

下列每个命令行都将编译 in.cs，创建 in.exe：

复制代码
csc /target:exe in.cs
csc in.cs

请参见

参考

/target（指定输出文件格式）（C# 编译器选项）

其他资源

C# 编译器选项


/target:library（创建代码库）（C# 编译器选项）

[image]

C# 语言参考
/target:library（创建代码库）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/target:library 选项使编译器创建一个动态链接库 (DLL) 而不是一个可执行文件 (EXE)。

/target:library

备注

DLL 创建后会带有 .dll 扩展名。

除非使用 /out 选项另外指定，否则输出文件的名称采用第一个输入文件的名称。

在命令行上指定该选项时，下一个 /out 或 /target:module 选项之前的所有文件都将用于创建 .dll 文件。

生成 .dll 文件时，不需要 main 方法。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“应用程序”属性页。

  3. 修改“输出类型”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 OutputType。

示例

编译 in.cs，创建 in.dll：

复制代码
csc /target:library in.cs

请参见

参考

/target（指定输出文件格式）（C# 编译器选项）

其他资源

C# 编译器选项


/target:module（创建模块以添加到程序集）（C# 编译器选项）

[image]

C# 语言参考
/target:module（创建模块以添加到程序集）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

此选项导致编译器不会生成程序集清单。

/target:module

备注

默认情况下，使用此选项编译时所创建的输出文件具有 .netmodule 扩展名。

没有程序集清单的文件无法由 .NET Framework 公共语言运行库加载。但可以通过 /addmodule 将这类文件并入程序集清单中。

如果在一次编译中创建了多个模块，一个模块中的 internal 类型可用于此编译中的其他模块。如果一个模块中的代码引用另一个模块中的 internal 类型，则两个模块都必须通过 /addmodule 合并到一个程序集清单中。

Visual Studio 开发环境不支持创建模块。

有关如何以编程方式设置此编译器选项的信息，请参见 OutputType。

示例

编译 in.cs，创建 in.netmodule：

复制代码
csc /target:module in.cs

请参见

参考

/target（指定输出文件格式）（C# 编译器选项）

其他资源

C# 编译器选项


/target:winexe（创建 Windows 程序）（C# 编译器选项）

[image]

C# 语言参考
/target:winexe（创建 Windows 程序）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/target:winexe 选项使编译器创建可执行 (EXE) 的 Windows 程序。

/target:winexe

备注

将创建带扩展名 .exe 的可执行文件。 Windows 程序是一种由 .NET Framework 库或使用 Win32 API 提供一个用户界面的程序。

使用 /target:exe 创建控制台应用程序。

除非另外使用 /out 选项指定，否则输出文件名采用包含 Main 方法的输入文件名。

在命令行指定该选项时，直至下一个 /out 或 /target 选项之前的所有文件都将用于创建 Windows 程序。

在编译到 .exe 文件中的源代码文件中要求有且仅有一个 main 方法。 /main 选项使您可以在代码中包含多个具有 main 方法的类的情况下，指定包含 main 方法的类。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“应用程序”属性页。

  3. 修改“输出类型”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 OutputType。

示例

将 in.cs 编译成 Windows 程序：

复制代码
csc /target:winexe in.cs

请参见

参考

/target（指定输出文件格式）（C# 编译器选项）

其他资源

C# 编译器选项


/unsafe（启用不安全模式)（C# 编译器选项）

[image]

C# 语言参考
/unsafe（启用不安全模式)（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/unsafe 编译器选项允许对使用 unsafe 关键字的代码进行编译。

/unsafe

备注

有关不安全代码的更多信息，请参见 不安全代码和指针（C# 编程指南）。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 选中“允许不安全代码”复选框。

有关如何以编程方式设置此编译器选项的信息，请参见 AllowUnsafeBlocks。

示例

编译不安全模式的 in.cs：

复制代码
csc /unsafe in.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/utf8output（使用 UTF-8 显示编译器消息）（C# 编译器选项）

[image]

C# 语言参考
/utf8output（使用 UTF-8 显示编译器消息）（C# 编译器选项）
另请参见 发送反馈意见

更新：2007 年 11 月

/utf8output 选项使用 UTF-8 编码显示编译器输出。

/utf8output

备注

在某些国际配置中，编译器输出无法在控制台上正确显示。在这些配置中，请使用 /utf8output 并将编译器输出重定向到文件。

此编译器选项在 Visual Studio 中不可用，且不能通过编程方式进行更改。

请参见

其他资源

C# 编译器选项


/warn（指定警告等级）（C# 编译器选项）

[image]

C# 语言参考
/warn（指定警告等级）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/warn 选项指定编译器要显示的警告等级。

/warn:option

参数

option

您希望为编译显示的警告等级：较小的数字只显示高严重度的警告；数字越大，显示的警告越多。有效值为 0 到 4：

警告等级

含义

0

关闭所有警告消息的显示。

1

显示严重的警告消息。

2

显示等级 1 警告以及某些不太严重的警告，如关于隐藏类成员的警告。

3

显示等级 2 警告以及某些不太严重的警告，例如有关总是计算为 true 或 false 的表达式的警告。

4（默认）

显示所有等级 3 警告以及信息性警告。

备注

若要获得有关错误或警告的信息，可以在帮助索引中查找错误代码。有关获得错误或警告信息的其他方式，请参见如何：定位编译器错误的帮助。

使用 /warnaserror 可将所有的警告都视为错误。可以使用 /nowarn 禁用某些警告。

/w 是 /warn 的缩写形式。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 修改“警告等级”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 WarningLevel。

示例

编译 in.cs 并让编译器仅显示等级 1 警告：

复制代码
csc /warn:1 in.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/warnaserror（将警告视为错误）（C# 编译器选项）

[image]

C# 语言参考
/warnaserror（将警告视为错误）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/warnaserror+ 选项将所有警告都视为错误

/warnaserror[<U>+</U> | -][:warning-list]

备注

将一般报告为警告的任何消息都报告为错误，并且暂停生成过程（不生成输出文件）。

默认情况下启用 /warnaserror-，这导致警告不会妨碍生成输出文件。/warnaserror 与 /warnaserror+ 相同，它使警告被视为错误。

（可选）如果您希望只将几个特定的警告视为错误，可以指定一个以逗号分隔的列表，其中列出被视为错误的警告编号。

使用 /warn 可指定您希望编译器显示的警告等级。可以使用 /nowarn 禁用某些警告。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“生成”属性页。

  3. 修改“将警告视为错误”属性。

    若要以编程方式设置此编译器选项，请参见 TreatWarningsAsErrors。

示例

编译 in.cs 并且不让编译器显示警告：

复制代码
csc /warnaserror in.cs
csc /warnaserror:642,649,652 in.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/win32icon（导入 .ico 文件）（C# 编译器选项）

[image]

C# 语言参考
/win32icon（导入 .ico 文件）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/win32icon 选项在输出文件中插入 .ico 文件，.ico 文件赋予输出文件在 Windows 资源管理器中的所需外观。

/win32icon:filename

参数

filename

要添加到输出文件的 .ico 文件。

备注

.ico 文件可以用资源编译器创建。编译 Visual C++ 程序时将调用资源编译器；.ico 文件是从 .rc 文件创建的。

请参见 /linkresource（用于引用 .NET Framework 资源文件）或 /resource（用于附加 .NET Framework 资源文件）。要导入 .res 文件，请参见 /win32res。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“应用程序”属性页。

  3. 修改“应用程序图标”属性。

有关如何以编程方式设置此编译器选项的信息，请参见 ApplicationIcon。

示例

编译 in.cs，并附加 .ico 文件 rf.ico 以生成 in.exe：

复制代码
csc /win32icon:rf.ico in.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/win32manifest（导入自定义 Win32 清单文件）（C# 编译器选项）

[image]

C# 语言参考
/win32manifest（导入自定义 Win32 清单文件）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

使用 /win32manifest 选项可以指定要嵌入到项目的可移植可执行 (PE) 文件中的用户定义的 Win32 应用程序清单文件。

/win32manifest: filename

参数

filename

自定义清单文件的名称和位置。

备注

默认情况下，Visual C# 编译器嵌入指定“asInvoker”的请求执行级别的应用程序清单。它在生成可执行文件的文件夹中创建清单，如果使用的是 Visual Studio，此文件夹通常为 bin\Debug 或 bin\Release。如果希望提供自定义清单（例如指定“highestAvailable”或“requireAdministrator”请求执行级别），请使用此选项指定文件的名称。

说明：

此选项与 /win32res（导入 Win32 资源文件）（C# 编译器选项）选项是互相排斥的。如果尝试在同一命令行中同时使用这两个选项，则会收到一个生成错误。

如果应用程序没有用于指定请求执行级别的应用程序清单，则会受到 Windows Vista 中的“用户帐户控制”功能下的文件/注册表虚拟化的影响。有关虚拟化的更多信息，请参见 Windows Vista Developer Story：针对用户帐户控制的 Windows Vista 应用程序开发要求。

如果满足下列任一条件，则应用程序会受到虚拟化的影响：

  • 使用了 /nowin32manifest 选项，并且在随后的生成步骤中未提供清单，或未通过使用 /win32res 选项在 Windows 资源 (.res) 文件中提供清单。

  • 提供的自定义清单未指定请求执行级别。

Visual Studio 创建默认的 .manifest 文件，并将该文件与可执行文件一起存储在 debug 和 release 目录中。可以用任意文本编辑器创建一个清单，然后将该文件添加到项目中，以此添加自定义清单。或者，也可以右击“解决方案资源管理器”中的“项目”图标，单击“添加新项”，然后单击“应用程序清单文件”。添加完新的或现有清单文件后，该文件将显示在“清单”下拉列表中。有关更多信息，请参见“项目设计器”->“应用程序”页 (C#)。

提供应用程序清单的操作，可以作为自定义后期生成步骤，也可以通过使用 /nowin32manifest（不包含 Win32 清单)（C# 编辑器选项） 选项作为 Win32 资源文件的组成部分。如果希望应用程序受到 Windows Vista 中的文件或注册表虚拟化的影响，请使用该选项。这会阻止编译器在可移植可执行 (PE) 文件中创建和嵌入默认清单。

示例

下面的示例演示 Visual C# 编译器插入到 PE 中的默认清单。

说明：

编译器将一个标准应用程序名称“MyApplication.app”插入到 xml 中。这是一种使应用程序可以在 Windows Server 2003 Service Pack 3 上运行的解决方法。

复制代码
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="asInvoker"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

请参见

参考

/nowin32manifest（不包含 Win32 清单)（C# 编辑器选项）

其他资源

C# 编译器选项
项目属性 (Visual Studio)


/win32res（导入 Win32 资源文件）（C# 编译器选项）

[image]

C# 语言参考
/win32res（导入 Win32 资源文件）（C# 编译器选项）
示例 另请参见 发送反馈意见

更新：2007 年 11 月

/win32res 选项在输出文件中插入 Win32 资源。

/win32res:filename

参数

filename

要添加到输出文件的资源文件。

备注

Win32 资源文件可以用资源编译器创建。在编译 Visual C++ 程序时会调用资源编译器；.res 文件是用 .rc 文件创建的。

Win32 资源可以包含版本或位图（图标）信息，这些信息有助于在 Windows 资源管理器中标识您的应用程序。如果不指定 /win32res，编译器将根据程序集版本生成版本信息。

请参见 /linkresource（用于引用 .NET Framework 资源文件）或 /resource（用于附加 .NET Framework 资源文件）。

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性”页。

  2. 单击“应用程序”属性页。

  3. 单击“资源文件”按钮，然后使用组合框选择文件。

示例

编译 in.cs，并附加 Win32 资源文件 rf.res 以生成 in.exe：

复制代码
csc /win32res:rf.res in.cs

请参见

其他资源

C# 编译器选项
项目属性 (Visual Studio)


如何：定位编译器错误的帮助

[image]

C# 语言参考
如何：定位编译器错误的帮助
另请参见 发送反馈意见

更新：2007 年 11 月

说明：

显示的对话框和菜单命令可能会与“帮助”中的描述不同，具体取决于您的当前设置或版本。若要更改设置，请在“工具”菜单上选择“导入和导出设置”。有关更多信息，请参见 Visual Studio 设置。

所有的 C# 编译器错误都有相应的主题解释了错误生成的原因，在某些情况下还解释了如何修复错误。若要获取有关特定错误信息的帮助，请尝试以下操作之一：

过程

查找错误的帮助

  • 单击 “输出”窗口 中的错误号，再按 F1。

    - 或 -

    在“索引”中的“查找”框中键入错误号。

    - 或 -

    在“搜索”页中键入错误号。

请参见

其他资源

C# 编译器选项


C# 编译器错误

[image]

Visual C# 参考：错误和警告
C# 编译器错误
另请参见 发送反馈意见

更新：2007 年 11 月

本节包含按数字顺序列出的 C# 编译器错误。所有的 C# 编译器错误都有相应的主题解释了错误生成的原因，在某些情况下还解释了如何修复错误。若要获取有关特定错误消息的帮助，请尝试以下操作之一：

  • 单击 “输出”窗口 中的错误号，再按 F1。

  • 在“索引”中的“查找”框中键入错误号。

  • 在“搜索”页中键入错误号。

  • 在“目录”列表中找到该错误。

说明：

显示的对话框和菜单命令可能会与“帮助”中的描述不同，具体取决于您的当前设置或版本。若要更改设置，请在“工具”菜单上选择“导入和导出设置”。有关更多信息，请参见 Visual Studio 设置。

编译器错误 CS0001

编译器错误 CS0003

编译器错误 CS0004

编译器错误 CS0005

编译器错误 CS0006

编译器错误 CS0007

编译器错误 CS0008

编译器错误 CS0009

编译器错误 CS0010

编译器错误 CS0011

编译器错误 CS0012

编译器错误 CS0013

编译器错误 CS0014

编译器错误 CS0015

编译器错误 CS0016

编译器错误 CS0017

编译器错误 CS0019

编译器错误 CS0020

编译器错误 CS0021

编译器错误 CS0022

编译器错误 CS0023

编译器错误 CS0025

编译器错误 CS0026

编译器错误 CS0027

编译器错误 CS0029

编译器错误 CS0030

编译器错误 CS0031

编译器错误 CS0034

编译器错误 CS0035

编译器错误 CS0036

编译器错误 CS0037

编译器错误 CS0038

编译器错误 CS0039

编译器错误 CS0040

编译器错误 CS0041

编译器错误 CS0042

编译器错误 CS0043

编译器错误 CS0050

编译器错误 CS0051

编译器错误 CS0052

编译器错误 CS0053

编译器错误 CS0054

编译器错误 CS0055

编译器错误 CS0056

编译器错误 CS0057

编译器错误 CS0058

编译器错误 CS0059

编译器错误 CS0060

编译器错误 CS0061

编译器错误 CS0065

编译器错误 CS0066

编译器错误 CS0068

编译器错误 CS0069

编译器错误 CS0070

编译器错误 CS0071

编译器错误 CS0072

编译器错误 CS0073

编译器错误 CS0074

编译器错误 CS0075

编译器错误 CS0076

编译器错误 CS0077

编译器错误 CS0079

编译器错误 CS0080

编译器错误 CS0081

编译器错误 CS0082

编译器错误 CS0100

编译器错误 CS0101

编译器错误 CS0102

编译器错误 CS0103

编译器错误 CS0104

编译器错误 CS0106

编译器错误 CS0107

编译器错误 CS0110

编译器错误 CS0111

编译器错误 CS0112

编译器错误 CS0113

编译器错误 CS0115

编译器错误 CS0116

编译器错误 CS0117

编译器错误 CS0118

编译器错误 CS0119

编译器错误 CS0120

编译器错误 CS0121

编译器错误 CS0122

编译器错误 CS0123

编译器错误 CS0126

编译器错误 CS0127

编译器错误 CS0128

编译器错误 CS0131

编译器错误 CS0132

编译器错误 CS0133

编译器错误 CS0134

编译器错误 CS0135

编译器错误 CS0136

编译器错误 CS0138

编译器错误 CS0139

编译器错误 CS0140

编译器错误 CS0143

编译器错误 CS0144

编译器错误 CS0145

编译器错误 CS0146

编译器错误 CS0148

编译器错误 CS0149

编译器错误 CS0150

编译器错误 CS0151

编译器错误 CS0152

编译器错误 CS0153

编译器错误 CS0154

编译器错误 CS0155

编译器错误 CS0156

编译器错误 CS0157

编译器错误 CS0158

编译器错误 CS0159

编译器错误 CS0160

编译器错误 CS0161

编译器错误 CS0163

编译器错误 CS0165

编译器错误 CS0167

编译器错误 CS0170

编译器错误 CS0171

编译器错误 CS0172

编译器错误 CS0173

编译器错误 CS0174

编译器错误 CS0175

编译器错误 CS0176

编译器错误 CS0177

编译器错误 CS0178

编译器错误 CS0179

编译器错误 CS0180

编译器错误 CS0182

编译器错误 CS0185

编译器错误 CS0186

编译器错误 CS0188

编译器错误 CS0191

编译器错误 CS0192

编译器错误 CS0193

编译器错误 CS0196

编译器错误 CS0198

编译器错误 CS0199

编译器错误 CS0200

编译器错误 CS0201

编译器错误 CS0202

编译器错误 CS0204

编译器错误 CS0205

编译器错误 CS0206

编译器错误 CS0208

编译器错误 CS0209

编译器错误 CS0210

编译器错误 CS0211

编译器错误 CS0212

编译器错误 CS0213

编译器错误 CS0214

编译器错误 CS0215

编译器错误 CS0216

编译器错误 CS0217

编译器错误 CS0218

编译器错误 CS0220

编译器错误 CS0221

编译器错误 CS0225

编译器错误 CS0226

编译器错误 CS0227

编译器错误 CS0228

编译器错误 CS0229

编译器错误 CS0230

编译器错误 CS0231

编译器错误 CS0233

编译器错误 CS0234

编译器错误 CS0236

编译器错误 CS0238

编译器错误 CS0239

编译器错误 CS0241

编译器错误 CS0242

编译器错误 CS0243

编译器错误 CS0244

编译器错误 CS0245

编译器错误 CS0246

编译器错误 CS0247

编译器错误 CS0248

编译器错误 CS0249

编译器错误 CS0250

编译器错误 CS0254

编译器错误 CS0255

编译器错误 CS0260

编译器错误 CS0261

编译器错误 CS0262

编译器错误 CS0263

编译器错误 CS0264

编译器错误 CS0265

编译器错误 CS0266

编译器错误 CS0267

编译器错误 CS0268

编译器错误 CS0269

编译器错误 CS0270

编译器错误 CS0271

编译器错误 CS0272

编译器错误 CS0273

编译器错误 CS0274

编译器错误 CS0275

编译器错误 CS0276

编译器错误 CS0277

编译器错误 CS0281

编译器错误 CS0283

编译器错误 CS0304

编译器错误 CS0305

编译器错误 CS0306

编译器错误 CS0307

编译器错误 CS0308

编译器错误 CS0310

编译器错误 CS0311

编译器错误 CS0312

编译器错误 CS0313

编译器错误 CS0314

编译器错误 CS0315

编译器错误 CS0316

编译器错误 CS0400

编译器错误 CS0401

编译器错误 CS0403

编译器错误 CS0404

编译器错误 CS0405

编译器错误 CS0406

编译器错误 CS0407

编译器错误 CS0409

编译器错误 CS0410

编译器错误 CS0411

编译器错误 CS0412

编译器错误 CS0413

编译器错误 CS0415

编译器错误 CS0416

编译器错误 CS0417

编译器错误 CS0418

编译器错误 CS0423

编译器错误 CS0424

编译器错误 CS0425

编译器错误 CS0426

编译器错误 CS0428

编译器错误 CS0430

编译器错误 CS0431

编译器错误 CS0432

编译器错误 CS0433

编译器错误 CS0434

编译器错误 CS0438

编译器错误 CS0439

编译器错误 CS0441

编译器错误 CS0442

编译器错误 CS0443

编译器错误 CS0445

编译器错误 CS0446

编译器错误 CS0447

编译器错误 CS0448

编译器错误 CS0449

编译器错误 CS0450

编译器错误 CS0451

编译器错误 CS0452

编译器错误 CS0453

编译器错误 CS0454

编译器错误 CS0455

编译器错误 CS0456

编译器错误 CS0457

编译器错误 CS0459

编译器错误 CS0460

编译器错误 CS0462

编译器错误 CS0463

编译器错误 CS0466

编译器错误 CS0468

编译器错误 CS0470

编译器错误 CS0471

编译器错误 CS0473

编译器错误 CS0500

编译器错误 CS0501

编译器错误 CS0502

编译器错误 CS0503

编译器错误 CS0504

编译器错误 CS0505

编译器错误 CS0506

编译器错误 CS0507

编译器错误 CS0508

编译器错误 CS0509

编译器错误 CS0513

编译器错误 CS0514

编译器错误 CS0515

编译器错误 CS0516

编译器错误 CS0517

编译器错误 CS0518

编译器错误 CS0520

编译器错误 CS0522

编译器错误 CS0523

编译器错误 CS0524

编译器错误 CS0525

编译器错误 CS0526

编译器错误 CS0527

编译器错误 CS0528

编译器错误 CS0529

编译器错误 CS0531

编译器错误 CS0533

编译器错误 CS0534

编译器错误 CS0535

编译器错误 CS0537

编译器错误 CS0538

编译器错误 CS0539

编译器错误 CS0540

编译器错误 CS0541

编译器错误 CS0542

编译器错误 CS0543

编译器错误 CS0544

编译器错误 CS0545

编译器错误 CS0546

编译器错误 CS0547

编译器错误 CS0548

编译器错误 CS0549

编译器错误 CS0550

编译器错误 CS0551

编译器错误 CS0552

编译器错误 CS0553

编译器错误 CS0554

编译器错误 CS0555

编译器错误 CS0556

编译器错误 CS0557

编译器错误 CS0558

编译器错误 CS0559

编译器错误 CS0562

编译器错误 CS0563

编译器错误 CS0564

编译器错误 CS0567

编译器错误 CS0568

编译器错误 CS0569

编译器错误 CS0570

编译器错误 CS0571

编译器错误 CS0572

编译器错误 CS0573

编译器错误 CS0574

编译器错误 CS0575

编译器错误 CS0576

编译器错误 CS0577

编译器错误 CS0578

编译器错误 CS0579

编译器错误 CS0582

编译器错误 CS0583

编译器错误 CS0584

编译器错误 CS0585

编译器错误 CS0586

编译器错误 CS0587

编译器错误 CS0588

编译器错误 CS0589

编译器错误 CS0590

编译器错误 CS0591

编译器错误 CS0592

编译器错误 CS0594

编译器错误 CS0596

编译器错误 CS0599

编译器错误 CS0601

编译器错误 CS0609

编译器错误 CS0610

编译器错误 CS0611

编译器错误 CS0616

编译器错误 CS0617

编译器错误 CS0619

编译器错误 CS0620

编译器错误 CS0621

编译器错误 CS0622

编译器错误 CS0623

编译器错误 CS0625

编译器错误 CS0629

编译器错误 CS0631

编译器错误 CS0633

编译器错误 CS0635

编译器错误 CS0636

编译器错误 CS0637

编译器错误 CS0641

编译器错误 CS0643

编译器错误 CS0644

编译器错误 CS0645

编译器错误 CS0646

编译器错误 CS0647

编译器错误 CS0648

编译器错误 CS0650

编译器错误 CS0653

编译器错误 CS0655

编译器错误 CS0656

编译器错误 CS0662

编译器错误 CS0663

编译器错误 CS0664

编译器错误 CS0666

编译器错误 CS0667

编译器错误 CS0668

编译器错误 CS0669

编译器错误 CS0670

编译器错误 CS0673

编译器错误 CS0674

编译器错误 CS0677

编译器错误 CS0678

编译器错误 CS0681

编译器错误 CS0682

编译器错误 CS0683

编译器错误 CS0685

编译器错误 CS0686

编译器错误 CS0687

编译器错误 CS0689

编译器错误 CS0690

编译器错误 CS0692

编译器错误 CS0694

编译器错误 CS0695

编译器错误 CS0698

编译器错误 CS0699

编译器错误 CS0701

编译器错误 CS0702

编译器错误 CS0703

编译器错误 CS0704

编译器错误 CS0706

编译器错误 CS0708

编译器错误 CS0709

编译器错误 CS0710

编译器错误 CS0711

编译器错误 CS0712

编译器错误 CS0713

编译器错误 CS0714

编译器错误 CS0715

编译器错误 CS0716

编译器错误 CS0717

编译器错误 CS0718

编译器错误 CS0719

编译器错误 CS0720

编译器错误 CS0721

编译器错误 CS0722

编译器错误 CS0723

编译器错误 CS0724

编译器错误 CS0726

编译器错误 CS0727

编译器错误 CS0729

编译器错误 CS0730

编译器错误 CS0731

编译器错误 CS0733

编译器错误 CS0734

编译器错误 CS0735

编译器错误 CS0736

编译器错误 CS0737

编译器错误 CS0738

编译器错误 CS0739

编译器错误 CS0742

编译器错误 CS0743

编译器错误 CS0744

编译器错误 CS0745

编译器错误 CS0746

编译器错误 CS0747

编译器错误 CS0748

编译器错误 CS0750

编译器错误 CS0751

编译器错误 CS0752

编译器错误 CS0753

编译器错误 CS0754

编译器错误 CS0755

编译器错误 CS0756

编译器错误 CS0757

编译器错误 CS0758

编译器错误 CS0759

编译器错误 CS0761

编译器错误 CS0762

编译器错误 CS0763

编译器错误 CS0764

编译器错误 CS0765

编译器错误 CS0766

编译器错误 CS0811

编译器错误 CS0815

编译器错误 CS0818

编译器错误 CS0819

编译器错误 CS0820

编译器错误 CS0821

编译器错误 CS0822

编译器错误 CS0825

编译器错误 CS0826

编译器错误 CS0828

编译器错误 CS0831

编译器错误 CS0832

编译器错误 CS0833

编译器错误 CS0834

编译器错误 CS0835

编译器错误 CS0836

编译器错误 CS0837

编译器错误 CS0838

编译器错误 CS0839

编译器错误 CS0840

编译器错误 CS0841

编译器错误 CS0842

编译器错误 CS0843

编译器错误 CS0844

编译器错误 CS0845

编译器错误 CS1001

编译器错误 CS1002

编译器错误 CS1003

编译器错误 CS1004

编译器错误 CS1007

编译器错误 CS1008

编译器错误 CS1009

编译器错误 CS1010

编译器错误 CS1011

编译器错误 CS1012

编译器错误 CS1013

编译器错误 CS1014

编译器错误 CS1015

编译器错误 CS1016

编译器错误 CS1017

编译器错误 CS1018

编译器错误 CS1019

编译器错误 CS1020

编译器错误 CS1021

编译器错误 CS1022

编译器错误 CS1023

编译器错误 CS1024

编译器错误 CS1025

编译器错误 CS1026

编译器错误 CS1027

编译器错误 CS1028

编译器错误 CS1029

编译器错误 CS1031

编译器错误 CS1032

编译器错误 CS1033

编译器错误 CS1034

编译器错误 CS1035

编译器错误 CS1036

编译器错误 CS1037

编译器错误 CS1038

编译器错误 CS1039

编译器错误 CS1040

编译器错误 CS1041

编译器错误 CS1043

编译器错误 CS1044

编译器错误 CS1055

编译器错误 CS1056

编译器错误 CS1057

编译器错误 CS1059

编译器错误 CS1061

编译器错误 CS1100

编译器错误 CS1101

编译器错误 CS1102

编译器错误 CS1103

编译器错误 CS1104

编译器错误 CS1105

编译器错误 CS1106

编译器错误 CS1107

编译器错误 CS1108

编译器错误 CS1109

编译器错误 CS1110

编译器错误 CS1112

编译器错误 CS1113

编译器错误 CS1501

编译器错误 CS1502

编译器错误 CS1503

编译器错误 CS1504

编译器错误 CS1507

编译器错误 CS1508

编译器错误 CS1509

编译器错误 CS1510

编译器错误 CS1511

编译器错误 CS1512

编译器错误 CS1513

编译器错误 CS1514

编译器错误 CS1515

编译器错误 CS1517

编译器错误 CS1518

编译器错误 CS1519

编译器错误 CS1520

编译器错误 CS1521

编译器错误 CS1524

编译器错误 CS1525

编译器错误 CS1526

编译器错误 CS1527

编译器错误 CS1528

编译器错误 CS1529

编译器错误 CS1530

编译器错误 CS1534

编译器错误 CS1535

编译器错误 CS1536

编译器错误 CS1537

编译器错误 CS1540

编译器错误 CS1541

编译器错误 CS1542

编译器错误 CS1545

编译器错误 CS1546

编译器错误 CS1547

编译器错误 CS1548

编译器错误 CS1549

编译器错误 CS1551

编译器错误 CS1552

编译器错误 CS1553

编译器错误 CS1554

编译器错误 CS1555

编译器错误 CS1556

编译器错误 CS1557

编译器错误 CS1558

编译器错误 CS1559

编译器错误 CS1560

编译器错误 CS1561

编译器错误 CS1562

编译器错误 CS1563

编译器错误 CS1564

编译器错误 CS1565

编译器错误 CS1566

编译器错误 CS1567

编译器错误 CS1569

编译器错误 CS1575

编译器错误 CS1576

编译器错误 CS1577

编译器错误 CS1578

编译器错误 CS1579

编译器错误 CS1583

编译器错误 CS1585

编译器错误 CS1586

编译器错误 CS1588

编译器错误 CS1593

编译器错误 CS1594

编译器错误 CS1597

编译器错误 CS1599

编译器错误 CS1600

编译器错误 CS1601

编译器错误 CS1604

编译器错误 CS1605

编译器错误 CS1606

编译器错误 CS1608

编译器错误 CS1609

编译器错误 CS1611

编译器错误 CS1612

编译器错误 CS1613

编译器错误 CS1614

编译器错误 CS1615

编译器错误 CS1617

编译器错误 CS1618

编译器错误 CS1619

编译器错误 CS1620

编译器错误 CS1621

编译器错误 CS1622

编译器错误 CS1623

编译器错误 CS1624

编译器错误 CS1625

编译器错误 CS1626

编译器错误 CS1627

编译器错误 CS1628

编译器错误 CS1629

编译器错误 CS1630

编译器错误 CS1631

编译器错误 CS1632

编译器错误 CS1637

编译器错误 CS1638

编译器错误 CS1639

编译器错误 CS1640

编译器错误 CS1641

编译器错误 CS1642

编译器错误 CS1643

编译器错误 CS1644

编译器错误 CS1646

编译器错误 CS1647

编译器错误 CS1648

编译器错误 CS1649

编译器错误 CS1650

编译器错误 CS1651

编译器错误 CS1654

编译器错误 CS1655

编译器错误 CS1656

编译器错误 CS1657

编译器错误 CS1660

编译器错误 CS1661

编译器错误 CS1662

编译器错误 CS1663

编译器错误 CS1664

编译器错误 CS1665

编译器错误 CS1666

编译器错误 CS1667

编译器错误 CS1670

编译器错误 CS1671

编译器错误 CS1672

编译器错误 CS1673

编译器错误 CS1674

编译器错误 CS1675

编译器错误 CS1676

编译器错误 CS1677

编译器错误 CS1678

编译器错误 CS1679

编译器错误 CS1680

编译器错误 CS1681

编译器错误 CS1686

编译器错误 CS1688

编译器错误 CS1689

编译器错误 CS1703

编译器错误 CS1704

编译器错误 CS1705

编译器错误 CS1706

编译器错误 CS1708

编译器错误 CS1713

编译器错误 CS1714

编译器错误 CS1715

编译器错误 CS1716

编译器错误 CS1719

编译器错误 CS1721

编译器错误 CS1722

编译器错误 CS1724

编译器错误 CS1725

编译器错误 CS1726

编译器错误 CS1727

编译器错误 CS1728

编译器错误 CS1729

编译器错误 CS1730

编译器错误 CS1731

编译器错误 CS1732

编译器错误 CS1733

编译器错误 CS1900

编译器错误 CS1902

编译器错误 CS1906

编译器错误 CS1908

编译器错误 CS1909

编译器错误 CS1910

编译器错误 CS1912

编译器错误 CS1913

编译器错误 CS1914

编译器错误 CS1917

编译器错误 CS1918

编译器错误 CS1919

编译器错误 CS1920

编译器错误 CS1921

编译器错误 CS1922

编译器错误 CS1925

编译器错误 CS1926

编译器错误 CS1928

编译器错误 CS1929

编译器错误 CS1930

编译器错误 CS1931

编译器错误 CS1932

编译器错误 CS1933

编译器错误 CS1934

编译器错误 CS1935

编译器错误 CS1936

编译器错误 CS1937

编译器错误 CS1938

编译器错误 CS1939

编译器错误 CS1940

编译器错误 CS1941

编译器错误 CS1942

编译器错误 CS1943

编译器错误 CS1944

编译器错误 CS1945

编译器错误 CS1946

编译器错误 CS1947

编译器错误 CS1948

编译器错误 CS1949

编译器错误 CS1950

编译器错误 CS1951

编译器错误 CS1952

编译器错误 CS1953

编译器错误 CS1954

编译器错误 CS1955

编译器错误 CS1958

编译器错误 CS1959

编译器错误 CS2001

编译器错误 CS2003

编译器错误 CS2005

编译器错误 CS2006

编译器错误 CS2007

编译器错误 CS2008

编译器错误 CS2011

编译器错误 CS2012

编译器错误 CS2013

编译器错误 CS2015

编译器错误 CS2016

编译器错误 CS2017

编译器错误 CS2018

编译器错误 CS2019

编译器错误 CS2020

编译器错误 CS2021

编译器错误 CS2022

编译器错误 CS2024

编译器错误 CS2032

编译器错误 CS2033

编译器错误 CS2034

编译器错误 CS2035

编译器错误 CS2036

编译器错误 CS5001

编译器警告（等级 1）CS0183

编译器警告（等级 1）CS0184

编译器警告（等级 1）CS0197

编译器警告（等级 1）CS0420

编译器警告（等级 1）CS0465

编译器警告（等级 1）CS0602

编译器警告（等级 1）CS0612

编译器警告（等级 1）CS0626

编译器警告（等级 1）CS0657

编译器警告（等级 1）CS0658

编译器警告（等级 1）CS0672

编译器警告（等级 1）CS0684

编译器警告（等级 1）CS0688

编译器警告（等级 1）CS0809

编译器警告（等级 1）CS0824

编译器警告（等级 1）CS1030

编译器警告（等级 1）CS1058

编译器警告（等级 1）CS1060

编译器警告（等级 1）CS1200

编译器警告（等级 1）CS1201

编译器警告（等级 1）CS1202

编译器警告（等级 1）CS1203

编译器警告（等级 1）CS1522

编译器警告（等级 1）CS1570

编译器警告（等级 1）CS1574

编译器警告（等级 1）CS1580

编译器警告（等级 1）CS1581

编译器警告（等级 1）CS1584

编译器警告（等级 1）CS1589

编译器警告（等级 1）CS1590

编译器警告（等级 1）CS1592

编译器警告（等级 1）CS1598

编译器警告（等级 1）CS1607

编译器警告（等级 1）CS1616

编译器警告（等级 1）CS1633

编译器警告（等级 1）CS1634

编译器警告（等级 1）CS1635

编译器警告（等级 1）CS1645

编译器警告（等级 1）CS1658

编译器警告（等级 1）CS1682

编译器警告（等级 1）CS1683

编译器警告（等级 1）CS1684

编译器警告（等级 1）CS1685

编译器警告（等级 1）CS1687

编译器警告（等级 1）CS1690

编译器警告（等级 1）CS1691

编译器警告（等级 1）CS1692

编译器警告（等级 1）CS1694

编译器警告（等级 1）CS1695

编译器警告（等级 1）CS1696

编译器警告（等级 1）CS1697

编译器警告（等级 1）CS1699

编译器警告（等级 1）CS1707

编译器警告（等级 1）CS1709

编译器警告（等级 1）CS1720

编译器警告（等级 1）CS1723

编译器警告（等级 1）CS1911

编译器警告（等级 1）CS1956

编译器警告（等级 1）CS1957

编译器警告（等级 1）CS2002

编译器警告（等级 1）CS2014

编译器警告（等级 1）CS2023

编译器警告（等级 1）CS2029

编译器警告（等级 1）CS3000

编译器警告（等级 1）CS3001

编译器警告（等级 1）CS3002

编译器警告（等级 1）CS3003

编译器警告（等级 1）CS3004

编译器警告（等级 1）CS3005

编译器警告（等级 1）CS3006

编译器警告（等级 1）CS3007

编译器警告（等级 1）CS3008

编译器警告（等级 1）CS3009

编译器警告（等级 1）CS3010

编译器警告（等级 1）CS3011

编译器警告（等级 1）CS3012

编译器警告（等级 1）CS3013

编译器警告（等级 1）CS3014

编译器警告（等级 1）CS3015

编译器警告（等级 1）CS3016

编译器警告（等级 1）CS3017

编译器警告（等级 1）CS3018

编译器警告（等级 1）CS3022

编译器警告（等级 1）CS3023

编译器警告（等级 1）CS3026

编译器警告（等级 1）CS3027

编译器警告（等级 1）CS5000

编译器警告（等级 2）CS0108

编译器警告（等级 2）CS0114

编译器警告（等级 2）CS0162

编译器警告（等级 2）CS0164

编译器警告（等级 2）CS0251

编译器警告（等级 2）CS0252

编译器警告（等级 2）CS0253

编译器警告（等级 2）CS0278

编译器警告（等级 2）CS0279

编译器警告（等级 2）CS0280

编译器警告（等级 2）CS0435

编译器警告（等级 2）CS0436

编译器警告（等级 2）CS0437

编译器警告（等级 2）CS0440

编译器警告（等级 2）CS0444

编译器警告（等级 2）CS0458

编译器警告（等级 2）CS0464

编译器警告（等级 2）CS0467

编译器警告（等级 2）CS0469

编译器警告（等级 2）CS0472

编译器警告（等级 2）CS0618

编译器警告（等级 2）CS0652

编译器警告（等级 2）CS0728

编译器警告（等级 2）CS1571

编译器警告（等级 2）CS1572

编译器警告（等级 2）CS1587

编译器警告（等级 2）CS1668

编译器警告（等级 2）CS1698

编译器警告（等级 2）CS1701

编译器警告（等级 2）CS1710

编译器警告（等级 2）CS1711

编译器警告（等级 2）CS1927

编译器警告（等级 2）CS3019

编译器警告（等级 2）CS3021

编译器警告（等级 3）CS0067

编译器警告（等级 3）CS0105

编译器警告（等级 3）CS0168

编译器警告（等级 3）CS0169

编译器警告（等级 3）CS0219

编译器警告（等级 3）CS0282

编译器警告（等级 3）CS0414

编译器警告（等级 3）CS0419

编译器警告（等级 3）CS0642

编译器警告（等级 3）CS0659

编译器警告（等级 3）CS0660

编译器警告（等级 3）CS0661

编译器警告（等级 3）CS0665

编译器警告（等级 3）CS0675

编译器警告（等级 3）CS0693

编译器警告（等级 3）CS1700

编译器警告（等级 3）CS1702

编译器警告（等级 3）CS1717

编译器警告（等级 3）CS1718

编译器警告（等级 4）CS0028

编译器警告（等级 4）CS0078

编译器警告（等级 4）CS0109

编译器警告（等级 4）CS0402

编译器警告（等级 4）CS0422

编译器警告（等级 4）CS0429

编译器警告（等级 4）CS0628

编译器警告（等级 4）CS0649

编译器警告（等级 4）CS1573

编译器警告（等级 4）CS1591

编译器警告（等级 4）CS1610

编译器警告（等级 4）CS1712

编译器警告 CS3024

请参见

其他资源

C# 编译器选项


编译器错误 CS0001

[image]

Visual C# 参考：错误和警告
编译器错误 CS0001
发送反馈意见

更新：2007 年 11 月

错误消息

内部编译器错误

尝试确定编译器是否由于无法分析意外的语法而失败。如果重复收到此错误，请与 Microsoft 联系。


编译器错误 CS0003

[image]

Visual C# 参考：错误和警告
编译器错误 CS0003
发送反馈意见

更新：2007 年 11 月

错误消息

内存不足

编译器无法分配足够的虚拟内存完成编译。关闭所有不必要的应用程序并重新编译。

可能还需要增加页文件的大小，请确保有可用的磁盘空间。

当 Windows 软件开发工具包 (SDK) 与 C# 编译器的版本不匹配时，或者当一个或多个支持 C# 编译器的文件损坏时，也可能出现此错误；请重新安装 Visual Studio。


编译器错误 CS0004

[image]

Visual C# 参考：错误和警告
编译器错误 CS0004
发送反馈意见

更新：2007 年 11 月

视为错误的警告

使用 /warnaserror 编译器选项进行编译，这会导致编译器生成作为错误的警告。


编译器错误 CS0005

[image]

Visual C# 参考：错误和警告
编译器错误 CS0005
发送反馈意见

更新：2007 年 11 月

错误消息

编译器选项“compiler_option”后面必须有参数

有些编译器选项需要参数。如果不传递编译器选项所需的参数，则会生成 CS0005。

有关更多信息，请参见 C# 编译器选项。


编译器错误 CS0006

[image]

Visual C# 参考：错误和警告
编译器错误 CS0006
发送反馈意见

更新：2007 年 11 月

错误消息

未能找到元数据文件“dll_name”

编译了程序并给它显式传递了包含元数据的文件的名称，但没有找到 .dll。有关更多信息，请参见 /reference（导入元数据）（C# 编译器选项）。


编译器错误 CS0007

[image]

Visual C# 参考：错误和警告
编译器错误 CS0007
发送反馈意见

更新：2007 年 11 月

错误消息

意外的公共语言运行库初始化错误 —“description”

如果未能加载运行库，则会发生此错误。如果计算机上不存在编译器试图加载的公共语言运行库的版本，或者公共语言运行库的安装或配置已损坏，则可能发生此错误。

如果更改了 csc.exe.config 文件，则可能发生上述情况。此文件是在安装期间配置的，不应更改。如果 csc.exe.config 文件有可能已被更改，请检查该文件，确保计算机上存在该文件中指定的运行库的版本。如果存在正确的版本，它可能已损坏。请重新安装公共语言运行库。


编译器错误 CS0008

[image]

Visual C# 参考：错误和警告
编译器错误 CS0008
发送反馈意见

更新：2007 年 11 月

错误消息

从文件“file”读取元数据时发生错误 —“description”

成功打开了 DLL 以进行元数据检索，但它已损坏，因此未能从中读取数据。有关更多信息，请参见 /reference（导入元数据）（C# 编译器选项）。


编译器错误 CS0009

[image]

Visual C# 参考：错误和警告
编译器错误 CS0009
发送反馈意见

更新：2007 年 11 月

错误消息

元数据文件“file”无法打开 — “description”

用 /reference 编译器选项指定的文件不包含有效的元数据。


编译器错误 CS0010

[image]

Visual C# 参考：错误和警告
编译器错误 CS0010
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

错误 --“error”。

在某个非常意外的事件导致停止编译时，会生成此错误。

更正此错误

  • 重新编译另一个项目。如果再次收到同一错误，请尝试重新安装 Visual Studio。如果仍收到此错误，请向 Microsoft 发送错误报告。

请参见

参考

/errorreport（设置错误报告行为）（C# 编译器选项）


编译器错误 CS0011

[image]

Visual C# 参考：错误和警告
编译器错误 CS0011
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法解析由类型“type”引用的程序集“assembly”中的基类或接口“class”

用 /reference 从文件中导入的类是从某个类派生的，或者找不到它实现的接口。如果没有使用 /reference 将所需的 DLL 也包括在编译中，则可能发生这种情况。

有关更多信息，请参见“添加引用”对话框和 /reference（导入元数据）（C# 编译器选项）。

示例

复制代码
// CS0011_1.cs
// compile with: /target:library

public class Outer
{
   public class B { }
}

第二个文件创建一个 DLL，它定义从前一个示例中创建的类 B 派生的类 C。

复制代码
// CS0011_2.cs
// compile with: /target:library /reference:CS0011_1.dll
// post-build command: del /f CS0011_1.dll
public class C : Outer.B {}

第三个文件替换第一步中创建的 DLL，并省略了内部类 B 的定义。

复制代码
// CS0011_3.cs
// compile with: /target:library /out:cs0011_1.dll
public class Outer {}

最后，第四个文件引用在第二个示例中定义的类 C，该类从类 B 派生，但现在却丢失了。

下面的示例生成 CS0011。

复制代码
// CS0011_4.cs
// compile with: /reference:CS0011_1.dll /reference:CS0011_2.dll
// CS0011 expected

class M
{
   public static void Main()
   {
      C c = new C();
   }
}

请参见

参考

“添加引用”对话框
/reference（导入元数据）（C# 编译器选项）


编译器错误 CS0012

[image]

Visual C# 参考：错误和警告
编译器错误 CS0012
发送反馈意见

更新：2007 年 11 月

错误消息

类型“type”在未被引用的程序集中定义。必须添加对程序集“assembly”的引用。

未找到引用类型的定义。如果所需的 .DLL 文件没有包括在编译中，则可能发生这种情况。有关更多信息，请参见“添加引用”对话框和 /reference（导入元数据）（C# 编译器选项）。

下面的编译序列将导致 CS0012：

复制代码
// cs0012a.cs
// compile with: /target:library
public class A {}

然后：

复制代码
// cs0012b.cs
// compile with: /target:library /reference:cs0012a.dll
public class B
{
   public static A f()
   {
      return new A();
   }
}

然后：

复制代码
// cs0012c.cs
// compile with: /reference:cs0012b.dll
class C
{
   public static void Main()
   {
      object o = B.f();   // CS0012
   }
}

可以通过使用 /reference:b.dll;a.dll 进行编译来解决此 CS0012 错误。


编译器错误 CS0013

[image]

Visual C# 参考：错误和警告
编译器错误 CS0013
发送反馈意见

更新：2007 年 11 月

错误消息

将元数据写入文件“ModelStore.dll”时发生错误：“没有剩余的逻辑空间来创建更多用户字符串”。

.NET Framework 公共语言运行库未发出元数据。请检查以确保路径正确并且磁盘未满。如果问题仍然存在，可能需要修复或重新安装 Visual Studio 和/或 .NET Framework。


编译器错误 CS0014

[image]

Visual C# 参考：错误和警告
编译器错误 CS0014
发送反馈意见

更新：2007 年 11 月

错误消息

无法找到所需的文件“file”

编译器需要某个文件，但它不在系统上。请确保路径是正确的。如果该文件是 Visual Studio 系统文件，那么您可能需要修复安装，或者删除并彻底重新安装 Visual Studio。


编译器错误 CS0015

[image]

Visual C# 参考：错误和警告
编译器错误 CS0015
发送反馈意见

更新：2007 年 11 月

错误消息

类型“type”的名称太长

用户定义类型的完全限定名必须少于或等于 2048 个字符。

下面的示例生成 CS0015：

复制代码
// CS0015.cs
namespace
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
{
namespace
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
{
namespace
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
{
public class
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC   // CS0015
{
   public static void Main()
   {
   }
}
}
}
}


编译器错误 CS0016

[image]

Visual C# 参考：错误和警告
编译器错误 CS0016
发送反馈意见

更新：2007 年 11 月

错误消息

未能写入输出文件“file”—“reason”

编译器未能写入输出文件。检查文件的路径，确保文件存在。如果该位置已经存在一个以前生成的文件，请确保该文件可写入，并且当前没有进程将该文件锁定。例如，确保在尝试生成时可执行文件没有运行。


编译器错误 CS0017

[image]

Visual C# 参考：错误和警告
编译器错误 CS0017
发送反馈意见

更新：2007 年 11 月

错误消息

程序“output file name”定义了不止一个入口点。请使用 /main 进行编译，以指定包含入口点的类型。

程序只能有一个 Main 方法。

若要解决该错误，可以删除代码中的所有 Main 方法，只保留一个，或者可以使用 /main 编译器选项指定要使用的 Main 方法。

下面的示例生成 CS0017：

复制代码
// CS0017.cs
// compile with: /target:exe
public class clx
{
   static public void Main()
   {
   }
}

public class cly
{
   public static void Main()   // CS0017, delete one Main or use /main
   {
   }
}


编译器错误 CS0019

[image]

Visual C# 参考：错误和警告
编译器错误 CS0019
示例 另请参见 发送反馈意见

更新： 2008 年 7 月

错误消息

运算符“operator”无法应用在“type”和“type”类型的操作数

二进制运算符所操作的数据类型是该运算符不能处理的数据类型。例如，不能对字符串使用 || 运算符，不能对 struct 类型使用 == 运算符（除非该类型显式重载该运算符）。

示例

在本例中，必须在 ConditionalAttribute 外指定条件逻辑。只能向 ConditionalAttribute 传递一个预定义符号。

下面的示例生成 CS0019。

复制代码
// CS0019.cs
// compile with: /target:library
using System.Diagnostics;
public class MyClass
{
   [ConditionalAttribute("DEBUG" || "TRACE")]   // CS0019
   public void TestMethod() {}

   // OK
   [ConditionalAttribute("DEBUG")]
   public void TestMethod2() {}
}

请参见

参考

运算符（C# 编程指南）

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增加了有关 == 运算符和构造的文本。 | 内容 Bug 修复


编译器错误 CS0020

[image]

Visual C# 参考：错误和警告
编译器错误 CS0020
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

被常数零除

表达式在除法运算的分母中使用了字面（不是变量）值零。未定义被零除，因此无效。

下面的示例生成 CS0020：

复制代码
// CS0020.cs
namespace x
{
   public class b
   {
      public static void Main()
      {
         1 / 0;   // CS0020
      }
   }
}

请参见

参考

运算符（C# 编程指南）


编译器错误 CS0021

[image]

Visual C# 参考：错误和警告
编译器错误 CS0021
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将带 [] 的索引应用于“type”类型的表达式

试图通过索引器访问不支持 索引器（C# 编程指南） 的数据类型的值。

当您试图在 C++ 程序集中使用索引器时，可能会遇到 CS0021。在这种情况下，请用 DefaultMember 属性修饰 C++ 类，以使 C# 编译器知道哪个索引器是默认的。下面的示例生成 CS0021。

示例

此文件编译成一个 .dll 文件（DefaultMember 属性被注释掉）以生成此错误。

复制代码
// CPP0021.cpp
// compile with: /clr /LD
using namespace System::Reflection;
// Uncomment the following line to resolve
//[DefaultMember("myItem")]
public ref class MyClassMC
{
        public:
        property int myItem[int]
        {
            int get(int i){  return 5; }
            void set(int i, int value) {}
        }
};

下面是调用此 .dll 文件的 C# 文件。此文件试图通过索引器访问该类，但由于没有成员被声明为要使用的默认索引器，所以生成错误。

复制代码
// CS0021.cs
// compile with: /reference:CPP0021.dll
public class MyClass
{
    public static void Main()
    {
        MyClassMC myMC = new MyClassMC();
        int j = myMC[1]; // CS0021
    }
}


编译器错误 CS0022

[image]

Visual C# 参考：错误和警告
编译器错误 CS0022
示例 发送反馈意见

更新：2007 年 11 月

错误消息

[] 内索引数错误，应为“number”

数组访问操作在方括号内指定的维数不正确。有关更多信息，请参见数组（C# 编程指南）。

示例

下面的示例生成 CS0022：

复制代码
// CS0022.cs
public class MyClass
{
    public static void Main()
    {
        int[] a = new int[10];
        a[0] = 0;     // single-dimension array
        a[0,1] = 9;   // CS0022, the array does not have two dimensions
    }
}


编译器错误 CS0023

[image]

Visual C# 参考：错误和警告
编译器错误 CS0023
发送反馈意见

更新：2007 年 11 月

错误消息

运算符“operator”无法应用于“type”类型的操作数

试图将运算符应用于类型没有被设计用于该运算符的变量。有关更多信息，请参见类型（C# 编程指南）和C# 运算符。

下面的示例生成 CS0023：

复制代码
// CS0023.cs
namespace x
{
   public class a
   {
      public static void Main()
      {
         string s = "hello";
         s = -s;   // CS0023, minus operator not allowed on strings
      }
   }
}


编译器错误 CS0025

[image]

Visual C# 参考：错误和警告
编译器错误 CS0025
发送反馈意见

更新：2007 年 11 月

错误消息

未能找到标准库文件“file”

未找到编译器所需的文件。请检查路径是否正确以及文件是否存在。

如果文件是 Visual Studio 系统文件，您可能需要修复 Visual Studio 安装，或者完全重新安装它。


编译器错误 CS0026

[image]

Visual C# 参考：错误和警告
编译器错误 CS0026
示例 发送反馈意见

更新：2007 年 11 月

错误消息

关键字“this”在静态属性、静态方法或静态字段初始值设定项中无效

this（C# 参考） 关键字引用的对象是类型的实例。由于静态方法不依赖于包含类的任何实例，因此“this”关键字无意义，而这是不允许的。有关更多信息，请参见静态类和静态类成员（C# 编程指南）和对象（C# 编程指南）。

示例

下面的示例生成 CS0026：

复制代码
// CS0026.cs
public class A
{
   public static int i = 0;

   public static void Main()
   {
// CS0026
      this.i = this.i + 1;
      // Try the following line instead:
      // i = i + 1;
   }
}


编译器错误 CS0027

[image]

Visual C# 参考：错误和警告
编译器错误 CS0027
发送反馈意见

更新：2007 年 11 月

错误消息

关键字“this”在当前上下文中不可用

在属性、方法或构造函数的外部发现了 this（C# 参考） 关键字。

若要修复此错误，请修改语句以消除 this 关键字的使用，并且/或者将语句的一部分或全部移到属性、方法或构造函数的内部。

下面的示例生成 CS0027：

复制代码
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication3
{
    class MyClass
    {

        int err1 = this.Fun() + 1;  // CS0027

        public int Fun()
        {
            return 10;
        }

        public void Test()
        {
            // valid use of this
            int err = this.Fun() + 1;
            Console.WriteLine(err);
        }

        public static void Main()
        {
            MyClass c = new MyClass();
            c.Test();
        }
    }
}


编译器错误 CS0029

[image]

Visual C# 参考：错误和警告
编译器错误 CS0029
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法将类型“type”隐式转换为“type”

编译器要求显式转换。例如，可能需要将右值转换成与左值相同的类型。或者必须提供转换例程以支持某些运算符重载。

在将某个类型的变量赋给其他类型的变量时必须进行转换。当在不同类型的变量之间进行赋值时，编译器必须将赋值运算符右边的类型转换为该赋值运算符左边的类型。假设有下面的代码：

复制代码
int i = 50;
long lng = 100;
i = lng;

i = lng; 进行赋值运算，但赋值运算符左右两边变量的数据类型不匹配。进行赋值前，编译器将变量 lng（类型为 long）隐式转换为 int。此为隐式转换，原因是没有代码显式指示编译器执行此转换。此代码的问题在于上述转换被视为收缩转换，而编译器不允许进行隐式收缩转换，原因是可能会丢失数据。

如果转换后的数据类型所占用的内存存储空间比转换前的数据类型所占用的少，则存在收缩转换。例如，将 long 类型转换为 int 类型就被视为收缩转换。long 类型占用 8 个字节的内存，而 int 类型只占用 4 个字节。若要查看数据丢失如何发生，请考虑以下示例：

复制代码
int i = 50;
long lng = 3147483647;
i = lng;

变量 lng 现在包含的值无法存储在变量 i 中，原因是该值太大。如果要将该值转换为 int 类型，就会丢失一些数据，并且转换后的值不同于转换前的值。

扩大转换与收缩转换相反。对于扩大转换，转换后的数据类型占用的内存存储空间比转换前的数据类型占用的多。下面是一个扩大转换的示例：

复制代码
int i = 50;
long lng = 100;
lng = i;

请注意此代码示例和第一个示例之间的区别。这里，变量 lng 位于赋值运算符的左边，所以它是赋值的目标。在可以进行赋值前，编译器必须将变量 i（类型为 int）隐式转换为 long 类型，这是一个扩大转换，原因是从占用 4 个字节内存的类型 (int) 转换为占用 8 个字节内存的类型 (long)。由于不会发生数据丢失，所以允许进行隐式扩大转换。任何可以用 int 类型存储的值也可以用 long 类型存储。

我们知道隐式收缩转换是不允许的，因此为了能够编译这些代码，就需要显式转换数据类型。显式转换是使用强制转换来完成的。强制转换是 C# 中用来描述将一种数据类型转换为另一种数据类型的术语。若要编译这些代码，我们需要使用以下语法：

复制代码
int i = 50;
long lng = 100;
i = (int) lng;   // cast to int

第三行代码通知编译器在进行赋值前，将变量 lng（类型为 long）显式转换为 int 类型。切记，使用收缩转换可能会丢失数据。使用收缩转换时应小心，而且即使可以编译代码，也可能会在运行时获得意外的结果。

此讨论只针对值类型。使用值类型就是直接使用存储在变量中的数据。但 .NET Framework 还具有引用类型。使用引用类型就是使用对变量的引用，而不是使用实际数据。引用类型的示例是类、接口和数组。不能隐式或显式地将一个引用类型转换为其他引用类型，除非编译器允许特定的转换或可以实现相应的转换运算符。

下面的示例生成 CS0029：

复制代码
// CS0029.cs
public class MyInt
{
    private int x = 0;

    // Uncomment this conversion routine to resolve CS0029
    /*
    public static implicit operator int(MyInt i)
    {
       return i.x;
    }
    */

    public static void Main()
   {
      MyInt myInt = new MyInt();
      int i = myInt; // CS0029
   }
}

请参见

参考

转换运算符（C# 编程指南）


编译器错误 CS0030

[image]

Visual C# 参考：错误和警告
编译器错误 CS0030
发送反馈意见

更新：2007 年 11 月

错误消息

无法将类型“type”转换为“type”

必须提供转换例程以支持某些运算符重载。有关更多信息，请参见转换运算符（C# 编程指南）。

下面的示例生成 CS0030：

复制代码
// CS0030.cs
namespace x
{
   public class iii
   {
      /*
      public static implicit operator iii(int aa)
      {
         return null;
      }

      public static implicit operator int(iii aa)
      {
         return 0;
      }
      */

      public static iii operator ++(iii aa)
      {
         return (iii)0;   // CS0030
         // uncomment the conversion routines to resolve CS0030
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0031

[image]

Visual C# 参考：错误和警告
编译器错误 CS0031
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

常数值“value”无法转换为“type”。(使用“unchecked”语法重写)

试图对类型不能存储值的变量赋值。有关更多信息，请参见类型（C# 编程指南）。

下面的示例在已检查和未检查的上下文中都会生成 CS0031：

复制代码
// CS0031.cs
namespace CS0031
{
   public class a
   {
      public static void Main()
      {
         int num = (int)2147483648M; //CS0031
         // Try using a larger numeric type instead:
         // long num = (long)2147483648M; //CS0031

         const decimal d = -10M; // Decimal literal
            unchecked
            {
                const byte b = (byte)d; // CS0031
                // For small values try using a signed byte instead:
                // const sbyte b = (sbyte)d;
            }
      }
   }
}

请参见

参考

unchecked（C# 参考）


编译器错误 CS0034

[image]

Visual C# 参考：错误和警告
编译器错误 CS0034
发送反馈意见

更新：2007 年 11 月

错误消息

运算符“operator”对于“type1”和“type2”类型的操作数具有二义性

运算符用在了两个对象上，并且编译器找到多个转换。因为转换必须是唯一的，因此必须进行一次强制转换，或者使其中一个转换成为显式转换。有关更多信息，请参见转换运算符（C# 编程指南）。

下面的示例生成 CS0034：

复制代码
// CS0034.cs
public class A
{
   // allows for the conversion of A object to int
   public static implicit operator int (A s)
   {
      return 0;
   }

   public static implicit operator string (A i)
   {
      return null;
   }
}

public class B
{
   public static implicit operator int (B s)
   // one way to resolve this CS0034 is to make one conversion explicit
   // public static explicit operator int (B s)
   {
      return 0;
   }

   public static implicit operator string (B i)
   {
      return null;
   }

   public static implicit operator B (string i)
   {
      return null;
   }

   public static implicit operator B (int i)
   {
      return null;
   }
}

public class C
{
   public static void Main ()
   {
      A a = new A();
      B b = new B();
      b = b + a;   // CS0034
      // another way to resolve this CS0034 is to make a cast
      // b = b + (int)a;
   }
}

在 C# 1.1 中，一个编译器 Bug 使其能够定义一个类，在其中实现针对 int 和 bool 的用户定义的隐式转换，并对该类型的对象使用按位 AND 运算符 (&)。在 C# 2.0 中，已修复此 Bug 以使编译器符合 C# 规范，因此以下代码现在将导致错误 CS0034：

复制代码
namespace CS0034
{
    class TestClass2
    {
        public void Test()
        {
            TestClass o1 = new TestClass();
            TestClass o2 = new TestClass();
            TestClass o3 = o1 & o2; //CS0034
        }
    }

    class TestClass
    {
        public static implicit operator int(TestClass o)
        {
            return 1;
        }

        public static implicit operator TestClass(int v)
        {
            return new TestClass();
        }

        public static implicit operator bool(TestClass o)
        {
            return true;
        }
    }

}


编译器错误 CS0035

[image]

Visual C# 参考：错误和警告
编译器错误 CS0035
发送反馈意见

更新：2007 年 11 月

错误消息

运算符“operator”对于“type”类型的操作数具有二义性

编译器有多个可用的转换，但在应用运算符之前不知道选择哪个转换。有关更多信息，请参见Templated User Defined Conversions和转换运算符（C# 编程指南）。

下面的示例生成 CS0035：

复制代码
// CS0035.cs
class MyClass
{
   private int i;

   public MyClass(int i)
   {
      this.i = i;
   }

   public static implicit operator double(MyClass x)
   {
      return (double) x.i;
   }

   public static implicit operator decimal(MyClass x)
   {
      return (decimal) x.i;
   }
}

class MyClass2
{
   static void Main()
   {
      MyClass x = new MyClass(7);
      object o = - x;   // CS0035
      // try a cast:
      // object o = - (double)x;
   }
}


编译器错误 CS0036

[image]

Visual C# 参考：错误和警告
编译器错误 CS0036
发送反馈意见

更新：2007 年 11 月

错误消息

输出参数不能具有“[In]”属性

目前，out 参数上不允许有 In 属性。

下面的示例生成 CS0036：

复制代码
// CS0036.cs

using System;
using System.Runtime.InteropServices;

public class MyClass
{
   public static void TestOut([In] out char TestChar)   // CS0036
   // try the following line instead
   // public static void TestOut(out char TestChar)
   {
      TestChar = 'b';
      Console.WriteLine(TestChar);
   }

   public static void Main()
   {
      char i;           //variable to receive the value
      TestOut(out i);   // the arg must be passed as out
      Console.WriteLine(i);
   }
}


编译器错误 CS0037

[image]

Visual C# 参考：错误和警告
编译器错误 CS0037
发送反馈意见

更新：2007 年 11 月

错误消息

无法将 null 转换成“type”，因为它是不可以为 null 的值类型

编译器不能给值类型赋空值，只能给引用类型或可为空值的类型赋空值。而 struct 是一个值类型。有关更多信息，请参见可空类型（C# 编程指南）。

下面的示例生成 CS0037：

复制代码
// CS0037.cs
public struct s
{
}

class a
{
   public static void Main()
   {
      int i = null;   // CS0037
      s ss = null;    // CS0037
   }
}


编译器错误 CS0038

[image]

Visual C# 参考：错误和警告
编译器错误 CS0038
发送反馈意见

更新：2007 年 11 月

错误消息

无法通过嵌套类型“type2”来访问外部类型“type1”的非静态成员

类中的字段不自动可用于嵌套类。若要可用于嵌套类，该字段必须是 static。否则，必须创建外部类的实例。有关更多信息，请参见嵌套类型（C# 编程指南）。

下面的示例生成 CS0038：

复制代码
// CS0038.cs
class OuterClass
{
   public int count;
   // try the following line instead
   // public static int count;

   class InnerClass
   {
      void func()
      {
         // or, create an instance
         // OuterClass class_inst = new OuterClass();
         // int count2 = class_inst.count;
         int count2 = count;   // CS0038
      }
   }

   public static void Main()
   {
   }
}


编译器错误 CS0039

[image]

Visual C# 参考：错误和警告
编译器错误 CS0039
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法通过引用转换、装箱转换、取消装箱转换、包装转换或 Null 类型转换将类型“type1”转换为“type2”

继承、引用转换和装箱转换允许使用 as（C# 参考） 运算符的转换。有关更多信息，请参见转换运算符（C# 编程指南）。

示例

下面的示例生成 CS0039。

复制代码
// CS0039.cs
using System;
class A
{
}
class B: A
{
}
class C: A
{
}
class M
{
    static void Main()
    {
        A a = new C();
        B b = new B();
        C c;

        // This is valid; there is a built-in reference
        // conversion from A to C.
        c = a as C;

        //The following generates CS0039; there is no
        // built-in reference conversion from B to C.
        c = b as C;  // CS0039
    }
}


编译器错误 CS0040

[image]

Visual C# 参考：错误和警告
编译器错误 CS0040
发送反馈意见

更新：2007 年 11 月

错误消息

创建调试信息文件时发生错误 —“reason”

在使用 /debug 编译器选项时会发生该错误，它指示编译器无法写入 .pdb 文件。该错误的可能解决方案包括重新安装 Visual Studio，确保编译器对文件或目录有写访问，或者不使用 /debug 进行编译。


编译器错误 CS0041

[image]

Visual C# 参考：错误和警告
编译器错误 CS0041
发送反馈意见

更新：2007 年 11 月

错误消息

“type”的完全限定名对于调试信息来说太长。请在不使用“/debug”选项的情况下进行编译。

在使用 /debug 编译器选项时可能会发生该错误。如果您遇到此错误，请尝试删除 bin 目录中的 PDB 文件并重新编译。如果仍然遇到此错误，可能需要修复或重新安装 Visual Studio。


编译器错误 CS0042

[image]

Visual C# 参考：错误和警告
编译器错误 CS0042
发送反馈意见

更新：2007 年 11 月

错误消息

创建调试信息文件“file”时发生错误 —“reason”

编译器未能创建调试信息；reason 包含错误条件的附加信息。


编译器错误 CS0043

[image]

Visual C# 参考：错误和警告
编译器错误 CS0043
发送反馈意见

更新：2007 年 11 月

错误消息

PDB 文件“file”的格式不正确或已过期。请删除该文件并重新生成。

删除用于此编译的 .pdb 文件并重新编译。


编译器错误 CS0050

[image]

Visual C# 参考：错误和警告
编译器错误 CS0050
示例 发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：返回类型“type”比方法“method”的可访问性低

方法的返回类型和形参表中引用的每个类型都必须至少具有和方法自身相同的可访问性。有关更多信息，请参见访问修饰符（C# 编程指南）。

示例

下面的示例生成 CS0050，因为没有为 MyClass 提供任何可访问性修饰符，因此它的可访问性默认为 private。

复制代码
// CS0050.cs
class MyClass //accessibility defaults to private
// try the following line instead
// public class MyClass
{
}

public class MyClass2
{
    public static MyClass MyMethod()   // CS0050
    {
        return new MyClass();
    }

    public static void Main() { }
}


编译器错误 CS0051

[image]

Visual C# 参考：错误和警告
编译器错误 CS0051
示例 发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：参数类型“type”比方法“method”的访问性低

方法的返回类型和形参表中引用的每个类型都必须至少具有和方法自身相同的可访问性。请确保方法签名中使用的类型不会因为省略 public 修饰符而意外变为专用。有关更多信息，请参见访问修饰符（C# 编程指南）。

示例

下面的示例生成 CS0051：

复制代码
// CS0051.cs
public class A
{
    // Try making B public since F is public
    // B is implicitly private here
    class B
    {
    }

    public static void F(B b)  // CS0051
    {
    }

    public static void Main()
    {
    }
}


编译器错误 CS0052

[image]

Visual C# 参考：错误和警告
编译器错误 CS0052
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致: 字段类型“type”比字段“field”的访问性低

字段类型的可访问性不能比字段本身的可访问性低，因为所有的公共构造都必须返回公共的可访问对象。

示例

下面的示例生成 CS0052：

复制代码
// CS0052.cs
    public class MyClass2
    {

        private class MyClass
        // try the following line instead
        // public class MyClass
        {
        }
        public MyClass M;   // CS0052
    }

    public class MyClass3
    {
        public static void Main()
        {
        }
    }

请参见

参考

C# 关键字
访问修饰符（C# 参考）
可访问性级别（C# 参考）
修饰符（C# 参考）


编译器错误 CS0053

[image]

Visual C# 参考：错误和警告
编译器错误 CS0053
发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：属性类型“type”比属性“property”的可访问性低

公共构造必须返回可以公开访问的对象。有关更多信息，请参见访问修饰符（C# 编程指南）。

下面的示例生成 CS0053：

复制代码
// CS0053.cs
class MyClass //defaults to private accessibility
// try the following line instead
// public class MyClass
{
}

public class MyClass2
{
   public MyClass myProperty   // CS0053
   {
      get
      {
         return new MyClass();
      }
      set
      {
      }
   }
}

public class MyClass3
{
   public static void Main()
   {
   }
}


编译器错误 CS0054

[image]

Visual C# 参考：错误和警告
编译器错误 CS0054
发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：索引器返回类型“type”比索引器“indexer”的可访问性低

公共构造必须返回可以公开访问的对象。有关更多信息，请参见访问修饰符（C# 编程指南）。

下面的示例生成 CS0054：

复制代码
// CS0054.cs
class MyClass
// try the following line instead
// public class MyClass
{
}

public class MyClass3
{
   public MyClass this[int i]   // CS0054
   {
      get
      {
         return new MyClass();
      }
   }
}

public class MyClass2
{
   public static void Main()
   {
   }
}


编译器错误 CS0055

[image]

Visual C# 参考：错误和警告
编译器错误 CS0055
发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致: 参数类型“type”比索引器“indexer”的访问性低

公共构造必须返回可以公开访问的对象。有关更多信息，请参见访问修饰符（C# 编程指南）。

下面的示例生成 CS0055：

复制代码
// CS0055.cs
class MyClass //defaults to private accessibility
// try the following line instead
// public class MyClass
{
}

public class MyClass2
{
   public int this[MyClass myClass]   // CS0055
   {
      get
      {
         return 0;
      }
   }
}

public class MyClass3
{
   public static void Main()
   {
   }
}


编译器错误 CS0056

[image]

Visual C# 参考：错误和警告
编译器错误 CS0056
发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：返回类型“type”比运算符“operator”的访问性低

公共构造必须返回可以公开访问的对象。有关更多信息，请参见访问修饰符（C# 编程指南）。

下面的示例生成 CS0056：

复制代码
// CS0056.cs
class MyClass
// try the following line instead
// public class MyClass
{
}

public class A
{
   public static implicit operator MyClass(A a)   // CS0056
   {
      return new MyClass();
   }

   public static void Main()
   {
   }
}


编译器错误 CS0057

[image]

Visual C# 参考：错误和警告
编译器错误 CS0057
发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致: 参数类型“type”比运算符“operator”的访问性低

公共构造必须返回可以公开访问的对象。有关更多信息，请参见访问修饰符（C# 编程指南）。

下面的示例生成 CS0057：

复制代码
// CS0057.cs
class MyClass //defaults to private accessibility
// try the following line instead
// public class MyClass
{
}

public class MyClass2
{
   public static implicit operator MyClass2(MyClass iii)   // CS0057
   {
      return new MyClass2();
   }

   public static void Main()
   {
   }
}


编译器错误 CS0058

[image]

Visual C# 参考：错误和警告
编译器错误 CS0058
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：返回类型“type”比委托“delegate”的访问性低

公共构造必须返回可以公开访问的对象。有关更多信息，请参见访问修饰符（C# 编程指南）。

下面的示例生成 CS0058，因为没有访问修饰符应用于 MyClass，因此默认情况下它的可访问性为 private：

复制代码
// CS0058.cs
class MyClass
// try the following line instead
// public class MyClass
{
}

public delegate MyClass MyClassDel();   // CS0058

public class A
{
   public static void Main()
   {
   }
}

请参见

参考

private（C# 参考）


编译器错误 CS0059

[image]

Visual C# 参考：错误和警告
编译器错误 CS0059
示例 发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：参数类型“type”比委托“delegate”的可访问性低

方法的返回类型和形参表中引用的每个类型都必须至少具有和方法自身相同的可访问性。有关更多信息，请参见访问修饰符（C# 编程指南）。

示例

下面的示例生成 CS0059：

复制代码
// CS0059.cs
class MyClass //defaults to private accessibility
// try the following line instead
// public class MyClass
{
}

public delegate void MyClassDel( MyClass myClass);   // CS0059

public class Program
{
    public static void Main()
    {
    }
}


编译器错误 CS0060

[image]

Visual C# 参考：错误和警告
编译器错误 CS0060
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：基类“class1”比类“class2”的访问性低

基类和继承类之间的类可访问性应一致。

下面的示例生成 CS0060：

复制代码
// CS0060.cs
class MyClass
// try the following line instead
// public class MyClass
{
}

public class MyClass2 : MyClass   // CS0060
{
   public static void Main()
   {
   }
}

请参见

参考

访问修饰符（C# 编程指南）


编译器错误 CS0061

[image]

Visual C# 参考：错误和警告
编译器错误 CS0061
发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：基接口“interface 1”比接口“interface 2”的可访问性低

公共构造必须返回可以公开访问的对象。

在派生接口中，接口可访问性不能被限定。有关更多信息，请参见 接口（C# 编程指南） 和 访问修饰符（C# 编程指南）。

下面的示例生成 CS0061。

复制代码
// CS0061.cs
// compile with: /target:library
internal interface A {}
public interface AA : A {}  // CS0061

// OK
public interface B {}
internal interface BB : B {}

internal interface C {}
internal interface CC : C {}


编译器错误 CS0065

[image]

Visual C# 参考：错误和警告
编译器错误 CS0065
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“event”：事件属性必须同时具有 add 访问器和 remove 访问器

不是字段的事件必须具有两种访问方法。

下面的示例生成 CS0065：

复制代码
// CS0065.cs
using System;
public delegate void Eventhandler(object sender, int e);
public class MyClass
{
   public event EventHandler Click   // CS0065,
   {
      // to fix, uncomment the add and remove definitions
      /*
      add
      {
         Click += value;
      }
      remove
      {
         Click -= value;
      }
      */
   }

   public static void Main()
   {
   }
}

请参见

参考

事件（C# 编程指南）


编译器错误 CS0066

[image]

Visual C# 参考：错误和警告
编译器错误 CS0066
发送反馈意见

更新：2007 年 11 月

错误消息

“event”：事件必须是委托类型

event 关键字需要委托类型。有关更多信息，请参见事件（C# 编程指南）和委托（C# 编程指南）。

下面的示例生成 CS0066：

复制代码
// CS0066.cs
using System;

public class EventHandler
{
}

// to fix the error, remove the event declaration and the
// EventHandler class declaration, and uncomment the following line
// public delegate void EventHandler();

public class a
{
   public event EventHandler Click;   // CS0066

   private void TestMethod()
   {
      if (Click != null)
         Click();
   }

   public static void Main()
   {
   }
}


编译器错误 CS0068

[image]

Visual C# 参考：错误和警告
编译器错误 CS0068
发送反馈意见

更新：2007 年 11 月

错误消息

“event”：接口中的事件不能有初始值设定项

接口中的事件不能有初始值设定项。有关更多信息，请参见接口（C# 编程指南）。

下面的示例生成 CS0068：

复制代码
// CS0068.cs

delegate void MyDelegate();

interface I
{
   event MyDelegate d = new MyDelegate(M.f);   // CS0068
   // try the following line instead
   // event MyDelegate d2;
}

class M
{
   event MyDelegate d = new MyDelegate(M.f);

   public static void f()
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0069

[image]

Visual C# 参考：错误和警告
编译器错误 CS0069
发送反馈意见

更新：2007 年 11 月

错误消息

接口中的事件不能具有 add 或 remove 访问器

不能在 interface 中定义事件的访问函数。有关更多信息，请参见事件（C# 编程指南）和接口（C# 编程指南）。

下面的示例生成 CS0069：

复制代码
// CS0069.cs
// compile with: /target:library

public delegate void EventHandler();

public interface a
{
   event EventHandler Click { remove {} }   // CS0069
   event EventHandler Click2;   // OK
}


编译器错误 CS0070

[image]

Visual C# 参考：错误和警告
编译器错误 CS0070
发送反馈意见

更新：2007 年 11 月

错误消息

事件“event”只能出现在 += 或 -= 的左边（从类型“type”中使用时除外）

在定义事件的类的外部，event 只能加上或减去引用。有关更多信息，请参见事件（C# 编程指南）。

下面的示例生成 CS0070：

复制代码
// CS0070.cs
using System;
public delegate void EventHandler();

public class A
{
   public event EventHandler Click;

   public static void OnClick()
   {
      EventHandler eh;
      A a = new A();
      eh = a.Click;
   }

   public static void Main()
   {
   }
}

public class B
{
   public int Foo ()
   {
      EventHandler eh = new EventHandler(A.OnClick);
      A a = new A();
      eh = a.Click;   // CS0070
      // try the following line instead
      // a.Click += eh;
      return 1;
   }
}


编译器错误 CS0071

[image]

Visual C# 参考：错误和警告
编译器错误 CS0071
示例 发送反馈意见

更新：2007 年 11 月

错误消息

事件的显式接口实现必须使用事件访问器语法

当显式实现在接口中声明的事件时，您必须手动提供通常由编译器提供的 add 和 remove 事件访问器。访问器代码可将类中的接口事件连接到另一事件（稍后在本主题中演示），或连接到其自身的委托类型。有关更多信息，请参见如何：实现接口事件（C# 编程指南）。

示例

下面的示例生成 CS0071。

复制代码
// CS0071.cs
public delegate void MyEvent(object sender);

interface ITest
{
    event MyEvent Clicked;
}

class Test : Itest
{
    event MyEvent ITest.Clicked;  // CS0071

    // try the following code instead
/*
private MyEvent clicked;

    event MyEvent Itest.Clicked
    {
        add
        {
            clicked += value;
        }
        remove
        {
            clicked -= value;
        }
    }
*/
    public static void Main() { }
}


编译器错误 CS0072

[image]

Visual C# 参考：错误和警告
编译器错误 CS0072
发送反馈意见

更新：2007 年 11 月

错误消息

“event”：无法重写；“method”不是事件

event 只能重写另一事件。有关更多信息，请参见事件（C# 编程指南）。

下面的示例生成 CS0072：

复制代码
// CS0072.cs
delegate void MyDelegate();

class Test1
{
   public virtual event MyDelegate MyEvent;
   public virtual void VMeth()
   {
   }

   public void FireAway()
   {
      if (MyEvent != null)
         MyEvent();
   }
}

class Test2 : Test1
{
   public override event MyDelegate VMeth   // CS0072
   // uncomment the following lines to resolve
   // public override event MyDelegate MyEvent
   {
      add
      {
         VMeth += value;
         // MyEvent += value;
      }
      remove
      {
         VMeth -= value;
         // MyEvent -= value;
      }
   }

   public static void Main()
   {
   }
}


编译器错误 CS0073

[image]

Visual C# 参考：错误和警告
编译器错误 CS0073
发送反馈意见

更新：2007 年 11 月

错误消息

add 访问器或 remove 访问器必须有一个主体

event 定义中的 add 或 remove 关键字必须有体。有关更多信息，请参见事件（C# 编程指南）。

下面的示例生成 CS0073：

复制代码
// CS0073.cs
delegate void del();

class Test
{
   public event del MyEvent
   {
      add;   // CS0073
      // try the following lines instead
      // add
      // {
      //    MyEvent += value;
      // }
      remove
      {
         MyEvent -= value;
      }

   }

   public static void Main()
   {
   }
}


编译器错误 CS0074

[image]

Visual C# 参考：错误和警告
编译器错误 CS0074
发送反馈意见

更新：2007 年 11 月

错误消息

“event”: 抽象事件不能有初始值设定项

如果 event 被标记为 abstract，它就不能初始化。有关更多信息，请参见事件（C# 编程指南）。

下面的示例生成 CS0074：

复制代码
// CS0074.cs
delegate void D();

abstract class Test
{
   public abstract event D e = null;   // CS0074
   // try the following line instead
   // public abstract event D e;

   public static void Main()
   {
   }
}


编译器错误 CS0075

[image]

Visual C# 参考：错误和警告
编译器错误 CS0075
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

若要强制转换某个负值，必须将该值放在括号内

如果您使用标识预定义类型的关键字进行强制转换，则不需要括号。否则，必须用括号括起来，因为 (x) –y 不会被视为强制转换表达式。摘自 C# 规范的第 7.6.6 节：

从消除歧义规则可以导出下述结论：如果 x 和 y 是标识符，则 (x)y、(x)(y) 和 (x)(-y) 是强制转换表达式，但 (x)-y 不是，即使 x 标识的是类型。但是，如果 x 是一个标识预定义类型（如 int）的关键字，则所有这四种形式均为强制转换表达式（因为这种关键字本身不可能是表达式）。

下面的代码生成 CS0075：

复制代码
// CS0075
namespace MyNamespace
{
    enum MyEnum { }
    public class MyClass
    {
        public static void Main()
        {
            // To fix the error, place the negative
            // values below in parentheses
            int i = (System.Int32) - 4; //CS0075
            MyEnum e = (MyEnum) - 1;    //CS0075
            System.Console.WriteLine(i); //to avoid warning
            System.Console.WriteLine(e); //to avoid warning
        }
    }
}

请参见

参考

强制转换和类型转换（C# 编程指南）


编译器错误 CS0076

[image]

Visual C# 参考：错误和警告
编译器错误 CS0076
发送反馈意见

更新：2007 年 11 月

错误消息

枚举名“value__”是保留字，无法使用

枚举中的项不能有称为 value__ 的标识符。


编译器错误 CS0077

[image]

Visual C# 参考：错误和警告
编译器错误 CS0077
发送反馈意见

更新：2007 年 11 月

错误消息

as 运算符必须与引用类型或可为 null 的类型一起使用(“int”是不可为 null 的值类型)。

向 as 运算符传递了值类型。由于 as 可以返回 null，因此只能向它传递引用类型或可以为 null 的类型有关可为 null 的类型的更多信息，请参见可空类型（C# 编程指南）。

下面的示例生成 CS0077：

复制代码
// CS0077.cs
using System;

class C
{
}

struct S
{
}

class M
{
   public static void Main()
   {
      object o1, o2;
      C c;
      S s;

      o1 = new C();
      o2 = new S();

      s = o2 as S;  // CS0077, S is not a reference type.
      // try the following line instead
      // c = o1 as C;
   }
}


编译器错误 CS0079

[image]

Visual C# 参考：错误和警告
编译器错误 CS0079
发送反馈意见

更新：2007 年 11 月

错误消息

事件“event”只能出现在 += 或 -= 的左边

未正确调用 event。有关更多信息，请参见事件（C# 编程指南）和委托（C# 编程指南）。

下面的示例生成 CS0079：

复制代码
// CS0079.cs
using System;

public delegate void MyEventHandler();

public class Class1
{
   private MyEventHandler _e;

   public event MyEventHandler Pow
   {
      add
      {
         _e += value;
         Console.WriteLine("in add accessor");
      }
      remove
      {
         _e -= value;
         Console.WriteLine("in remove accessor");
      }
   }

   public void Handler()
   {
   }

   public void Fire()
   {
      if (_e != null)
      {
         Pow();   // CS0079
         // try the following line instead
         // _e();
      }
   }

   public static void Main()
   {
      Class1 p = new Class1();
      p.Pow += new MyEventHandler(p.Handler);
      p._e();
      p.Pow += new MyEventHandler(p.Handler);
      p._e();
      p._e -= new MyEventHandler(p.Handler);
      if (p._e != null)
      {
         p._e();
      }
      p.Pow -= new MyEventHandler(p.Handler);
      if (p._e != null)
      {
         p._e();
      }
   }
}


编译器错误 CS0080

[image]

Visual C# 参考：错误和警告
编译器错误 CS0080
发送反馈意见

更新：2007 年 11 月

错误消息

在非泛型声明上不允许使用约束

找到的语法只能用于泛型声明才能对类型参数应用约束。有关更多信息，请参见泛型（C# 编程指南）。

下面的示例生成 CS0080，因为 MyClass 不是泛型类，并且 Foo 不是泛型方法。

复制代码
namespace MyNamespace
{
    public class MyClass where MyClass : System.IDisposable // CS0080    //the following line shows an example of correct syntax
    //public class MyClass<T> where T : System.IDisposable
    {
        public void Foo() where Foo : new() // CS0080
        //the following line shows an example of correct syntax
        //public void Foo<U>() where U : struct
        {
        }
    }

    public class Program
    {
        public static void Main()
        {
        }
    }
}


编译器错误 CS0081

[image]

Visual C# 参考：错误和警告
编译器错误 CS0081
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

类型参数声明必须是标识符而不是类型

当您声明泛型方法或类型时，请将类型参数指定为标识符，例如“T”或“inputType”。客户端代码在调用方法时会提供类型，以替换方法体或类体中标识符的每个匹配项。有关更多信息，请参见 泛型类型参数（C# 编程指南）。

复制代码
// CS0081.cs
class MyClass
{
   public void F<int>() {}   // CS0081
   public void F<T>(T input) {}   // OK

   public static void Main()
   {
      MyClass a = new MyClass();
      a.F<int>(2);
      a.F<double>(.05);
   }
}

请参见

参考

泛型（C# 编程指南）


编译器错误 CS0082

[image]

Visual C# 参考：错误和警告
编译器错误 CS0082
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

类型“type”已保留了一个名为“name”的具有相同参数类型的成员

在编译时将属性转换为在标识符前面带有 get_ 和/或 set_ 的方法。如果您自己定义的方法与该方法名称发生冲突，则会生成错误。

示例

下面的示例生成 CS0082：

复制代码
//cs0082.cs
class MyClass
{

    //property
    public int MyProp
    {
        get //CS0082
        {
            return 1;
        }
    }

    //conflicting Get
    public int get_MyProp()
    {
        return 2;
    }

    public static int Main()
    {
        return 1;
    }
}

请参见

参考

属性（C# 编程指南）


编译器错误 CS0100

[image]

Visual C# 参考：错误和警告
编译器错误 CS0100
发送反馈意见

更新：2007 年 11 月

错误消息

参数名“parameter name”重复

方法声明多次使用了同一个参数名。参数名在方法声明中必须唯一。有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0100：

复制代码
// CS0100.cs
namespace x
{
   public class a
   {
      public static void f(int i, char i)   // CS0100
      // try the following line instead
      // public static void f(int i, char j)
      {
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0101

[image]

Visual C# 参考：错误和警告
编译器错误 CS0101
发送反馈意见

更新：2007 年 11 月

错误消息

命名空间“namespace”已经包含了“type”的定义

namespace 有重复的标识符。重命名或删除其中一个重复的标识符。有关更多信息，请参见命名空间（C# 编程指南）

下面的示例生成 CS0101：

复制代码
// CS0101.cs
namespace MyNamespace
{
   public class MyClass
   {
      static public void Main()
      {
      }
   }

   public class MyClass   // CS0101
   {
   }
}


编译器错误 CS0102

[image]

Visual C# 参考：错误和警告
编译器错误 CS0102
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型“type name”已经包含“identifier”的定义

一个类在同一范围内包含多个具有相同名称的标识符的声明。若要修复此错误，请重命名重复的标识符。

示例

下面的示例生成 CS0102。

复制代码
// CS0102.cs
// compile with: /target:library
namespace MyApp
{
   public class MyClass
   {
      string s = "Hello";
      string s = "Goodbye";   // CS0102

      public void GetString()
      {
         string s = "Hello again";   // method scope, no error
      }
   }
}


编译器错误 CS0103

[image]

Visual C# 参考：错误和警告
编译器错误 CS0103
发送反馈意见

更新：2007 年 11 月

错误消息

当前上下文中不存在名称“identifier”

试图使用类、命名空间或范围中不存在的名称。检查名称的拼写，并检查 using 语句和程序集引用，确保您尝试使用的名称可用。

如果在循环或者 try 或 if 块中声明一个变量，然后试图从封闭代码块或另一代码块访问此变量，会发生此错误。如下面的示例所示。

下面的示例生成 CS0103：

复制代码
// CS0103.cs
using System;

class MyClass
{
   public static void Main()
   {
      // MyClass conn = null;
      try
      {
         MyClass conn = new MyClass();   // delete this line
         // and uncomment the following line and the line above the try
         // conn = new MyClass();
      }
      catch(Exception e)
      {
         if (conn != null)   // CS0103
            Console.WriteLine("{0}", e);
      }
   }
}


编译器错误 CS0104

[image]

Visual C# 参考：错误和警告
编译器错误 CS0104
发送反馈意见

更新：2007 年 11 月

错误消息

“reference”是“identifier”和“identifier”之间不明确的引用

程序包含两个命名空间的 using 指令，而且代码引用了在这两个命名空间中均出现的名称。

下面的示例生成 CS0104：

复制代码
// CS0104.cs
using x;
using y;

namespace x
{
   public class Test
   {
   }
}

namespace y
{
   public class Test
   {
   }
}

public class a
{
   public static void Main()
   {
      Test test = new Test();   // CS0104, is Test in x or y namespace?
      // try the following line instead
      // y.Test test = new y.Test();
   }
}


编译器错误 CS0106

[image]

Visual C# 参考：错误和警告
编译器错误 CS0106
示例 发送反馈意见

更新：2007 年 11 月

错误消息

修饰符“modifier”对该项无效

类或接口成员是用无效的访问修饰符标记的。下列示例描述了一些无效的修饰符：

  • 在接口方法上不允许使用 static 和 public 修饰符。

  • 在显式接口声明上不允许使用 public 关键字。在这种情况下，请从显式接口声明中移除 public 关键字。

  • 在显式接口声明上不允许使用 abstract 关键字，因为显式接口实现永远不能被重写。

在以前的 Visual Studio 版本中，不允许在类上使用 static 修饰符，但允许 static 类以 Microsoft Visual Studio 2005 开头。

有关更多信息，请参见 接口（C# 编程指南）

示例

下面的示例生成 CS0106。

复制代码
// CS0106.cs
namespace MyNamespace
{
   interface I
   {
      void m();
      static public void f();   // CS0106
   }

   public class MyClass
   {
      public void I.m() {}   // CS0106
      public static void Main() {}
   }
}


编译器错误 CS0107

[image]

Visual C# 参考：错误和警告
编译器错误 CS0107
发送反馈意见

更新：2007 年 11 月

错误消息

不止一个保护修饰符

类成员上只允许使用一个访问修饰符（public、private、protected 或 internal），internal protected 是个例外。

下面的示例生成 CS0107：

复制代码
// CS0107.cs
public class C
{
   private internal void f()   // CS0107, delete private or internal
   {
   }

   public static int Main()
   {
      return 1;
   }
}


编译器错误 CS0110

[image]

Visual C# 参考：错误和警告
编译器错误 CS0110
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“const declaration”的常数值计算涉及循环定义

const 变量 (a) 的声明不能引用另一个也引用 (a) 的 const 变量 (b)。

下面的示例生成 CS0110：

复制代码
// CS0110.cs
namespace MyNamespace
{
   public class A
   {
      public static void Main()
      {
      }
   }

   public class B : A
   {
      public const int i = c + 1;   // CS0110, c already references i
      public const int c = i + 1;
      // the following line would be OK
      // public const int c = 10;
   }
}

请参见

参考

常量（C# 编程指南）


编译器错误 CS0111

[image]

Visual C# 参考：错误和警告
编译器错误 CS0111
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型“class”已经定义了一个具有相同参数类型的名为“member”的成员

如果一个类包含两个具有相同名称和参数类型的成员声明，则会发生 CS0111。有关更多信息，请参见方法（C# 编程指南）。

示例

下面的示例生成 CS0111。

复制代码
// CS0111.cs
class A
{
   void Test() { }
   public static void Test(){}   // CS0111

   public static void Main() {}
}


编译器错误 CS0112

[image]

Visual C# 参考：错误和警告
编译器错误 CS0112
发送反馈意见

更新：2007 年 11 月

错误消息

静态成员“function”不能标记为 override、virtual 或 abstract

使用 override、virtual 或 abstract 关键字的任何方法声明不能还使用 static 关键字。

有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0112：

复制代码
// CS0112.cs
namespace MyNamespace
{
   abstract public class MyClass
   {
      public abstract void Foo();
   }
   public class MyClass2 : MyClass
   {
      override public static void Foo()   // CS0112, remove static keyword
      {
      }
      public static int Main()
      {
         return 0;
      }
   }
}


编译器错误 CS0113

[image]

Visual C# 参考：错误和警告
编译器错误 CS0113
发送反馈意见

更新：2007 年 11 月

错误消息

标记为 override 的成员“function”不能标记为 new 或 virtual

用 new 和 override 关键字标记方法是互斥的。

下面的示例生成 CS0113：

复制代码
// CS0113.cs
namespace MyNamespace
{
   abstract public class MyClass
   {
      public abstract void Foo();
   }

   public class MyClass2 : MyClass
   {
      override new public void Foo()   // CS0113, remove new keyword
      {
      }

      public static int Main()
      {
         return 0;
      }
   }
}


编译器错误 CS0115

[image]

Visual C# 参考：错误和警告
编译器错误 CS0115
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“function”：没有找到适合的方法来重写

方法被标记为 override，但编译器未找到可重写的方法。有关更多信息，请参见override（C# 参考）和了解何时使用 Override 和 New 关键字（C# 编程指南）。

示例

下面的示例生成 CS0115。可以用下列两种方法之一解决 CS0115：

  • 从 MyClass2 的方法中移除 override 关键字。

  • 将 MyClass1 用作 MyClass2 的基类。

复制代码
// CS0115.cs
namespace MyNamespace
{
    abstract public class MyClass1
    {
        public abstract int f();
    }

    abstract public class MyClass2
    {
        public override int f()   // CS0115
        {
            return 0;
        }

        public static void Main()
        {
        }
    }
}


编译器错误 CS0116

[image]

Visual C# 参考：错误和警告
编译器错误 CS0116
示例 发送反馈意见

更新：2007 年 11 月

错误消息

命名空间并不直接包含诸如字段或方法之类的成员

在 namespace 内，编译器只接受类、结构、联合、枚举、接口和委托。当 C/C++ 开发人员忘记了在 C# 中，方法和变量必须在结构或类中定义时，通常会生成此错误。有关更多信息，请参见C# 程序的通用结构（C# 编程指南）。

示例

下面的示例生成 CS0116：

复制代码
// CS0116.cs
namespace x
{
    using System;

    // method must be in class/struct
    void Method(string str) // CS0116
    {
        Console.WriteLine(str);
    }
}


编译器错误 CS0117

[image]

Visual C# 参考：错误和警告
编译器错误 CS0117
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”不包含“identifier”的定义

  • 当引用的成员对于此数据类型不存在时，某些情况下将出现此错误。

示例

下面的示例生成 CS0117：

复制代码
// CS0117.cs
public class BaseClass { }

    public class base021 : BaseClass
    {
        public void TestInt()
        {
            int i = base.someMember; //CS0117
        }
        public static int Main()
        {
            return 1;
        }
    }


编译器错误 CS0118

[image]

Visual C# 参考：错误和警告
编译器错误 CS0118
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“construct1_name”是一个“construct1”，但其用法与“construct2”一样

编译器检测到以错误的方式使用了某个构造，或对某个构造尝试了不允许的操作。一些常见示例包括：

  • 尝试实例化命名空间（而不是类）

  • 尝试调用字段（而不是方法）

  • 尝试将类型用作变量

  • 尝试使用外部别名作为类型。

若要解决此错误，请确保您正在执行的操作对其所针对的类型是有效的。

示例

下面的示例生成 CS0118。

复制代码
// CS0118.cs
// compile with: /target:library
namespace MyNamespace
{
   class MyClass
   {
      // MyNamespace not a class
      MyNamespace ix = new MyNamespace ();   // CS0118
   }
}


编译器错误 CS0119

[image]

Visual C# 参考：错误和警告
编译器错误 CS0119
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“construct1_name”是一个“construct1”，它在给定的上下文中无效。

编译器检测到意外构造，如下所示：

  • 类构造函数在条件语句中是无效的测试表达式。

  • 在引用数组元素时使用的是类名而不是实例名。

  • 按结构或类那样使用方法标识符。

示例

下面的示例生成 CS0119。

复制代码
// CS0119.cs
using System;
public class MyClass
{
   public static void Test() {}

   public static void Main()
   {
      Console.WriteLine(Test.x);   // CS0119
   }
}


编译器错误 CS0120

[image]

Visual C# 参考：错误和警告
编译器错误 CS0120
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

非静态的字段、方法或属性“member”要求对象引用

必须首先创建对象实例，才能使用非静态的字段、方法或属性。有关静态方法的更多信息，请参见静态类和静态类成员（C# 编程指南）。有关创建类的实例的更多信息，请参见实例构造函数（C# 编程指南）。

下面的示例生成 CS0120：

复制代码
// CS0120_1.cs
public class MyClass
{
   // Non-static field
   public int i;
   // Non-static method
   public void f(){}
   // Non-static property
   int Prop
   {
      get
      {
         return 1;
      }
   }

   public static void Main()
   {
      i = 10;   // CS0120
      f();   // CS0120
      int p = Prop;   // CS0120
      // try the following lines instead
      // MyClass mc = new MyClass();
      // mc.i = 10;
      // mc.f();
      // int p = mc.Prop;
   }
}

如果从静态方法调用非静态方法，也会生成 CS0120，如下所示：

复制代码
// CS0120_2.cs
// CS0120 expected
using System;

public class MyClass
{
   public static void Main()
   {
      TestCall();   // CS0120
      // To call a non-static method from Main,
      // first create an instance of the class.
      // Use the following two lines instead:
      // MyClass anInstanceofMyClass = new MyClass();
      // anInstanceofMyClass.TestCall();
   }

   public void TestCall()
   {
   }
}

同样，静态方法不能调用实例方法，除非显式给它提供了类的实例，如下所示：

复制代码
// CS0120_3.cs
using System;

public class MyClass
{
   public static void Main()
   {
      do_it("Hello There");   // CS0120
   }

   private void do_it(string sText)
   // You could also add the keyword static to the method definition:
   // private static void do_it(string sText)
   {
      Console.WriteLine(sText);
   }
}

请参见

参考

类和结构（C# 编程指南）


编译器错误 CS0121

[image]

Visual C# 参考：错误和警告
编译器错误 CS0121
示例 发送反馈意见

更新：2007 年 11 月

错误消息

在以下方法或属性之间的调用不明确：“method1”和“method2”

因隐式转换的缘故，编译器无法调用重载方法的某种形式。可以用以下方法纠正该错误：

  • 以不发生隐式转换的方式指定此方法的参数。

  • 移除此方法的所有重载。

  • 在调用方法之前，强制转换到正确的类型。

示例

下面的示例生成 CS0121：

复制代码
// CS0121.cs
public class C
{
   void f(int i, double d)
   {
   }

   void f(double d, int i)
   {
   }

   public static void Main()
   {
      C c = new C();

      c.f(1, 1);   // CS0121
      // try the following line instead
      // c.f(1, 1.0);
      // or
      // c.f(1.0, 1);
      // or
      // c.f(1, (double)1);   // cast and specify which method to call
   }
}

下面的示例在 Microsoft Visual Studio 2008 中而不是在 Visual Studio 2005 中生成 CS0121：

复制代码
    //CS0121_2.cs
    class Program2
    {

        static int ol_invoked = 0;

        delegate int D1(int x);
        delegate T D1<T>(T x);
        delegate T D1<T, U>(U u);

        static void F(D1 d1) { ol_invoked = 1; }
        static void F<T>(D1<T> d1t) { ol_invoked = 2; }
        static void F<T, U>(D1<T, U> d1t) { ol_invoked = 3; }

        static int Test001()
        {
            F(delegate(int x) { return 1; }); //CS0121
            if (ol_invoked == 1)
                return 0;
            else
                return 1;
        }

        static int Main()
        {
            return Test001();
        }
    }


编译器错误 CS0122

[image]

Visual C# 参考：错误和警告
编译器错误 CS0122
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不可访问“member”，因为它受保护级别限制

类成员的访问修饰符禁止访问该成员。有关更多信息，请参见访问修饰符（C# 编程指南）。

出现此错误（未在下面的示例中显示）的一个原因是：在友元程序集的目标上省略了 /out 编译器标志。有关更多信息，请参见 友元程序集（C# 编程指南） 和 /out（设置输出文件名）（C# 编译器选项）

示例

下面的示例生成 CS0122：

复制代码
// CS0122.cs
public class MyClass
{
    // Make public to resolve CS0122
    void Foo()
    {
    }
}

public class MyClass2
{
    public static int Main()
    {
        MyClass a = new MyClass();
        // Foo is private
        a.Foo();   // CS0122
        return 0;
   }
}


编译器错误 CS0123

[image]

Visual C# 参考：错误和警告
编译器错误 CS0123
发送反馈意见

更新：2007 年 11 月

错误消息

“method”的重载均与委托“delegate”不匹配

试图创建委托时失败，因为未使用正确的签名。必须使用与委托声明相同的签名声明委托实例。

可以通过调整方法或委托签名解决此错误。有关更多信息，请参见委托（C# 编程指南）。

下面的示例生成 CS0123。

复制代码
// CS0123.cs
delegate void D();
delegate void D2(int i);

public class C
{
   public static void f(int i) {}

   public static void Main()
   {
      D d = new D(f);   // CS0123
      D2 d2 = new D2(f);   // OK
   }
}


编译器错误 CS0126

[image]

Visual C# 参考：错误和警告
编译器错误 CS0126
发送反馈意见

更新：2007 年 11 月

错误消息

需要一个类型可转换为“type”的对象

存在返回语句，但该语句并未返回所需类型的值。有关更多信息，请参见属性（C# 编程指南）。

下面的示例生成 CS0126：

复制代码
// CS0126.cs
public class a
{
   public int i
   {
      set
      {
      }
      get
      {
         return;   // CS0126, specify a value to return
      }
   }
}


编译器错误 CS0127

[image]

Visual C# 参考：错误和警告
编译器错误 CS0127
发送反馈意见

更新：2007 年 11 月

错误消息

由于“function”返回 void，返回关键字后面不得有对象表达式

具有 void 返回类型的方法无法返回值。有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0127：

复制代码
// CS0127.cs
namespace MyNamespace
{
   public class MyClass
   {
      public int hiddenMember2
      {
         get
         {
            return 0;
         }
         set   // CS0127, set has an implicit void return type
         {
            return 0;   // remove return statement to resolve this CS0127
         }
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0128

[image]

Visual C# 参考：错误和警告
编译器错误 CS0128
发送反馈意见

更新：2007 年 11 月

错误消息

已在此范围定义了名为“variable”的局部变量

编译器检测到两个同名的局部变量的声明。有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0128：

复制代码
// CS0128.cs
namespace MyNamespace
{
   public class MyClass
   {
      public static void Main()
      {
         char i;
         int i;   // CS0128
      }
   }
}


编译器错误 CS0131

[image]

Visual C# 参考：错误和警告
编译器错误 CS0131
示例 发送反馈意见

更新：2007 年 11 月

错误消息

赋值号左边必须是变量、属性或索引器

在赋值语句中，右边的值赋给左边。左边必须是变量、属性或索引器。

若要修复此错误，请确保所有运算符都位于右边，而左边是变量、属性或索引器。有关更多信息，请参见语句、表达式和运算符（C# 编程指南）。

示例

下面的示例生成 CS0131。

复制代码
// CS0131.cs
public class MyClass
{
    public int i = 0;
    public void MyMethod()
    {
        i++ = 1;   // CS0131
        // try the following line instead
        // i = 1;
    }
    public static void Main() { }
}

如果您试图对赋值运算符的左边执行算术运算（如下面的示例中所示），也可能发生此错误。

复制代码
// CS0131b.cs
public class C
{
    public static int Main()
    {
        int a = 1, b = 2, c = 3;
        if (a + b = c) // CS0131
        // try this instead
        // if (a + b == c)
            return 0;
        return 1;
    }
}


编译器错误 CS0132

[image]

Visual C# 参考：错误和警告
编译器错误 CS0132
发送反馈意见

更新：2007 年 11 月

错误消息

“constructor”：静态构造函数必须无参数

static 构造函数不能用一个或多个参数声明。有关更多信息，请参见构造函数（C# 编程指南）。

下面的示例生成 CS0132：

复制代码
// CS0132.cs
namespace MyNamespace
{
   public class MyClass
   {
      public MyClass(int i)
      {
      }
   }

   public class MyClass2 : MyClass
   {
      static MyClass2(int i)   // CS0132
      {
      }
   }
}


编译器错误 CS0133

[image]

Visual C# 参考：错误和警告
编译器错误 CS0133
发送反馈意见

更新：2007 年 11 月

错误消息

指派给“variable”的表达式必须是常数

const 变量不能采用非常数的表达式作为它的值。有关更多信息，请参见常量（C# 编程指南）。

下面的示例生成 CS0133：

复制代码
// CS0133.cs
public class MyClass
{
   public const int i = c;   // CS0133, c is not constant
   public static int c = i;
   // try the following line instead
   // public const int i = 6;

   public static void Main()
   {
   }
}


编译器错误 CS0134

[image]

Visual C# 参考：错误和警告
编译器错误 CS0134
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“variable”的类型为“type”。只能用 null 对引用类型(字符串除外)的常量字段进行初始化。

常量表达式是可在编译时可完全计算的表达式。由于应用 new 运算符是创建引用类型的非空值的唯一方法，并且常量表达式中不允许使用 new 运算符，因此除字符串外，引用类型常量的唯一可能值为 null。

如果尝试创建 const 字符串数组时遇到此错误，则解决方案是使该数组成为只读数组，并在构造函数中将其初始化。

示例

下面的示例生成 CS0134。

复制代码
// CS0134.cs
// compile with: /target:library
class MyTest {}

class MyClass
{
   const MyTest test = new MyTest();   // CS0134

   //OK
   const MyTest test2 = null;
   const System.String test3 = "test";
}


编译器错误 CS0135

[image]

Visual C# 参考：错误和警告
编译器错误 CS0135
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“declaration1”与声明“declaration2”冲突

编译器不允许名称隐藏，名称隐藏通常会导致代码中的逻辑错误。

示例

下面的示例生成 CS0135：

复制代码
// CS0135.cs
public class MyClass2
{
   public static int i = 0;

   public static void Main()
   {
      {
         int i = 4;
         i++;
      }
      i = 0;   // CS0135
   }
}

C# 语言规范 7.5.2.1 节：

对于表达式或声明符中以简单名称形式给定的标识符的每个匹配项，直接封闭局部变量声明空间 (§CNDJ6nn5us4RjIIAqgBLqQsCAAAACAAAAA4AAABfAFIAZQBmADQANgAxADYAMgAyADEAMwA4AAAA REF _Ref461622138 \r \h 3.3) 内的表达式或声明符中与简单名称相同的标识符的其他每个匹配项都必须引用同一个实体。此规则可确保名称的含义在给定的块、switch 块、for-、foreach- 或 using 语句或匿名函数中始终相同。


编译器错误 CS0136

[image]

Visual C# 参考：错误和警告
编译器错误 CS0136
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能在此范围内声明名为“var”的局部变量，因为这样会使“var”具有不同的含义，而它已经用于“parent or current/child”范围以表示其他内容

变量声明隐藏了本应在范围内的另一个声明。请重命名在生成 CS0136 的行上声明的变量。

示例

下面的示例生成 CS0136：

复制代码
// CS0136.cs
namespace MyNamespace
{
   public class MyClass
   {
      public static void Main()
      {
         int i = 0;
         {
            char i = 'a';   // CS0136, hides int i
         }
         i++;
      }
   }
}

C# 语言规范 7.5.2.1 节：

对于表达式或声明符中以简单名称形式给定的标识符的每个匹配项，直接封闭局部变量声明空间 (§CNDJ6nn5us4RjIIAqgBLqQsCAAAACAAAAA4AAABfAFIAZQBmADQANgAxADYAMgAyADEAMwA4AAAA REF _Ref461622138 \r \h 3.3) 内的表达式或声明符中与简单名称相同的标识符的其他每个匹配项都必须引用同一个实体。此规则可确保名称的含义在给定的块、switch 块、for-、foreach- 或 using 语句或匿名函数中始终相同。


编译器错误 CS0138

[image]

Visual C# 参考：错误和警告
编译器错误 CS0138
发送反馈意见

更新：2007 年 11 月

错误消息

using 命名空间指令只能应用于命名空间；“type”是一个类型，而不是命名空间

using 指令只能将命名空间的名称用作参数。有关更多信息，请参见命名空间（C# 编程指南）。

下面的示例生成 CS0138：

复制代码
// CS0138.cs
using System.Object;   // CS0138


编译器错误 CS0139

[image]

Visual C# 参考：错误和警告
编译器错误 CS0139
发送反馈意见

更新：2007 年 11 月

错误消息

没有要中断或继续的封闭循环

在循环外遇到了 break 或 continue 语句。

有关更多信息，请参见跳转语句。

下面的示例生成 CS0139 两次：

复制代码
// CS0139.cs
namespace x
{
   public class a
   {
      public static void Main()
      {
         continue;  // CS0139
         break;     // CS0139
      }
   }
}


编译器错误 CS0140

[image]

Visual C# 参考：错误和警告
编译器错误 CS0140
发送反馈意见

更新：2007 年 11 月

错误消息

标签“label”重复

同名的标签出现了两次。有关更多信息，请参见goto（C# 参考）。

下面的示例生成 CS0140：

复制代码
// CS0140.cs
namespace MyNamespace
{
   public class MyClass
   {
      public static void Main()
      {
         label1: int i = 0;
         label1: int j = 0;   // CS0140, comment this line to resolve
         goto label1;
      }
   }
}


编译器错误 CS0143

[image]

Visual C# 参考：错误和警告
编译器错误 CS0143
发送反馈意见

更新：2007 年 11 月

错误消息

类型“class”未定义构造函数

没有适当的构造函数可用。

下面的示例生成 CS0143：

复制代码
// CS0143.cs
class MyClass
{
   static public void Main ()
   {
      double d = new double(4.5);   // CS0143
   }
}


编译器错误 CS0144

[image]

Visual C# 参考：错误和警告
编译器错误 CS0144
发送反馈意见

更新：2007 年 11 月

错误消息

无法创建抽象类或接口“interface”的实例

不能创建 abstract 类或 interface 的实例。有关更多信息，请参见接口（C# 编程指南）。

下面的示例生成 CS0144：

复制代码
// CS0144.cs
interface MyInterface
{
}
public class MyClass
{
   public static void Main()
   {
      MyInterface myInterface = new MyInterface ();   // CS0144
   }
}


编译器错误 CS0145

[image]

Visual C# 参考：错误和警告
编译器错误 CS0145
发送反馈意见

更新：2007 年 11 月

错误消息

常数字段要求提供一个值

必须初始化 const 变量。有关更多信息，请参见常量（C# 编程指南）。

下面的示例生成 CS0145：

复制代码
// CS0145.cs
class MyClass
{
   const int i;   // CS0145
   // try the following line instead
   // const int i = 0;

   public static void Main()
   {
   }
}


编译器错误 CS0146

[image]

Visual C# 参考：错误和警告
编译器错误 CS0146
发送反馈意见

更新：2007 年 11 月

错误消息

涉及“class1”和“class2”的循环基类依赖项

类的继承列表包括对自身的直接或间接引用。类不能从自身继承。有关更多信息，请参见继承（C# 编程指南）。

下面的示例生成 CS0146：

复制代码
// CS0146.cs
namespace MyNamespace
{
   public interface InterfaceA
   {
   }

   public class MyClass : InterfaceA, MyClass2
   {
      public void Main()
      {
      }
   }

   public class MyClass2 : MyClass   // CS0146
   {
   }
}


编译器错误 CS0148

[image]

Visual C# 参考：错误和警告
编译器错误 CS0148
发送反馈意见

更新：2007 年 11 月

错误消息

委托“delegate”没有有效的构造函数

您导入并使用了用另一个编译器创建的托管程序（即使用 .NET Framework 公共语言运行库的托管程序）。该编译器允许了格式错误的 delegate 构造函数。有关更多信息，请参见委托（C# 编程指南）。


编译器错误 CS0149

[image]

Visual C# 参考：错误和警告
编译器错误 CS0149
发送反馈意见

更新：2007 年 11 月

错误消息

应输入方法名称

在创建 delegate 时指定方法。有关更多信息，请参见委托（C# 编程指南）。

下面的示例生成 CS0149：

复制代码
// CS0149.cs
using System;

delegate string MyDelegate(int i);

class MyClass
{
   // class member-field of the declared delegate type
   static MyDelegate dt;

   public static void Main()
   {
      dt = new MyDelegate(17.45);   // CS0149
      // try the following line instead
      // dt = new MyDelegate(Func2);
      F(dt);
   }

   public static string Func2(int j)
   {
      Console.WriteLine(j);
      return j.ToString();
   }

   public static void F(MyDelegate myFunc)
   {
      myFunc(8);
   }
}


编译器错误 CS0150

[image]

Visual C# 参考：错误和警告
编译器错误 CS0150
发送反馈意见

更新：2007 年 11 月

错误消息

应输入常数值

在需要常数的地方发现了变量。有关更多信息，请参见 switch（C# 参考）。

下面的示例生成 CS0150：

复制代码
// CS0150.cs
namespace MyNamespace
{
   public class MyClass
   {
      public static void Main()
      {
         int i = 0;
         int j = 0;

         switch(i)
         {
            case j:   // CS0150, j is a variable int, not a constant int
            // try the following line instead
            // case 1:
         }
      }
   }
}

如果以变量值指定数组大小并以数组初始值设定项初始化数组，也会生成此错误。若要移除该错误，请以单独的语句初始化数组。

复制代码
// CS0150.cs
    namespace MyNamespace
    {
        public class MyClass
        {
            public static void Main()
            {
                int size = 2;
                double[] nums = new double[size] { 46.9, 89.4 }; //CS0150
                // Try the following lines instead
                // double[] nums = new double[size];
                // nums[0] = 46.9;
                // nums[1] = 89.4;
            }
        }

    }


编译器错误 CS0151

[image]

Visual C# 参考：错误和警告
编译器错误 CS0151
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入整型值

在需要整型数据类型的情况中使用了变量。有关更多信息，请参见类型（C# 编程指南）。

示例

当没有转换或者可用的隐式转换导致了不明确的情形时，会出现此错误。下面的示例生成 CS0151。

复制代码
// CS0151.cs
public class MyClass
{
   public static implicit operator int (MyClass aa)
   {
      return 0;
   }

   public static implicit operator long (MyClass aa)
   {
      return 0;
   }

   public static void Main()
   {
      MyClass a = new MyClass();

      // Compiler cannot choose between int and long
      switch (a)   // CS0151
      // try the following line instead
      // switch ((int)a)
      {
         case 1:
            break;
      }
   }
}

在 Visual Studio 2008 及更高版本中，void 方法调用会生成 CS0151。通过调用返回整数类型（如 int 或 long）的方法可以修复该错误。

复制代码
class C
{
    static void Main()
    {

        switch (M()) // CS0151
        {
            default:
                break;
        }
    }

    static void M()
    {
    }
}


编译器错误 CS0152

[image]

Visual C# 参考：错误和警告
编译器错误 CS0152
发送反馈意见

更新：2007 年 11 月

错误消息

标签“label”已经出现在此 switch 语句中

在 switch 语句中重复使用了某个标签。有关更多信息，请参见switch（C# 参考）。

下面的示例生成 CS0152：

复制代码
// CS0152.cs
namespace MyNamespace
{
   public class MyClass
   {
      public static void Main()
      {
         int i = 0;

         switch (i)
         {
            case 1:
               i++;
               return;

            case 1:   // CS0152, two case 1 statements
               i++;
               return;
         }
      }
   }
}


编译器错误 CS0153

[image]

Visual C# 参考：错误和警告
编译器错误 CS0153
发送反馈意见

更新：2007 年 11 月

错误消息

goto case 只在 switch 语句中有效

试图在 switch 语句外部使用 switch 语法。有关更多信息，请参见switch（C# 参考）。

下面的示例生成 CS0153：

复制代码
// CS0153.cs
public class a
{
   public static void Main()
   {
      goto case 5;   // CS0153
   }
}


编译器错误 CS0154

[image]

Visual C# 参考：错误和警告
编译器错误 CS0154
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性或索引器“property”无法用于此上下文中，因为它缺少 get 访问器

使用属性的尝试失败，因为该属性中未定义 get 访问器方法。有关更多信息，请参见字段（C# 编程指南）。

示例

下面的示例生成 CS0154：

复制代码
// CS0154.cs
public class MyClass2
{
    public int i
    {
        set
        {
        }
        // uncomment the get method to resolve this error
        /*
        get
        {
            return 0;
        }
        */
    }
}

public class MyClass
{
    public static void Main()
    {
        MyClass2 myClass2 = new MyClass2();
        int j = myClass2.i;   // CS0154, no get method
    }
}


编译器错误 CS0155

[image]

Visual C# 参考：错误和警告
编译器错误 CS0155
发送反馈意见

更新：2007 年 11 月

错误消息

捕获或抛弃的类型必须从 System.Exception 派生

试图将不是从 System.Exception 派生的数据类型传递到 catch 块中。只有从 System.Exception 派生的数据类型才可以传递到 catch 块。有关更多信息，请参见异常处理语句和异常和异常处理（C# 编程指南）。

下面的示例生成 CS0155：

复制代码
// CS0155.cs
using System;

namespace MyNamespace
{
    public class MyClass2
    // try the following line instead
    // public class MyClass2 : Exception
    {
    }
    public class MyClass
    {
        public static void Main()
        {
            try
            {
            }
            catch (MyClass2)   // CS0155, resolves if you derive MyClass2 from Exception
            {
            }
        }
    }
}


编译器错误 CS0156

[image]

Visual C# 参考：错误和警告
编译器错误 CS0156
发送反馈意见

更新：2007 年 11 月

错误消息

在嵌套在最近的封闭 catch 子句内部的 finally 子句内不允许使用不带参数的 throw 语句

不带参数的 throw 语句只能出现在不带参数的 catch 子句中。

有关更多信息，请参见异常处理语句和异常和异常处理（C# 编程指南）。

下面的示例生成 CS0156：

复制代码
// CS0156.cs
using System;

namespace MyNamespace
{
   public class MyClass2 : Exception
   {
   }

   public class MyClass
   {
      public static void Main()
      {
         try
         {
            throw;   // CS0156
         }

         catch(MyClass2)
         {
            throw;   // this throw is valid
         }
      }
   }
}


编译器错误 CS0157

[image]

Visual C# 参考：错误和警告
编译器错误 CS0157
发送反馈意见

更新：2007 年 11 月

错误消息

控制不能离开 finally 子句主体

finally 子句中的所有语句必须执行。有关更多信息，请参见异常处理语句和异常和异常处理（C# 编程指南）。

下面的示例生成 CS0157：

复制代码
// CS0157.cs
using System;
namespace MyNamespace
{
   public class MyClass2 : Exception
   {
   }

   public class MyClass
   {
      public static void Main()
      {
         try
         {
         }

         finally
         {
            return;   // CS0157, cannot leave finally clause
         }
      }
   }
}


编译器错误 CS0158

[image]

Visual C# 参考：错误和警告
编译器错误 CS0158
发送反馈意见

更新：2007 年 11 月

错误消息

在包含的范围中标签“label”遮盖了具有同样名称的另一个标签

内部范围中的标签隐藏了外部范围中的同名标签。有关更多信息，请参见goto（C# 参考）。

下面的示例生成 CS0158：

复制代码
// CS0158.cs
namespace MyNamespace
{
   public class MyClass
   {
      public static void Main()
      {
         goto lab1;
         lab1:
         {
            lab1:
            goto lab1;   // CS0158
         }
      }
   }
}


编译器错误 CS0159

[image]

Visual C# 参考：错误和警告
编译器错误 CS0159
发送反馈意见

更新：2007 年 11 月

错误消息

goto 语句范围内没有“label”这样的标签

在 goto 语句范围内无法找到 goto 语句所引用的标签。

下面的示例生成 CS0159：

复制代码
// CS0159.cs
public class Class1
{
   public static void Main()
   {
      int i = 0;

      switch (i)
      {
         case 1:
            goto case 3;   // CS0159, case 3 label does not exist
         case 2:
            break;
      }
      goto NOWHERE;   // CS0159, NOWHERE label does not exist
   }
}


编译器错误 CS0160

[image]

Visual C# 参考：错误和警告
编译器错误 CS0160
发送反馈意见

更新：2007 年 11 月

错误消息

上一个 catch 子句已经捕获了此类型或超类型(“type”)的所有异常

一系列 catch 语句需要按派生的递减顺序排列。例如，派生程度最高的对象必须首先出现。

有关更多信息，请参见异常处理语句和异常和异常处理（C# 编程指南）。

下面的示例生成 CS0160：

复制代码
// CS0160.cs
public class MyClass2 : System.Exception {}
public class MyClass
{
   public static void Main()
   {
      try {}

      catch(System.Exception) {}   // Second-most derived; should be second catch
      catch(MyClass2) {}   // CS0160  Most derived; should be first catch
   }
}


编译器错误 CS0161

[image]

Visual C# 参考：错误和警告
编译器错误 CS0161
发送反馈意见

更新：2007 年 11 月

错误消息

“method”：并非所有的代码路径都返回值

返回值的方法必须在所有代码路径中都具有 return 语句。有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0161：

复制代码
// CS0161.cs
public class Test
{
   public static int Main() // CS0161
   {
      int i = 10;
      if (i < 10)
      {
         return i;
      }
      else
      {
         // uncomment the following line to resolve
         // return 1;
      }
   }
}


编译器错误 CS0163

[image]

Visual C# 参考：错误和警告
编译器错误 CS0163
发送反馈意见

更新：2007 年 11 月

错误消息

控制不能从一个 case 标签(“label”)贯穿到另一个 case 标签

对于包含一个或多个语句并且后跟另一个 case 语句的 case 语句，必须使用以下关键字之一显式终止：

  • return

  • goto

  • break

  • throw

  • continue

如果要实现“贯穿”行为，请使用 goto case #。有关更多信息，请参见 switch（C# 参考）

下面的示例生成 CS0163：

复制代码
// CS0163.cs
public class MyClass
{
   public static void Main()
   {
      int i = 0;

      switch (i)   // CS0163
      {
         case 1:
            i++;
            // uncomment one of the following lines to resolve
            // return;
            // break;
            // goto case 3;

         case 2:
            i++;
            return;

         case 3:
            i = 0;
            return;
      }
   }
}


编译器错误 CS0165

[image]

Visual C# 参考：错误和警告
编译器错误 CS0165
示例 发送反馈意见

更新： 2008 年 7 月

错误消息

使用了未赋值的局部变量“name”

C# 编译器不允许使用未初始化的变量。如果编译器检测到使用了可能未初始化的变量，就会生成 CS0165。有关更多信息，请参见字段（C# 编程指南）。请注意，当编译器遇到可能会导致使用未赋值变量的构造时，即使您的特定代码不会使用该变量，也会生成此错误。这样可以避免对明确赋值使用过度复杂的规则。

如果遇到此错误

有关更多信息，请参见 http://blogs.msdn.com/ericlippert/archive/2006/08/18/706398.aspx。

示例

下面的示例生成 CS0165：

复制代码
// CS0165.cs
using System;

class MyClass
{
   public int i;
}

class MyClass2
{
   public static void Main(string [] args)
   {
      int i, j;
      if (args[0] == "test")
      {
         i = 0;
      }

      /*
      // to resolve, either initialize the variables when declared
      // or provide for logic to initialize them, as follows:
      else
      {
         i = 1;
      }
      */

      j = i;   // CS0165, i might be uninitialized

      MyClass myClass;
      myClass.i = 0;   // CS0165
      // use new as follows
      // MyClass myClass = new MyClass();
      // myClass.i = 0;
   }
}

下面的代码在 Visual Studio 2008 中而不是在 Visual Studio 2005 中生成 CS0165：

复制代码
//cs0165_2.cs
class Program
{
    public static int Main()
    {
        int i1, i2, i3, i4, i5;

        // this is an error, because 'as' is an operator
       // that is not permitted in a constant expression.
        if (null as object == null)
            i1 = 1;

        // this is an error, because 'is' is an operator that
        //  is not permitted in a constant expression.
        // warning CS0184: The given expression is never of the provided ('object') type
        if (!(null is object))
            i2 = 1;

        // this is an error, because a variable j3 is not
        // permitted in a constant expression.
        int j3 = 0;
        if ((0 == j3 * 0) && (0 == 0 * j3))
            i3 = 1;

        // this is an error, because a variable j4 is not
        // permitted in a constant expression.
        int j4 = 0;
        if ((0 == (j4 & 0)) && (0 == (0 & j4)))
            i4 = 1;

        // this might be an error, because a variable j5 is not
        // permitted in a constant expression.
        // warning CS1718: Comparison made to same variable; did you mean to compare something else?
        int? j5 = 1;
        if (j5 == j5)
            i5 = 1;

        System.Console.WriteLine("{0}{1}{2}{3}{4}{5}", i1, i2, i3, i4, i5); //CS0165

        return 1;
    }

}

此错误发生在递归委托定义中，通过在以下两个语句中定义该委托可避免此错误：

复制代码
class Program
    {
        delegate void Del();
        static void Main(string[] args)
        {
            Del d = delegate() { System.Console.WriteLine(d); }; //CS0165
// Try this instead:
// Del d = null;
//d = delegate() { System.Console.WriteLine(d); };
            d();
        }
    }

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 添加了有关递归委托的文本和代码示例。 | 内容 Bug 修复


编译器错误 CS0167

[image]

Visual C# 参考：错误和警告
编译器错误 CS0167
发送反馈意见

更新：2007 年 11 月

错误消息

委托“delegate”缺少 Invoke 方法

您导入并使用了用另一个编译器创建的托管程序（即使用 .NET Framework 公共语言运行库的托管程序）。该编译器允许了格式错误的 delegate。因此，Invoke 方法不可用。有关更多信息，请参见委托（C# 编程指南）。


编译器错误 CS0170

[image]

Visual C# 参考：错误和警告
编译器错误 CS0170
发送反馈意见

更新：2007 年 11 月

错误消息

使用了可能未赋值的字段“field”

结构中的字段在使用前未初始化。若要解决此问题，首先确定哪个字段尚未初始化，然后将其初始化，再尝试访问该字段。有关初始化结构的更多信息，请参见结构（C# 编程指南）和使用结构（C# 编程指南）。

下面的示例生成 CS0170：

复制代码
// CS0170.cs
public struct error
{
   public int i;
}

public class MyClass
{
   public static void Main()
   {
      error e;
      // uncomment the next line to resolve this error
      // e.i = 0;
      System.Console.WriteLine( e.i );   // CS0170 because
                                         //e.i was never assigned
   }
}


编译器错误 CS0171

[image]

Visual C# 参考：错误和警告
编译器错误 CS0171
发送反馈意见

更新：2007 年 11 月

错误消息

在控制返回到调用方之前，自动实现的属性“name”的支持字段必须被完全赋值。请考虑从构造函数初始值设定项中调用默认构造函数。

struct 中的构造函数必须初始化结构中的所有字段。有关更多信息，请参见构造函数（C# 编程指南）。

下面的示例生成 CS0171：

复制代码
// CS0171.cs
struct MyStruct
{
   MyStruct(int initField)   // CS0171
   {
      // i = initField;      // uncomment this line to resolve this error
   }
   public int i;
}

class MyClass
{
   public static void Main()
   {
      MyStruct aStruct = new MyStruct();
   }
}


编译器错误 CS0172

[image]

Visual C# 参考：错误和警告
编译器错误 CS0172
发送反馈意见

更新：2007 年 11 月

错误消息

无法确定条件表达式的类型，因为“type1”和“type2”可相互隐式转换

在条件语句中，: 运算符两边的类型必须可以相互转换。而且，不能有相互转换例程；只需一个转换。有关更多信息，请参见转换运算符（C# 编程指南）。

下面的示例生成 CS0172：

复制代码
// CS0172.cs
public class Square
{
   public class Circle
   {
      public static implicit operator Circle(Square aa)
      {
         return null;
      }

      public static implicit operator Square(Circle aa)
      // using explicit resolves this error
      // public static explicit operator Square(Circle aa)
      {
         return null;
      }
   }

   public static void Main()
   {
      Circle aa = new Circle();
      Square ii = new Square();
      object o = (1 == 1) ? aa : ii;   // CS0172
      // the following cast would resolve this error
      // (1 == 1) ? aa : (Circle)ii;
   }
}


编译器错误 CS0173

[image]

Visual C# 参考：错误和警告
编译器错误 CS0173
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法确定条件表达式的类型，因为“class1”和“class2”之间没有隐式转换

当希望不同类的对象在同一代码中使用时，类之间的转换非常有用。然而，在一起工作的两个类不能有相互转换和多余转换。

若要解决 CS0173，请确认 class1 与 class2 之间有且仅有一个隐式转换，不论向哪个方向进行转换或在哪个类中进行转换都是如此。有关更多信息，请参见隐式数值转换表（C# 参考）和转换运算符（C# 编程指南）。

示例

下面的示例生成 CS0173：

复制代码
// CS0173.cs
public class C {}
public class A {}

public class MyClass
{
   public static void F(bool b)
   {
      A a = new A();
      C c = new C();
      object o = b ? a : c;  // CS0173
   }

   public static void Main()
   {
      F(true);
   }
}

下面的代码在 Microsoft Visual Studio 2008 中而不是在 Visual Studio 2005 中生成 CS0173。

复制代码
//cs0173_2.cs
class M
{

static int Main ()
{
              int X = 1;
        object o = (X == 0) ? null : null; //CS0173
        return -1;
}
}


编译器错误 CS0174

[image]

Visual C# 参考：错误和警告
编译器错误 CS0174
发送反馈意见

更新：2007 年 11 月

错误消息

“base”引用需要基类

该错误仅在 .NET Framework 公共语言运行库编译 System.Object 类（它是唯一没有基类的类）的源代码时才发生。


编译器错误 CS0175

[image]

Visual C# 参考：错误和警告
编译器错误 CS0175
发送反馈意见

更新：2007 年 11 月

错误消息

在此上下文中使用关键字“base”无效

必须使用 base（C# 参考） 关键字指定基类的特定成员。有关更多信息，请参见构造函数（C# 编程指南）。

下面的示例生成 CS0175：

复制代码
// CS0175.cs
using System;
class BaseClass
{
    public int TestInt = 0;
}

class MyClass : BaseClass
{
    public static void Main()
    {
        MyClass aClass = new MyClass();
        aClass.BaseTest();
    }

    public void BaseTest()
    {
        Console.WriteLine(base); // CS0175
        // Try the following line instead:
        // Console.WriteLine(base.TestInt);
        base = 9;   // CS0175

        // Try the following line instead:
        // base.TestInt = 9;
    }
}


编译器错误 CS0176

[image]

Visual C# 参考：错误和警告
编译器错误 CS0176
发送反馈意见

更新：2007 年 11 月

错误消息

无法使用实例引用访问静态成员“member”；改用类型名来限定它

只有类名称可用于限定 static 变量；实例名称不能是限定符。有关更多信息，请参见静态类和静态类成员（C# 编程指南）。

下面的示例生成 CS0176：

复制代码
// CS0176.cs
public class MyClass2
{
   public static int ii;
}

public class a
{
   public static void Main()
   {
      MyClass2 myClass2 = new MyClass2 ();
      int i = myClass2.ii;   // CS0176
      // try the following line instead
      // int i = MyClass2.ii;
   }
}


编译器错误 CS0177

[image]

Visual C# 参考：错误和警告
编译器错误 CS0177
发送反馈意见

更新：2007 年 11 月

错误消息

控制离开当前方法之前必须对输出参数“parameter”赋值

在方法体中没有给用 out 关键字标记的参数赋值。有关更多信息，请参见传递参数（C# 编程指南）

下面的示例生成 CS0177：

复制代码
// CS0177.cs
public class MyClass
{
   public static void Foo(out int i)   // CS0177
   {
   // uncomment the following line to resolve this error
   //   i = 0;
   }

   public static void Main()
   {
       int x = -1;
       Foo(out x);
   }
}


编译器错误 CS0178

[image]

Visual C# 参考：错误和警告
编译器错误 CS0178
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无效的秩说明符：应为“,”或“]”

数组初始化的格式错误。例如，在指定数组维数时，可以指定以下内容：

  • 中括号中的数字。

  • 空的中括号。

  • 括在中括号中的逗号。

有关更多信息，请参见 数组（C# 编程指南） 和 C# 规范 (C# 语言规范) 中有关数组初始值设定项的部分。

示例

下面的示例生成 CS0178。

复制代码
// CS0178.cs
class MyClass
{
   public static void Main()
   {
      int a = new int[5][,][][5;   // CS0178
      int[,] b = new int[3,2];   // OK

      int[][] c = new int[10][];
      c[0] = new int[5][5];   // CS0178
      c[0] = new int[2];   // OK
      c[1] = new int[2]{1,2};   // OK
   }
}


编译器错误 CS0179

[image]

Visual C# 参考：错误和警告
编译器错误 CS0179
发送反馈意见

更新：2007 年 11 月

错误消息

“member”不能是外部的，也无法声明主体

当类成员被标记为 extern 时，意味着该成员的定义位于另一文件中。因此，标记为 extern 的类成员不能在类中定义。请移除 extern 关键字或删除定义。有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0179：

复制代码
// CS0179.cs
public class MyClass
{
   public extern int ExternMethod(int aa)   // CS0179
   {
      return 0;
   }
   // try the following line instead
   // public extern int ExternMethod(int aa);

   public static void Main()
   {
   }
}


编译器错误 CS0180

[image]

Visual C# 参考：错误和警告
编译器错误 CS0180
发送反馈意见

更新：2007 年 11 月

错误消息

“member”不能既是外部的又是抽象的

abstract 和 extern 关键字是互斥的。 extern 关键字表示该成员在文件外部定义，而 abstract 表示其实现在派生类中提供。有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0180：

复制代码
// CS0180.cs
namespace MyNamespace
{
   public class MyClass
   {
      public extern abstract int Foo(int a);   // CS0180

      public static void Main()
      {
      }
   }
}


编译器错误 CS0182

[image]

Visual C# 参考：错误和警告
编译器错误 CS0182
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性参数必须是属性参数类型的常数表达式、typeof 表达式或数组创建表达式

对于可将何种参数用于属性，存在特定的限制。请注意，除了该错误消息中指定的限制以外，也不允许将以下类型用作属性参数：

  • sbyte

  • ushort

  • uint

  • ulong

  • decimal

有关更多信息，请参见全局属性（C# 编程指南）。

示例

下面的示例生成 CS0182：

复制代码
// CS0182.cs
public class MyClass
{
    static string s = "Test";

    [System.Diagnostics.ConditionalAttribute(s)]   // CS0182
    // try the following line instead
    // [System.Diagnostics.ConditionalAttribute("Test")]
    void NonConstantArgumentToConditional()
    {
    }

    public static void Main()
    {
    }
}


编译器错误 CS0185

[image]

Visual C# 参考：错误和警告
编译器错误 CS0185
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”不是 lock 语句要求的引用类型

lock 语句只能计算引用类型。有关更多信息，请参见线程同步（C# 编程指南）和引用类型（C# 参考）。

示例

下面的示例生成 CS0185：

复制代码
// CS0185.cs
public class MainClass
{
    public static void Main ()
    {
        lock (1)   // CS0185
        // try the following lines instead
        // MainClass x = new MainClass();
        // lock(x)
        {
        }
    }
}


编译器错误 CS0186

[image]

Visual C# 参考：错误和警告
编译器错误 CS0186
发送反馈意见

更新：2007 年 11 月

错误消息

在此上下文中使用 NULL 无效

下面的示例生成 CS0186：

复制代码
// CS0186.cs
using System;
using System.Collections;

class MyClass
{
   static void Main()
   {
      // Each of the following lines generates CS0186:
      foreach (int i in null) {}   // CS0186
      foreach (int i in (IEnumerable) null) { };   // CS0186
   }
}


编译器错误 CS0188

[image]

Visual C# 参考：错误和警告
编译器错误 CS0188
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

在给“this”对象的所有字段赋值之前，无法使用该对象

构造函数必须给 struct 中的所有字段都赋值，然后才能调用 struct 中的方法。

如果在尝试初始化结构构造函数中的属性时看到此错误，则解决方案将更改此构造函数参数以指定支持字段而不指定该属性本身。由于自动实现的属性不具有支持字段，因此在任何情况下都不可能从构造函数进行初始化，从而应在结构中避免使用自动实现的属性。

有关更多信息，请参见使用结构（C# 编程指南）。

示例

下面的示例生成 CS0188：

复制代码
// CS0188.cs
// compile with: /t:library
namespace MyNamespace
{
    class MyClass
    {
        struct S
        {
            public int a;

            void Foo()
            {
            }

            S(int i)
            {
                // a = i;
                Foo();  // CS0188
            }
        }
        public static void Main()
        { }

    }
}

请参见

参考

结构（C# 编程指南）
自动实现的属性（C# 编程指南）


编译器错误 CS0191

[image]

Visual C# 参考：错误和警告
编译器错误 CS0191
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法对属性或索引器“name”赋值 -- 它是只读的

readonly 字段只能在构造函数或声明中获得赋值。有关更多信息，请参见构造函数（C# 编程指南）。

如果 readonly 字段为 static，但构造函数没有标记为 static，也会导致 CS0191。

示例

下面的示例生成 CS0191。

复制代码
// CS0191.cs
class MyClass
{
    public readonly int TestInt = 6;  // OK to assign to readonly field in declaration

    MyClass()
    {
        TestInt = 11; // OK to assign to readonly field in constructor
    }

    public void TestReadOnly()
    {
        TestInt = 19;                  // CS0191
    }

    public static void Main()
    {
    }
}


编译器错误 CS0192

[image]

Visual C# 参考：错误和警告
编译器错误 CS0192
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法向静态只读字段“name”的字段传递 ref 或 out 参数(静态构造函数中除外)

用 readonly 关键字标记的字段（变量）不能传递给 ref 或 out 参数，除非在构造函数内。有关更多信息，请参见字段（C# 编程指南）。

如果 readonly 字段为 static 而构造函数没有被标记为 static，则也会导致 CS0192。

示例

下面的示例生成 CS0192。

复制代码
// CS0192.cs
class MyClass
{
    public readonly int TestInt = 6;
    static void TestMethod(ref int testInt)
    {
        testInt = 0;
    }

    MyClass()
    {
        TestMethod(ref TestInt);   // OK
    }

    public void PassReadOnlyRef()
    {
        TestMethod(ref TestInt);   // CS0192
    }

    public static void Main()
    {
    }
}


编译器错误 CS0193

[image]

Visual C# 参考：错误和警告
编译器错误 CS0193
发送反馈意见

更新：2007 年 11 月

错误消息

* 或 -> 运算符只能应用于指针

* 或 -> 运算符用于了非指针类型。有关更多信息，请参见指针类型（C# 编程指南）。

下面的示例生成 CS0193：

复制代码
// CS0193.cs
using System;

public struct Age
{
   public int AgeYears;
   public int AgeMonths;
   public int AgeDays;
}

public class MyClass
{
   public static void SetAge(ref Age anAge, int years, int months, int days)
   {
      anAge->Months = 3;   // CS0193, anAge is not a pointer
      // try the following line instead
      // anAge.AgeMonths = 3;
   }

   public static void Main()
   {
      Age MyAge = new Age();
      Console.WriteLine(MyAge.AgeMonths);
      SetAge(ref MyAge, 22, 4, 15);
      Console.WriteLine(MyAge.AgeMonths);
   }
}


编译器错误 CS0196

[image]

Visual C# 参考：错误和警告
编译器错误 CS0196
发送反馈意见

更新：2007 年 11 月

错误消息

指针必须只根据一个值进行索引

指针不能有多个索引。有关更多信息，请参见指针类型（C# 编程指南）

下面的示例生成 CS0196：

复制代码
// CS0196.cs
public class MyClass
{
   public static void Main ()
   {
      int *i = null;
      int j = 0;
      j = i[1,2];   // CS0196
      // try the following line instead
      // j = i[1];
   }
}


编译器错误 CS0198

[image]

Visual C# 参考：错误和警告
编译器错误 CS0198
发送反馈意见

更新：2007 年 11 月

错误消息

无法对静态只读字段“name”的字段赋值(静态构造函数或变量初始值设定项中除外)

readonly 变量与要在其中初始化它的构造函数必须具有相同的 static 用法。有关更多信息，请参见静态构造函数（C# 编程指南）。

下面的示例生成 CS0198：

复制代码
// CS0198.cs
class MyClass
{
   public static readonly int TestInt = 6;

   MyClass()
   {
      TestInt = 11;   // CS0198, constructor is not static and readonly field is
   }

   public static void Main()
   {
   }
}


编译器错误 CS0199

[image]

Visual C# 参考：错误和警告
编译器错误 CS0199
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法向静态只读字段“name”的字段传递 ref 或 out 参数(静态构造函数中除外)

在构造函数中作为 ref 或 out 参数传递的 readonly 变量，必须具有与构造函数相同的 static 用法。有关更多信息，请参见传递参数（C# 编程指南）。

示例

下面的示例生成 CS0199：

复制代码
// CS0199.cs
class MyClass
{
    public static readonly int TestInt = 6;

    static void TestMethod(ref int testInt)
    {
        testInt = 0;
    }

    MyClass()
    {
        TestMethod(ref TestInt);   // CS0199, TestInt is static
    }

    public static void Main()
    {
    }
}


编译器错误 CS0200

[image]

Visual C# 参考：错误和警告
编译器错误 CS0200
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法对属性或索引器“property”赋值 — 它是只读的

试图对属性赋值，但该属性没有 set 访问器。通过添加 set 访问器解决该错误。有关更多信息，请参见如何：声明和使用读/写属性（C# 编程指南）。

示例

下面的示例生成 CS0200：

复制代码
// CS0200.cs
public class MainClass
{
    // private int _mi;
    int I
    {
        get
        {
            return 1;
        }

        // uncomment the set accessor and declaration for _mi
        /*
        set
        {
            _mi = value;
        }
        */
    }

    public static void Main ()
    {
        MainClass II = new MainClass();
        II.I = 9;   // CS0200
    }
}


编译器错误 CS0201

[image]

Visual C# 参考：错误和警告
编译器错误 CS0201
示例 发送反馈意见

更新：2007 年 11 月

错误消息

只有 assignment、call、increment、decrement 和 new 对象表达式可用作语句

编译器在遇到无意义的语句时会生成错误。

示例

下面的示例生成 CS0201。

复制代码
// CS0201.cs
public class MainClass
{
   public static void Main()
   {
      2 * 3;   // CS0201
   }
}

下面的示例生成 CS0201。

复制代码
// CS0201_b.cs
// compile with: /target:library
public class MyList<T>
{
   public void Add(T x)
   {
      int i = 0;
      if ( (object)x == null)
      {
         checked(i++);   // CS0201

         // OK
         checked {
            i++;
         }
      }
   }
}


编译器错误 CS0202

[image]

Visual C# 参考：错误和警告
编译器错误 CS0202
发送反馈意见

更新：2007 年 11 月

错误消息

foreach 要求“type.GetEnumerator()”的返回类型“type”必须有合适的公共 MoveNext 方法和公共 Current 属性

用于启用使用 foreach 语句的函数 GetEnumerator 无法返回指针或数组；它必须返回能够充当枚举数的类的实例。充当枚举数的适当要求包括一个公共 Current 属性和一个公共 MoveNext 方法。

说明：

在 C# 2.0 中，编译器将自动为您生成 Current 和 MoveNext。有关更多信息，请参见泛型接口（C# 编程指南）中的代码示例。

下面的示例生成 CS0202：

复制代码
// CS0202.cs

public class C1
{
   public int Current
   {
      get
      {
         return 0;
      }
   }

   public bool MoveNext ()
   {
      return false;
   }

   public static implicit operator C1 (int c1)
   {
      return 0;
   }
}

public class C2
{
   public int Current
   {
      get
      {
         return 0;
      }
   }

   public bool MoveNext ()
   {
      return false;
   }

   public C1[] GetEnumerator ()
   // try the following line instead
   // public C1 GetEnumerator ()
   {
      return null;
   }
}

public class MainClass
{
   public static void Main ()
   {
      C2 c2 = new C2();

      foreach (C1 x in c2)   // CS0202
      {
         System.Console.WriteLine(x.Current);
      }
   }
}


编译器错误 CS0204

[image]

Visual C# 参考：错误和警告
编译器错误 CS0204
发送反馈意见

更新：2007 年 11 月

错误消息

只允许 65534 个局部变量

方法中已经超过了 65534 个局部变量的限制。


编译器错误 CS0205

[image]

Visual C# 参考：错误和警告
编译器错误 CS0205
发送反馈意见

更新：2007 年 11 月

错误消息

无法调用抽象基成员：“method”

无法调用 abstract 方法，因为它没有方法体。有关更多信息，请参见抽象类、密封类及类成员（C# 编程指南）。

下面的示例生成 CS0205：

复制代码
// CS0205.cs
abstract public class MyClass
{
   abstract public void M();
}

public class MyClass2 : MyClass
{
   public override void M()
   {
      base.M();   // CS0205, delete this line
   }

   public static void Main()
   {
   }
}


编译器错误 CS0206

[image]

Visual C# 参考：错误和警告
编译器错误 CS0206
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性或索引器不得作为 out 或 ref 参数传递

属性不可作为 ref 或 out 参数传递。有关更多信息，请参见传递参数（C# 编程指南）。

示例

下面的示例生成 CS0206：

复制代码
// CS0206.cs
public class MyClass
{
    public static int P
    {
        get
        {
            return 0;
        }
        set
        {
        }
    }

    public static void MyMeth(ref int i)
    // public static void MyMeth(int i)
    {
    }

    public static void Main()
    {
        MyMeth(ref P);   // CS0206
        // try the following line instead
        // MyMeth(P);   // CS0206
    }
}


编译器错误 CS0208

[image]

Visual C# 参考：错误和警告
编译器错误 CS0208
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法获取托管类型（“type”）的地址或大小或声明指向它的指针

即使与 unsafe 关键字一起使用时，也不允许获取托管对象的地址或大小或者声明指向托管类型的指针。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

示例

下面的示例生成 CS0208：

复制代码
// CS0208.cs
// compile with: /unsafe

class S
{
    public int a = 98;
}

public class MyClass
{
    unsafe public static int Main()
    {
        S s = new S();  // S is managed
        S * s2 = &s;    // CS0208
        return 1;
    }
}


编译器错误 CS0209

[image]

Visual C# 参考：错误和警告
编译器错误 CS0209
发送反馈意见

更新：2007 年 11 月

错误消息

fixed 语句中声明的局部变量类型必须是指针类型

在 fixed 语句中声明的变量必须是指针。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例生成 CS0209：

复制代码
// CS0209.cs
// compile with: /unsafe

class Point
{
   public int x, y;
}

public class MyClass
{
   unsafe public static void Main()
   {
      Point pt = new Point();

      fixed (int i)    // CS0209
      {
      }
      // try the following lines instead
      /*
      fixed (int* p = &pt.x)
      {
      }
      fixed (int* q = &pt.y)
      {
      }
      */
   }
}


编译器错误 CS0210

[image]

Visual C# 参考：错误和警告
编译器错误 CS0210
发送反馈意见

更新：2007 年 11 月

错误消息

必须在 fixed 或者 using 语句声明中提供初始值设定项

必须在 fixed 语句中声明并初始化变量。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例生成 CS0210：

复制代码
// CS0210a.cs
// compile with: /unsafe

class Point
{
   public int x, y;
}

public class MyClass
{
   unsafe public static void Main()
   {
      Point pt = new Point();

      fixed (int i)    // CS0210
      {
      }
      // try the following lines instead
      /*
      fixed (int* p = &pt.x)
      {
      }
      fixed (int* q = &pt.y)
      {
      }
      */
   }
}

下面的示例也生成 CS0210，因为 using 语句没有初始值设定项。

复制代码
// CS0210b.cs

using System.IO;
class Test
{
   static void Main()
   {
      using (StreamWriter w) // CS0210
      // Try this line instead:
      // using (StreamWriter w = new StreamWriter("TestFile.txt"))
      {
         w.WriteLine("Hello there");
      }
   }
}


编译器错误 CS0211

[image]

Visual C# 参考：错误和警告
编译器错误 CS0211
发送反馈意见

更新：2007 年 11 月

错误消息

无法获取给定表达式的地址

可以获取字段、局部变量和指针间接寻址的地址，但不能获取诸如两个局部变量之和的地址。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例生成 CS0211：

复制代码
// CS0211.cs
// compile with: /unsafe

public class MyClass
{
   unsafe public void M()
   {
      int a = 0, b = 0;
      int *i = &(a + b);   // CS0211, the addition of two local variables
      // try the following line instead
      // int *i = &a;
   }

   public static void Main()
   {
   }
}


编译器错误 CS0212

[image]

Visual C# 参考：错误和警告
编译器错误 CS0212
发送反馈意见

更新：2007 年 11 月

错误消息

只能获取 fixed 语句初始值设定项内的未固定表达式的地址

有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例显示如何获取非固定表达式的地址。下面的示例生成 CS0212。

复制代码
// CS0212a.cs
// compile with: /unsafe /target:library

public class A {
   public int iField = 5;

   unsafe public void M() {
      A a = new A();
      int* ptr = &a.iField;   // CS0212
   }

   // OK
   unsafe public void M2() {
      A a = new A();
      fixed (int* ptr = &a.iField) {}
   }
}

下面的示例也生成 CS0212 并显示如何纠正该错误：

复制代码
// CS0212b.cs
// compile with: /unsafe /target:library
using System;

public class MyClass
{
   unsafe public void M()
   {
      // Null-terminated ASCII characters in an sbyte array
      sbyte[] sbArr1 = new sbyte[] { 0x41, 0x42, 0x43, 0x00 };
      sbyte* pAsciiUpper = &sbArr1[0];   // CS0212
      // To resolve this error, delete the previous line and
      // uncomment the following code:
      // fixed (sbyte* pAsciiUpper = sbArr1)
      // {
      //    String szAsciiUpper = new String(pAsciiUpper);
      // }
   }
}


编译器错误 CS0213

[image]

Visual C# 参考：错误和警告
编译器错误 CS0213
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能使用固定语句来获取已固定的表达式的地址

unsafe 方法中的局部变量或某个参数已固定（在堆栈上），因此无法在 fixed 表达式中获取这两个变量的任何一个的地址。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

示例

下面的示例生成 CS0213。

复制代码
// CS0213.cs
// compile with: /unsafe
public class MyClass
{
   unsafe public static void Main()
   {
      int i = 45;
      fixed (int *j = &i) { }  // CS0213
      // try the following line instead
      // int* j = &i;

      int[] a = new int[] {1,2,3};
      fixed (int *b = a)
      {
         fixed (int *c = b) { }  // CS0213
         // try the following line instead
         // int *c = b;
      }
   }
}


编译器错误 CS0214

[image]

Visual C# 参考：错误和警告
编译器错误 CS0214
发送反馈意见

更新：2007 年 11 月

错误消息

指针和固定大小缓冲区只能在不安全的上下文中使用

指针只能与 unsafe 关键字一起使用。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例生成 CS0214：

复制代码
// CS0214.cs
// compile with: /target:library /unsafe
public struct S
{
   public int a;
}

public class MyClass
{
   public static void Test()
   {
      S s = new S();
      S * s2 = &s;    // CS0214
      s2->a = 3;      // CS0214
      s.a = 0;
   }

   // OK
   unsafe public static void Test2()
   {
      S s = new S();
      S * s2 = &s;
      s2->a = 3;
      s.a = 0;
   }
}


编译器错误 CS0215

[image]

Visual C# 参考：错误和警告
编译器错误 CS0215
发送反馈意见

更新：2007 年 11 月

错误消息

运算符 True 或 False 的返回类型必须是 bool

用户定义的 true 和 false 运算符的返回类型必须是 bool。有关更多信息，请参见可重载运算符（C# 编程指南）。

下面的示例生成 CS0215：

复制代码
// CS0215.cs
class MyClass
{
   public static int operator true (MyClass MyInt)   // CS0215
   // try the following line instead
   // public static bool operator true (MyClass MyInt)
   {
      return true;
   }

   public static int operator false (MyClass MyInt)   // CS0215
   // try the following line instead
   // public static bool operator false (MyClass MyInt)
   {
      return true;
   }

   public static void Main()
   {
   }
}


编译器错误 CS0216

[image]

Visual C# 参考：错误和警告
编译器错误 CS0216
发送反馈意见

更新：2007 年 11 月

错误消息

运算符“operator”还要求定义匹配的运算符“missing_operator”

用户定义的 true 运算符需要用户定义的 false 运算符，反之亦然。有关更多信息，请参见运算符（C# 编程指南）。

下面的示例生成 CS0216：

复制代码
// CS0216.cs
class MyClass
{
   public static bool operator true (MyClass MyInt)   // CS0216
   {
      return true;
   }

   // to resolve, uncomment the following operator definition
   /*
   public static bool operator false (MyClass MyInt)
   {
      return true;
   }
   */

   public static void Main()
   {
   }
}


编译器错误 CS0217

[image]

Visual C# 参考：错误和警告
编译器错误 CS0217
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

为了可以像短路运算符一样应用，用户定义的逻辑运算符(“operator”)必须具有与它的两个参数的类型相同的返回类型。

如果将运算符定义为用户定义类型，然后尝试将运算符用作短路运算符，则用户定义的运算符必须具有相同类型的参数和返回值。有关短路运算符的更多信息，请参见 && 运算符和 || 运算符。

下面的示例生成 CS0217：

复制代码
// CS0217.cs
using System;

public class MyClass
{
   public static bool operator true (MyClass f)
   {
      return false;
   }

   public static bool operator false (MyClass f)
   {
      return false;
   }

   public static implicit operator int(MyClass x)
   {
      return 0;
   }

   public static int operator & (MyClass f1, MyClass f2)   // CS0217
   // try the following line instead
   // public static MyClass operator & (MyClass f1, MyClass f2)
   {
      return new MyClass();
   }

   public static void Main()
   {
      MyClass f = new MyClass();
      int i = f && f;
   }
}

请参见

参考

可重载运算符（C# 编程指南）


编译器错误 CS0218

[image]

Visual C# 参考：错误和警告
编译器错误 CS0218
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

类型(“type”)必须包含运算符 true 和运算符 false 的声明

如果将运算符定义为用户定义的类型，然后尝试将运算符用作短路运算符，则用户定义的运算符必须定义了 true 运算符和 false 运算符。有关短路运算符的更多信息，请参见 && 运算符和 || 运算符。

下面的示例生成 CS0218：

复制代码
// CS0218.cs
using System;
public class MyClass
{
   // uncomment these operator declarations to resolve this CS0218
   /*
   public static bool operator true (MyClass f)
   {
      return false;
   }

   public static bool operator false (MyClass f)
   {
      return false;
   }
   */

   public static implicit operator int(MyClass x)
   {
      return 0;
   }

   public static MyClass operator & (MyClass f1, MyClass f2)
   {
      return new MyClass();
   }

   public static void Main()
   {
      MyClass f = new MyClass();
      int i = f && f;   // CS0218, requires operators true and false
   }
}

请参见

参考

转换运算符（C# 编程指南）


编译器错误 CS0220

[image]

Visual C# 参考：错误和警告
编译器错误 CS0220
发送反馈意见

更新：2007 年 11 月

错误消息

在检查模式下，运算在编译时溢出

checked（这是默认设置）检测到某个运算并导致了数据丢失。为解决该错误，请更正赋值输入或使用 unchecked。有关更多信息，请参见Checked 和 Unchecked（C# 参考）。

下面的示例生成 CS0220：

复制代码
// CS0220.cs
using System;

class TestClass
{
   const int x = 1000000;
   const int y = 1000000;

   public int MethodCh()
   {
      int z = (x * y);   // CS0220
      return z;
   }

   public int MethodUnCh()
   {
      unchecked
      {
         int z = (x * y);
         return z;
      }
   }

   public static void Main()
   {
      TestClass myObject = new TestClass();
      Console.WriteLine("Checked  : {0}", myObject.MethodCh());
      Console.WriteLine("Unchecked: {0}", myObject.MethodUnCh());
   }
}


编译器错误 CS0221

[image]

Visual C# 参考：错误和警告
编译器错误 CS0221
发送反馈意见

更新：2007 年 11 月

错误消息

常数值“value”无法转换为“type”(使用“unchecked”语法重写)

checked（默认情况下是打开的）检测到某个赋值运算会导致数据丢失。为解决该错误，请更正赋值或使用 unchecked。有关更多信息，请参见Checked 和 Unchecked（C# 参考）。

下面的示例生成 CS0221：

复制代码
// CS0221.cs
public class MyClass
{
   public static void Main()
   {
      // unchecked
      // {
         int a = (int)0xFFFFFFFF;   // CS0221
         a++;
      // }
   }
}


编译器错误 CS0225

[image]

Visual C# 参考：错误和警告
编译器错误 CS0225
示例 发送反馈意见

更新：2007 年 11 月

错误消息

params 参数必须是一维数组

当使用 params 关键字时，必须指定该数据类型的一维数组。有关更多信息，请参见方法（C# 编程指南）。

示例

下面的示例生成 CS0225：

复制代码
// CS0225.cs
public class MyClass
{
    public static void TestParams(params int a)   // CS0225
    // try the following line instead
    // public static void TestParams(params int[] a)
    {
    }

    public static void Main()
    {
        TestParams(1);
    }
}


编译器错误 CS0226

[image]

Visual C# 参考：错误和警告
编译器错误 CS0226
示例 发送反馈意见

更新：2007 年 11 月

错误消息

__arglist 表达式只能出现在调用或 new 表达式内部。

不受支持的关键字 __arglist 只能出现在方法调用或 new 表达式内部。

示例

下面的代码将生成 CS0226：

复制代码
// cs0226.cs
using System;

public class C
    {
    public static int Main ()
        {
        __arglist(1,"This is a string"); // CS0226
        return 0;
        }
    }


编译器错误 CS0227

[image]

Visual C# 参考：错误和警告
编译器错误 CS0227
发送反馈意见

更新：2007 年 11 月

错误消息

不安全代码只会在使用 /unsafe 编译的情况下出现

如果源代码包含 unsafe 关键字，还必须使用 /unsafe 编译器选项。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例（在不使用 /unsafe 编译时）将生成 CS0227：

复制代码
// CS0227.cs
public class MyClass
{
   unsafe public static void Main()   // CS0227
   {
   }
}


编译器错误 CS0228

[image]

Visual C# 参考：错误和警告
编译器错误 CS0228
发送反馈意见

更新：2007 年 11 月

错误消息

“type”不包含“member”的定义，或者它是不可访问的

可能已经替换了 System.Hashtable、System.String 或 System.Array 的系统版本，并且该替换不包含 member。

否则，您可能需要修复或重新安装 Visual Studio。


编译器错误 CS0229

[image]

Visual C# 参考：错误和警告
编译器错误 CS0229
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“member1”和“member2”之间具有二义性

不同接口的成员具有相同的名称。如果要保留相同的名称，则必须限定这些名称。有关更多信息，请参见接口（C# 编程指南）。

说明：

在某些情况下，此二义性可以通过使用 using 别名向标识符提供明确的前缀来解决。

示例

下面的示例生成 CS0229：

复制代码
// CS0229.cs

interface IList
{
    int Count
    {
        get;
        set;
    }

    void Counter();
}

interface Icounter
{
    double Count
    {
        get;
        set;
    }
}

interface IListCounter : IList , Icounter {}

class MyClass
{
    void Test(IListCounter x)
    {
        x.Count = 1;  // CS0229
        // Try one of the following lines instead:
        // ((IList)x).Count = 1;
        // or
        // ((Icounter)x).Count = 1;
    }

    public static void Main() {}
}


编译器错误 CS0230

[image]

Visual C# 参考：错误和警告
编译器错误 CS0230
发送反馈意见

更新：2007 年 11 月

错误消息

在 foreach 语句中，类型和标识符都是必需的

foreach 语句格式不对。

下面的示例生成 CS0230：

复制代码
// CS0230.cs
using System;

class MyClass
{
   public static void Main()
   {
      int[] myarray = new int[3] {1,2,3};

      foreach (int in myarray)   // CS0230
      // try the following line instead
      // foreach (int x in myarray)
      {
         Console.WriteLine(x);
      }
   }
}


编译器错误 CS0231

[image]

Visual C# 参考：错误和警告
编译器错误 CS0231
发送反馈意见

更新：2007 年 11 月

错误消息

params 参数必须是形参表中的最后一个参数。

params 参数支持可变的参数数目，并且必须位于所有其他参数之后。有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0231：

复制代码
// CS0231.cs
class Test
{
   public void TestMethod(params int[] p, int i) {} // CS0231
   // To resolve the error, use the following line instead:
   // public void TestMethod(int i, params int[] p) {}

   static void Main()
   {
   }
}


编译器错误 CS0233

[image]

Visual C# 参考：错误和警告
编译器错误 CS0233
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“identifier”没有预定义大小，因此 sizeof 只能用于不安全的上下文中（请考虑使用 System.Runtime.InteropServices.Marshal.SizeOf）

sizeof 运算符只能用于为编译时常数的类型。如果您遇到此错误，请确保在编译时可以确定标识符的大小。如果不能确定，则用 SizeOf 代替 sizeof。

示例

下面的示例生成 CS0233：

复制代码
// CS0233.cs
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public struct S
{
    public int a;
}

public class MyClass
{
    public static void Main()
    {
        S myS = new S();
        Console.WriteLine(sizeof(S));   // CS0233
        // Try the following line instead:
        // Console.WriteLine(Marshal.SizeOf(myS));
   }
}


编译器错误 CS0234

[image]

Visual C# 参考：错误和警告
编译器错误 CS0234
发送反馈意见

更新：2007 年 11 月

错误消息

命名空间“namespace”中不存在类型或命名空间名称“name”（是否缺少程序集引用？）

应为某个类型。可能的原因包括：

  • 在编译中未引用包含某个类型定义的程序集。请使用 /reference（导入元数据）指定程序集。

  • 将某个变量名传递给了 typeof 运算符。

有关如何在开发环境中添加引用的信息，请参见“添加引用”对话框。

下面的示例生成 CS0234：

复制代码
// CS0234.cs
public class C
{
   public static void Main()
   {
      System.DateTime x = new System.DateTim();   // CS0234
      // try the following line instead
      // System.DateTime x = new System.DateTime();
   }
}


编译器错误 CS0236

[image]

Visual C# 参考：错误和警告
编译器错误 CS0236
发送反馈意见

更新：2007 年 11 月

错误消息

字段初始值设定项不能引用非静态字段、方法或属性“field”

实例字段不能用于初始化方法之外的其他实例字段。如果您正尝试在方法之外初始化一个变量，请考虑在类构造函数内部执行初始化。有关更多信息，请参见方法（C# 编程指南）。

下面的示例生成 CS0236：

复制代码
// CS0236.cs
public class MyClass
{
   public int i = 5;
   public int j = i;  // CS0236
   public int k;      // initialize in constructor

   MyClass()
   {
      k = i;
   }

   public static void Main()
   {
   }
}


编译器错误 CS0238

[image]

Visual C# 参考：错误和警告
编译器错误 CS0238
发送反馈意见

更新：2007 年 11 月

错误消息

因为“member”不是重写，所以无法将其密封

在还未用 override 进行标记的成员上使用了 sealed。有关更多信息，请参见继承（C# 编程指南）。

下面的示例生成 CS0238：

复制代码
// CS0238.cs
abstract class MyClass
{
   public abstract void f();
}

class MyClass2 : MyClass
{
   public static void Main()
   {
   }

   public sealed void f() // CS0238
   // Try the following definition instead:
   // public override sealed void f()
   {
   }
}


编译器错误 CS0239

[image]

Visual C# 参考：错误和警告
编译器错误 CS0239
发送反馈意见

更新：2007 年 11 月

错误消息

“member”: 无法重写继承成员“inherited member”，因为它已被密封

成员不能 overridesealed 继承成员。有关更多信息，请参见Checked 和 Unchecked（C# 参考）。

下面的示例生成 CS0239：

复制代码
// CS0239.cs
abstract class MyClass
{
   public abstract void f();
}

class MyClass2 : MyClass
{
   public static void Main()
   {
   }

   public override sealed void f()
   {
   }
}

class MyClass3 : MyClass2
{
   public override void f()   // CS0239
   // Try the following definition instead:
   // public new void f()
   {
   }
}


编译器错误 CS0241

[image]

Visual C# 参考：错误和警告
编译器错误 CS0241
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不允许有默认参数说明符

方法参数不能有默认值。如果要获得同样的效果，请使用方法重载。有关更多信息，请参见传递参数（C# 编程指南）。

示例

下面的示例生成 CS0241。此外，该示例还演示如何使用重载模拟具有默认参数的方法。

复制代码
// CS0241.cs
public class A
{
   public void Test(int i = 9) {}   // CS0241
}

public class B
{
   public void Test() { Test(9); }
   public void Test(int i)  {}
}

public class C
{
   public static void Main()
   {
      B x = new B();
      x.Test();
   }
}


编译器错误 CS0242

[image]

Visual C# 参考：错误和警告
编译器错误 CS0242
发送反馈意见

更新：2007 年 11 月

错误消息

相关操作在 void 指针上未定义

不允许增加 void 指针。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例生成 CS0242：

复制代码
// CS0242.cs
// compile with: /unsafe
class TestClass
{
   public unsafe void Test()
   {
      void * p = null;
      p++;   // CS0242, incrementing a void pointer not allowed
   }

   public static void Main()
   {
   }
}


编译器错误 CS0243

[image]

Visual C# 参考：错误和警告
编译器错误 CS0243
发送反馈意见

更新：2007 年 11 月

错误消息

Conditional 属性在“method”上无效，因为该属性是重写方法

在用 override 关键字标记的方法上不允许有 Conditional 属性。有关更多信息，请参见了解何时使用 Override 和 New 关键字（C# 编程指南）。

编译器从不绑定到重写方法；它只绑定到基方法，而且公共语言运行库在适当的时候调用重写。

下面的示例生成 CS0243：

复制代码
// CS0243.cs
// compile with: /target:library
public class MyClass
{
   public virtual void M() {}
}

public class MyClass2 : MyClass
{
   [System.Diagnostics.ConditionalAttribute("MySymbol")]   // CS0243
   // remove Conditional attribute or remove override keyword
   public override void M() {}
}


编译器错误 CS0244

[image]

Visual C# 参考：错误和警告
编译器错误 CS0244
发送反馈意见

更新：2007 年 11 月

错误消息

“is”和“as”在指针类型上都无效

在指针类型上使用 is 和 as 关键字无效。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例生成 CS0244：

复制代码
// CS0244.cs
// compile with: /unsafe

class UnsafeTest
{
   unsafe static void SquarePtrParam (int* p)
   {
      bool b = p is object;   // CS0244 p is pointer
   }

   unsafe public static void Main()
   {
      int i = 5;
      SquarePtrParam (&i);
   }
}


编译器错误 CS0245

[image]

Visual C# 参考：错误和警告
编译器错误 CS0245
发送反馈意见

更新：2007 年 11 月

错误消息

不能直接调用析构函数和 object.Finalize。请考虑调用 IDisposable.Dispose（如可用）。

有关更多信息，请参见垃圾回收编程原理和析构函数（C# 编程指南）。

下面的示例生成 CS0245：

复制代码
// CS0245.cs
using System;
using System.Collections;

class MyClass // : IDisposable
{
   /*
   public void Dispose()
   {
      // cleanup code goes here
   }
   */

   void m()
   {
      this.Finalize();   // CS0245
      // this.Dispose();
   }

   public static void Main()
   {
   }
}


编译器错误 CS0246

[image]

Visual C# 参考：错误和警告
编译器错误 CS0246
发送反馈意见

更新： 2008 年 7 月

错误消息

找不到类型或命名空间名称“type/namespace”（是否缺少 using 指令或程序集引用？）

未找到类型。可能忘了引用 (/reference) 包含该类型的程序集，或者可能未用 using 指令正确地限定其用法。

出现此错误的几个原因是：

  • 试图使用的类型名称或命名空间名称可能拼写错误（包括正确的大小写）。没有正确的名称，编译器就无法找到在代码中引用的类型或命名空间的定义。这种情况最常发生，原因是 C# 区分大小写，而在引用类型时没有使用正确的大小写。例如，Dataset ds; 会生成 CS0246；请注意，Dataset 中的 s 没有大写。

  • 如果错误的原因在于命名空间名称，则可能是没有引用 (/reference) 包含该命名空间的程序集。例如，代码可能包含 using Accessibility;。但是，如果项目不引用程序集 Accessibility.dll，则会遇到 CS0246。有关如何在开发环境中添加引用的信息，请参见“添加引用”对话框。

  • 如果错误的原因在于类型名称，则可能是没有使用正确的 using 指令，或没有完全限定该类型的名称。考虑下面的代码行：DataSet ds;。为了能够使用 DataSet 类型，您需要做以下两件事。第一，需要一个对包含 DataSet 类型定义的程序集的引用。第二，需要对 DataSet 所在的命名空间使用 using 指令。例如，由于 DataSet 位于 System.Data 命名空间中，所以需要在代码的开头有以下语句：using System.Data;。

  • 第二步不是必需的。但如果省略这一步，则要求在引用 DataSet 类型时对它进行完全限定。完全限定 DataSet 类型意味着每次在代码中引用它时都要使用命名空间和类型。所以，如果决定跳过第二步，您需要将上述声明代码更改为：System.Data.DataSet ds;。

  • 如果是非类型的错误，说明您可能在需要类型时使用了变量或其他对象。例如，在 is 语句中，如果您使用 Type 对象而不是实际的类型，将会遇到此错误。

  • 如果使用 using 别名指令但不完全限定类型名称，则生成此错误。using 别名指令不使用源代码文件中的其他 using 指令来解析类型。例如，下面的代码将生成 CS0246，因为虽然存在用于 System.Collections.Generic 的 using 指令，但没有完全限定类型 List<int>：

    复制代码
    using System.Collections.Generic;
// Generates CS0246:
using myAliasName = List<int>;
// Does not generate an error:
using myAliasName2 = System.Collections.Generic.List<int>

下面的示例生成 CS0246：

复制代码
// CS0246.cs
// using System.Diagnostics;

public class MyClass
{
   [Conditional("A")]   // CS0246, uncomment using directive to resolve
   public void Test()
   {
   }

   public static void Main()
   {
   }
}

下面是一个示例，其中使用了 Type 类型的对象，但应该使用实际的类型（上面的第 4 种情况）：

复制代码
// CS0246b.cs
using System;

class C
{
    public bool supports(object o, Type t)
    {
         if (o is t)  // CS0246 – t is not a type
         {
            return true;
         }
         return false;
    }

    public static void Main()
    {
    }
}

修订记录

日期 | 修订记录 | 原因
2008 年 7 月 | 增加了可能导致此错误的原因。 | 信息补充。


编译器错误 CS0247

[image]

Visual C# 参考：错误和警告
编译器错误 CS0247
发送反馈意见

更新：2007 年 11 月

错误消息

无法对 stackalloc 采用负值大小

将负数传递给了 stackalloc 语句。

下面的示例生成 CS0247：

复制代码
// CS0247.cs
// compile with: /unsafe
public class MyClass
{
   unsafe public static void Main()
   {
      int *p = stackalloc int [-30];   // CS0247
   }
}


编译器错误 CS0248

[image]

Visual C# 参考：错误和警告
编译器错误 CS0248
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法创建大小为负值的数组

用负数指定了数组大小。有关更多信息，请参见数组（C# 编程指南）。

示例

下面的示例生成 CS0248：

复制代码
// CS0248.cs
class MyClass
{
    public static void Main()
    {
        int[] myArray = new int[-3] {1,2,3};   // CS0248, pass a nonnegative number
    }
}


编译器错误 CS0249

[image]

Visual C# 参考：错误和警告
编译器错误 CS0249
发送反馈意见

更新：2007 年 11 月

错误消息

不要重写 object.Finalize。相反，请提供析构函数。

使用析构函数语法指定销毁对象时执行的指令。

有关更多信息，请参见 C# 和 C++ 中的析构函数语法。

下面的示例生成 CS0249：

复制代码
// CS0249.cs
class MyClass
{
   protected override void Finalize()   // CS0249
   // try the following line instead
   // ~MyClass()
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0250

[image]

Visual C# 参考：错误和警告
编译器错误 CS0250
发送反馈意见

更新：2007 年 11 月

错误消息

不要直接调用基类 Finalize 方法。它将从析构函数中自动调用。

程序不能试图强制清理基类资源。

有关更多信息，请参见 Finalize 方法和析构函数。

下面的示例生成 CS0250：

复制代码
// CS0250.cs
class B
{
}

class C : B
{
   ~C()
   {
      base.Finalize();   // CS0250
   }

   public static void Main()
   {
   }
}


编译器错误 CS0254

[image]

Visual C# 参考：错误和警告
编译器错误 CS0254
发送反馈意见

更新：2007 年 11 月

错误消息

固定语句赋值的右边不能是强制转换表达式

fixed 表达式的右边不可使用转换。有关更多信息，请参见不安全代码和指针（C# 编程指南）。

下面的示例生成 CS0254：

复制代码
// CS0254.cs
// compile with: /unsafe
class Point
{
   public uint x, y;
}

class FixedTest
{
   unsafe static void SquarePtrParam (int* p)
   {
      *p *= *p;
   }

   unsafe public static void Main()
   {
      Point pt = new Point();
      pt.x = 5;
      pt.y = 6;

      fixed (int* p = (int*)&pt.x)   // CS0254
      // try the following line instead
      // fixed (uint* p = &pt.x)
      {
         SquarePtrParam ((int*)p);
      }
   }
}


编译器错误 CS0255

[image]

Visual C# 参考：错误和警告
编译器错误 CS0255
发送反馈意见

更新：2007 年 11 月

错误消息

stackalloc 不能用在 catch 或 finally 块中

stackalloc 关键字不可用在 catch 或 finally 块中。有关更多信息，请参见异常和异常处理（C# 编程指南）。

下面的示例生成 CS0255：

复制代码
// CS0255.cs
// compile with: /unsafe
using System;

public class TestTryFinally
{
   public static unsafe void Test()
   {
      int i = 123;
      string s = "Some string";
      object o = s;

      try
      {
         // Conversion is not valid; o contains a string not an int
         i = (int) o;
      }

      finally
      {
         Console.Write("i = {0}", i);
         int* fib = stackalloc int[100];   // CS0255
      }
   }

   public static void Main()
   {
   }
}


编译器错误 CS0260

[image]

Visual C# 参考：错误和警告
编译器错误 CS0260
发送反馈意见

更新：2007 年 11 月

错误消息

类型“type”的声明上缺少分部修饰符；存在此类型的其他分部声明

此错误指示找到了多个具有相同名称的类声明，并且这些声明中至少有一个但不是全部被声明为分部。如果您打算在若干部分中定义一个类，则所有那些部分必须用关键字 partial 声明。如果您创建的新类碰巧与同一命名空间中的其他位置声明的分部类具有相同的名称，则也会发生此错误。

下面的示例生成 CS0260：

复制代码
// CS0260.cs
class C  // CS0260
{
}

partial class C
{
}


编译器错误 CS0261

[image]

Visual C# 参考：错误和警告
编译器错误 CS0261
发送反馈意见

更新：2007 年 11 月

错误消息

“type”的分部声明必须全部是类、构造或接口

如果分部类型在不同的位置声明为不同的构造类型，则会发生此错误。有关更多信息，请参见分部类和方法（C# 编程指南）。

下面的示例生成 CS0261：

复制代码
// CS0261.cs
partial class A  // CS0261 – A declared as a class here, but as a struct below
{
}

partial struct A
{
}


编译器错误 CS0262

[image]

Visual C# 参考：错误和警告
编译器错误 CS0262
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”的分部声明包含冲突的可访问性修饰符

如果分部类型有不一致的修饰符，如 public、private、protected、internal 或 abstract，则将发生此错误。这些修饰符在该类型的所有分部声明中必须一致。有关更多信息，请参见分部类和方法（C# 编程指南）。

示例

下面的示例生成 CS0262：

复制代码
// CS0262.cs
class A
{
    public partial class C  // CS0262
    {
    }
    private partial class C
    {
    }
}


编译器错误 CS0263

[image]

Visual C# 参考：错误和警告
编译器错误 CS0263
发送反馈意见

更新：2007 年 11 月

错误消息

“type”的分部声明不得指定不同的基类

在分部声明中定义类型时，请在每个分部声明中指定相同的基类型。有关更多信息，请参见分部类和方法（C# 编程指南）。

下面的示例生成 CS0263：

复制代码
// CS0263.cs
// compile with: /target:library
class B1
{
}

class B2
{
}
partial class C : B1  // CS0263 – is the base class B1 or B2?
{
}

partial class C : B2
{
}


编译器错误 CS0264

[image]

Visual C# 参考：错误和警告
编译器错误 CS0264
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”的分部声明必须具有相同顺序的相同类型参数名

如果您要在分部声明中定义一个泛型类型，但类型参数的名称或顺序在所有分部声明中不一致，就会发生此错误。若要消除此错误，请检查每个分部声明的类型参数，确保使用的参数名称和顺序相同。有关更多信息，请参见分部类和方法（C# 编程指南）和泛型类型参数（C# 编程指南）。

示例

下面的示例生成 CS0264。

复制代码
// CS0264.cs

partial class MyClass<T>  // CS0264
{
}

partial class MyClass <MyType>
{
}


编译器错误 CS0265

[image]

Visual C# 参考：错误和警告
编译器错误 CS0265
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”的分部声明具有与类型参数“type parameter”不一致的约束

当您将某个泛型类定义为分部类，以便其分部定义出现在多个位置，如果该泛型类的约束在两个或更多的位置中不一致或不同，则会发生此错误。如果您在多个位置指定约束，这些约束必须相同。最简单的解决方法是在一个位置指定约束，并在所有其他位置省略它们。有关更多信息，请参见分部类和方法（C# 编程指南）和类型参数的约束（C# 编程指南）。

下面的代码生成错误 CS0265。

示例

在此代码中，分部类定义全部位于单个文件中，不过它们也可以分布在多个文件中。

复制代码
// CS0265.cs
public class GenericsErrors
{
    interface IFace1 { }
    interface IFace2 { }
    partial class PartialBadBounds<T> where T : IFace1 { } // CS0265
    partial class PartialBadBounds<T> where T : IFace2 { }
}


编译器错误 CS0266

[image]

Visual C# 参考：错误和警告
编译器错误 CS0266
发送反馈意见

更新：2007 年 11 月

错误消息

无法将类型“type1”隐式转换为“type2”。存在显式转换（是否缺少强制转换？）

如果您的代码试图转换两种不能隐式转换的类型（例如将基类分配给一个缺少显式强制转换的派生类），将发生此错误。有关更多信息，请参见转换运算符（C# 编程指南）。

下面的示例生成 CS0266：

复制代码
// CS0266.cs
class MyClass
{
    public static void Main()
    {
        object obj = "MyString";
        // Cannot implicitly convert 'object' to 'MyClass'
        MyClass myClass = obj;  // CS0266
        // Try this line instead
        // MyClass c = ( MyClass )obj;
    }
}


编译器错误 CS0267

[image]

Visual C# 参考：错误和警告
编译器错误 CS0267
发送反馈意见

更新：2007 年 11 月

错误消息

分部修饰符只能仅挨在“class”、“struct”或“interface”之前出现

在类、结构或接口的声明中，partial 修饰符的位置不正确。若要修复此错误，请重排修饰符的顺序。有关更多信息，请参见分部类和方法（C# 编程指南）。

下面的示例生成 CS0267：

复制代码
// CS0267.cs
public partial class MyClass
{
   public MyClass()
   {
   }
}

partial public class MyClass  // CS0267
// Try this line instead:
// public partial class MyClass
{
   public void Foo()
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0268

[image]

Visual C# 参考：错误和警告
编译器错误 CS0268
发送反馈意见

更新：2007 年 11 月

错误消息

导入的类型“type”无效。它包含一个循环基类依赖项。

如果从另一种语言导入的类型有循环基类依赖项，则发生此错误。此类型不能在 C# 程序中使用。要解决此错误，请在所有引用的程序集或模块中检查从其他语言导入的类型。


编译器错误 CS0269

[image]

Visual C# 参考：错误和警告
编译器错误 CS0269
示例 发送反馈意见

更新：2007 年 11 月

错误消息

使用了未赋值的 out 参数“parameter”

编译器无法验证在使用 out 参数之前是否已给它赋值，它的值在赋值时可能未定义。在访问值之前，请确保初始化 out 参数。如果您需要使用传入的变量的值，请改用 ref 参数。有关更多信息，请参见传递参数（C# 编程指南）。

示例

下面的示例生成 CS0269：

复制代码
// CS0269.cs
class C
{
    public static void F(out int i)
    // Try this instead:
    // public static void F(ref int i)
    {
        int k = i;  // CS0269
        i = 1;
    }

    public static void Main()
    {
        int myInt;
        F(out myInt);
    }
}

如果变量的初始化发生在 try 块中，则也会出现此错误，因为编译器无法验证 try 块是否可以成功执行：

复制代码
// CS0269b.cs
class C
{
    public static void F(out int i)
    {
        try
        {
            // Assignment occurs, but compiler can't verify it
            i = 1;
        }
        catch
        {
        }

        int k = i;  // CS0269
        i = 1;
    }

    public static void Main()
    {
        int myInt;
        F(out myInt);
    }
}


编译器错误 CS0270

[image]

Visual C# 参考：错误和警告
编译器错误 CS0270
发送反馈意见

更新：2007 年 11 月

错误消息

不能在变量声明中指定数组大小（请尝试使用“new”表达式初始化）

将大小指定为数组声明的一部分时会发生此错误。若要解决此错误，请使用 new 运算符表达式。

下面的示例生成 CS0270：

复制代码
// CS0270.cs
// compile with: /t:module

public class Test
{
   int[10] a;   // CS0270
   // To resolve, use the following line instaead:
   // int[] a = new int[10];
}


编译器错误 CS0271

[image]

Visual C# 参考：错误和警告
编译器错误 CS0271
发送反馈意见

更新：2007 年 11 月

错误消息

由于 get 访问器不可访问，因此不能在此上下文中使用属性或索引器“property/indexer”

当您试图访问一个不可访问的 get 访问器时，会发生此错误。若要解决此错误，请提高访问器的可访问性，或者更改调用位置。有关更多信息，请参见访问器可访问性和属性（C# 编程指南）。

下面的示例生成 CS0271：

复制代码
// CS0271.cs
public class MyClass
{
   public int Property
   {
      private get { return 0; }
      set { }
   }

   public int Property2
   {
      get { return 0; }
      set { }
   }
}

public class Test
{
   public static void Main(string[] args)
   {
      MyClass c = new MyClass();
      int a = c.Property;   // CS0271
      int b = c.Property2;   // OK
   }
}


编译器错误 CS0272

[image]

Visual C# 参考：错误和警告
编译器错误 CS0272
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于无法访问 set 访问器，属性或索引器“property/indexer”不能在此上下文中使用

当 set 访问器不能由程序代码访问时，将发生此错误。若要解决此错误，请提高访问器的可访问性，或者更改调用位置。有关更多信息，请参见非对称访问器可访问性（C# 编程指南）。

示例

下面的示例生成 CS0272：

复制代码
// CS0272.cs
public class MyClass
{
    public int Property
    {
        get { return 0; }
        private set { }
    }
}

public class Test
{
    static void Main()
    {
        MyClass c = new MyClass();
        c.Property = 10;      // CS0272
        // To resolve, remove the previous line
        // or use an appropriate modifier on the set accessor.
    }
}


编译器错误 CS0273

[image]

Visual C# 参考：错误和警告
编译器错误 CS0273
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“property_accessor”访问器的可访问性修饰符必须比属性或索引器“property”具有更强的限制

set/get 访问器的可访问性修饰符必须比属性或索引器“property/indexer”具有更强的限制

如果您用一个访问修饰符声明一个属性或索引器，而该访问修饰符的限制性低于该属性或索引器的其中一个访问器的访问修饰符，则会发生此错误。若要解决此错误，请对属性或 set 访问器使用适当的访问修饰符。有关更多信息，请参见访问器可访问性。

示例

此示例包含一个具有内部 set 方法的内部属性。下面的示例生成 CS0273。

复制代码
// CS0273.cs
// compile with: /target:library
public class MyClass
{
   internal int Property
   {
      get { return 0; }
      internal set {}   // CS0273
      // try the following line instead
      // private set {}
   }
}


编译器错误 CS0274

[image]

Visual C# 参考：错误和警告
编译器错误 CS0274
发送反馈意见

更新：2007 年 11 月

错误消息

不能为属性或索引器“property/indexer”的两个访问器同时指定可访问性修饰符

当您声明一个在两个访问器上都有访问修饰符的属性或索引器时，将出现此错误。若要解决此错误，请仅在两个访问器中的一个上使用访问修饰符。有关更多信息，请参见访问器可访问性。

下面的示例生成 CS0274：

复制代码
// CS0274.cs
public class MyClass
{
    public int Property   // CS0274
    {
        public get { return 0; }
        protected set { }
    }
}


编译器错误 CS0275

[image]

Visual C# 参考：错误和警告
编译器错误 CS0275
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“accessor”：可访问性修饰符不能在接口中的访问器上使用

当您在接口中的属性或索引器的任何一个访问器上使用访问修饰符时，将出现此错误。若要解决此错误，请移除访问修饰符。

示例

下面的示例生成 CS0275：

复制代码
// CS0275.cs
public interface MyInterface
{
    int Property
    {
        get;
        internal set;   // CS0275
    }
}


编译器错误 CS0276

[image]

Visual C# 参考：错误和警告
编译器错误 CS0276
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“property/indexer”：仅当属性或索引器同时具有 get 访问器和 set 访问器时，才能对访问器使用可访问修饰符

当您仅使用一个访问器声明属性或索引器，并对该访问器使用访问修饰符时，会出现此错误。若要解决此错误，请移除访问修饰符或添加另一个访问器。

示例

下面的示例生成 CS0276：

复制代码
// CS0276.cs
public class MyClass
{
    public int Property
    {
        protected set { }   // CS0276
    }
    public int Property2
    {
        internal get { }   // CS0276
    }
}


编译器错误 CS0277

[image]

Visual C# 参考：错误和警告
编译器错误 CS0277
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“class”不实现接口成员“accessor”。“class accessor”不是公共的

当您尝试实现某个接口的属性，但在类中实现的属性访问器不是公共的时，则会发生此错误。实现接口成员的方法需要具有公共访问性。若要解决此错误，请移除属性访问器的访问修饰符。

示例

下面的示例生成 CS0277：

复制代码
// CS0277.cs
public interface MyInterface
{
    int Property
    {
        get;
        set;
    }
}

public class MyClass : MyInterface   // CS0277
{
    public int Property
    {
        get { return 0; }
        // Try this instead:
        //set { }
        protected set { }
    }
}


编译器错误 CS0281

[image]

Visual C# 参考：错误和警告
编译器错误 CS0281
示例 发送反馈意见

更新：2007 年 11 月

错误消息

已授予对“AssemblyName1”的友元访问，但输出程序集的名称为“AssemblyName2”。请尝试添加对“AssemblyName1”的引用，或者更改输出程序集的名称以使其匹配。

友元访问是一种新的公共语言运行库 (CLR) 功能，它可以使一个程序集看到另一个程序集的非公共类型。当授予友元访问的程序集为被授予程序集指定错误的名称时，将会出现此错误。有关更多信息，请参见友元程序集（C# 编程指南）。

示例

下面的代码示例序列将生成 CS0281。

按照下面的方式生成用于创建具有强名称的程序集的文件：

  • sn -d CS0281.snk

  • sn -k CS0281.snk

  • sn -i CS0281.snk CS0281.snk

  • sn -pc CS0281.snk key.publickey

  • sn -tp key.publickey

复制代码
// CS0281.cs
// compile with: /target:library /keyfile:CS0281.snk
public class A {}
复制代码
// CS0281_b.cs
// compile with: /target:library /keyfile:CS0281.snk /reference:CS0281.dll
[assembly:System.Runtime.CompilerServices.InternalsVisibleTo("CS0281 , PublicKey=00240000048000009400000006020000002400005253413100040000010001004b2d4d56af7c50be2fcbbf97cb880b9e73ad84467a587191fef63aadc118a96cecf9d508cd679c907b6e20f71684300bdc2c0a851019af0c96b29bf8f1339753276041aefd67db46139e6348b3a12f29537b4dc6c2c19829df2c9ed6803f3c63c3b84cfa2728849386aea575c543a5f70fa85793d2946f15f7fe1ccb0c5e8fe0")]
class B : A {}

下面的示例生成 CS0281。

注意，此示例创建的输出文件与第一个示例创建的输出文件同名。若要解决此问题，请不要更改组件的程序集属性并添加类 C。

复制代码
// CS0281_c.cs
// compile with: /target:library /out:CS0281.dll /keyfile:CS0281.snk /reference:CS0281_b.dll
// CS0281 expected
[assembly:System.Reflection.AssemblyVersion("3")]
[assembly:System.Reflection.AssemblyCulture("en-us")]
class C : B {}
public class A {}


编译器错误 CS0283

[image]

Visual C# 参考：错误和警告
编译器错误 CS0283
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能将类型“type”声明为 const

常量声明中指定的类型必须是 byte、char、short、int、long、float、double、decimal、bool、string、枚举类型、或被赋予 null 值的引用类型。每个常数表达式产生的值必须属于目标类型，或者属于可隐式转换为目标类型的类型。

示例

下面的示例生成 CS0283。

复制代码
// CS0283.cs
struct MyTest
{
}
class MyClass
{
    // To resolve the error but retain the "const-ness",
    // change const to readonly.
    const MyTest test = new MyTest();   // CS0283

    public static int Main() {
        return 1;
    }
}


编译器错误 CS0304

[image]

Visual C# 参考：错误和警告
编译器错误 CS0304
发送反馈意见

更新：2007 年 11 月

错误消息

变量类型“type”没有 new() 约束，因此无法创建该类型的实例

当您使用 new 创建某个类型变量的实例时，如果该类型变量未指定 new() constraint，则会发生此错误。除非使用 new() 约束确保存在默认的构造函数，否则不能使用 new 直接调用未知类型的构造函数。如果无法使用 new 约束，可考虑使用 typeof 表达式访问所需的构造函数。

下面的示例生成 CS0304：

复制代码
// CS0304.cs
// compile with: /target:library
class C<T>
{
   T t = new T();   // CS0304
}

类方法中同样不允许这种形式的 new 语句：

复制代码
// CS0304_2.cs
// compile with: /target:library
class C<T>
{
   public void f()
   {
      T t = new T();  // CS0304
   }
}


编译器错误 CS0305

[image]

Visual C# 参考：错误和警告
编译器错误 CS0305
示例 发送反馈意见

更新：2007 年 11 月

错误消息

使用泛型类型“generic type”需要“number”类型参数

当未找到预期的类型参数数目时会发生此错误。若要解决 CS0305，请使用所需数目的类型参数。

示例

下面的示例生成 CS0305。

复制代码
// CS0305.cs
public class MyList<T> {}
public class MyClass<T> {}

class MyClass
{
   public static void Main()
   {
      MyList<MyClass, MyClass> list1 = new MyList<MyClass>();   // CS0305
      MyList<MyClass> list2 = new MyList<MyClass>();   // OK
   }
}


编译器错误 CS0306

[image]

Visual C# 参考：错误和警告
编译器错误 CS0306
发送反馈意见

更新：2007 年 11 月

错误消息

类型“type”不能用作类型参数

不允许将该类型用作类型参数。这可能是因为该类型是一个指针类型。

下面的示例生成 CS0306：

复制代码
// CS0306.cs
class C<T>
{
}

class M
{
    // CS0306 – int* not allowed as a type parameter
     C<int*> f;
}


编译器错误 CS0307

[image]

Visual C# 参考：错误和警告
编译器错误 CS0307
发送反馈意见

更新：2007 年 11 月

错误消息

“construct”“identifier”不是泛型方法。如果原打算使用表达式列表，请用括号将 < 表达式括起来。

指定的构造不是一个类型或方法，而类型和方法是唯一能接受泛型参数的构造。请移除尖括号中的类型参数。如果需要泛型，请将泛型构造声明为泛型类型或方法。

下面的示例生成 CS0307：

复制代码
// CS0307.cs
class C
{
   public int P { get { return 1; } }
   public static void Main()
   {
      C c = new C();
      int p = c.P<int>();  // CS0307 – C.P is a property
      // Try this instead
      // int p = c.P;
   }
}


编译器错误 CS0308

[image]

Visual C# 参考：错误和警告
编译器错误 CS0308
发送反馈意见

更新：2007 年 11 月

错误消息

非泛型类型或方法“identifier”不能与类型参数一起使用。

方法或类型不是泛型的，但它却与类型参数一起使用。若要避免此错误，请移除尖括号和类型参数，或者将方法或类型重新声明为泛型类型或方法。

下面的示例生成 CS0308：

复制代码
// CS0308a.cs
class MyClass
{
   public void F() {}
   public static void Main()
   {
      F<int>();  // CS0308 – F is not generic.
      // Try this instead:
      // F();
   }
}

下面的示例生成 CS0308。若要解决此错误，请使用指令“using System.Collections.Generic”。

复制代码
// CS0308b.cs
// compile with: /t:library
using System.Collections;
// To resolve, uncomment the following line:
// using System.Collections.Generic;
public class MyStack<T>
{
    // Store the elements of the stack:
    private T[] items = new T[100];
    private int stack_counter = 0;

    // Define the iterator block:
    public IEnumerator<T> GetEnumerator()   // CS0308
    {
        for (int i = stack_counter - 1 ; i >= 0; i--)
        yield return items[i];
    }
}


编译器错误 CS0310

[image]

Visual C# 参考：错误和警告
编译器错误 CS0310
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型“typename”必须是具有公共的无参数构造函数的非抽象类型，才能用作泛型类型或方法“generic”中的参数“parameter”

泛型类型或方法在它的 where 子句中定义了一个新约束，要求任何类型均必须具有公共的无参数构造函数才能用作该泛型类型或方法的类型参数。若要避免此错误，请确保类型具有正确的构造函数，或者修改泛型类型或方法的约束子句。

示例

下面的示例生成 CS0310：

复制代码
// CS0310.cs
using System;

class G<T> where T : new()
{
    T t;

    public G()
    {
        t = new T();
        Console.WriteLine(t);
    }
}

class B
{
    private B() { }
    // Try this instead:
    // public B() { }
}

class CMain
{
    public static void Main()
    {
        G<B> g = new G<B>();   // CS0310
        Console.WriteLine(g.ToString());
    }
}


编译器错误 CS0311

[image]

Visual C# 参考：错误和警告
编译器错误 CS0311
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

不能将类型“type1”用作泛型类型或方法“<name>”中的类型参数“T”。没有从“type1”到“type2”的隐式引用转换。

在将某一约束应用于泛型类型参数时，必须存在从具体参数到该约束类型的隐式标识或引用转换。

更正此错误

  1. 更改要在创建类时使用的参数。

  2. 如果您拥有类，则可移除约束或执行某一操作以启用隐式引用或标识转换。例如，可以使第二个类型从第一个类型继承。

示例

复制代码
// cs0311.cs
class B{}
class C{}
class Test<T> where T : C
{ }

class Program
{
    static void Main()
    {
        Test<B> test = new Test<B>(); //CS0311
    }
}

如果在尝试使用值类型参数时发生此错误，您会发现隐式数值转换（例如，从 short 到 int 的转换）不满足泛型类型参数的约束。

请参见

参考

类型参数的约束（C# 编程指南）


编译器错误 CS0312

[image]

Visual C# 参考：错误和警告
编译器错误 CS0312
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

不能将类型“type1”用作泛型类型或方法“name”中的类型参数“name”。可为 null 的类型“type1”未满足“type2”的约束。

可为 null 的类型与其对应的不可为 null 的类型截然不同；不能在这两者之间执行隐式引用转换或标识转换。可为 null 的装箱转换未满足泛型类型约束。在下面的示例中，第一个类型参数为 Nullable<int>，而第二个类型参数为 System.Int32。

更正此错误

  1. 移除约束。

  2. 在下面的示例中，使第二个类型参数为 int? 或 object。

示例

下面的代码生成 CS0312：

复制代码
// cs0312.cs
class Program
{
    static void MTyVar<T, U>() where T : U { }

    static int Main()
    {
        MTyVar<int?, int>(); // CS0312
        return 1;
    }
}

虽然可为 null 的类型与不可为 null 的类型截然不同，但允许在可为 null 的值和不可为 null 的值之间执行各种转换。

请参见

参考

可空类型（C# 编程指南）


编译器错误 CS0313

[image]

Visual C# 参考：错误和警告
编译器错误 CS0313
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

不能将类型“type1”用作泛型类型或方法“type2”中的类型参数“parameter name”。可为 null 的类型“type1”未满足“type2”的约束。可为 null 的类型无法满足任何接口约束。

可为 null 的类型与其不可为 null 的对应项不是等效的。在下面的示例中，ImplStruct 满足 BaseInterface 约束，但 ImplStruct? 不满足该约束，因为 Nullable<ImplStruct> 未实现 BaseInterface。

更正此错误

  • 使用下面的代码作为示例时，一种解决方案是将常规 ImplStruct 指定为对 TestMethod 的调用中的第一个类型参数。然后，修改 TestMethod，在其返回语句中创建 Implstruct 的可为 null 的版本：

    复制代码
    return new Nullable<T>(t);

示例

下面的代码生成 CS0313：

复制代码
// cs0313.cs
public interface BaseInterface { }
public struct ImplStruct : BaseInterface { }

public class TestClass
{
    public T? TestMethod<T, U>(T t) where T : struct, U
    {
        return t;
    }
}

public class NullableTest
{
    public static void Run()
    {

        TestClass tc = new TestClass();
        tc.TestMethod<ImplStruct?, BaseInterface>(new ImplStruct?()); // CS0313
    }
    public static void Main()
    { }
}

请参见

参考

可空类型（C# 编程指南）


编译器错误 CS0314

[image]

Visual C# 参考：错误和警告
编译器错误 CS0314
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

不能将类型“type1”用作泛型类型或方法“name”中的类型参数“name”。没有从“type1”到“type2”的装箱转换或类型参数转换。

在泛型类型使用具有约束的类型参数时，新的类也必须满足这些相同的约束。

更正此错误

  • 在下面的示例中，将 where T : ClassConstraint 添加到类 B。

示例

下面的代码生成 CS0314：

复制代码
// cs0314.cs
// Compile with: /target:library
public class ClassConstraint { }

public class A<T> where T : ClassConstraint
{ }

public class B<T> : A<T> //CS0314
{ }

// Try using this instead.
public class C<T> : A<T> where T : ClassConstraint
{ }

请参见

参考

类型参数的约束（C# 编程指南）


编译器错误 CS0315

[image]

Visual C# 参考：错误和警告
编译器错误 CS0315
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

不能将类型“valueType”用作泛型类型或方法“TypeorMethod<T>”中的类型参数“T”。没有从“valueType”到“referenceType”的装箱转换。

如果将泛型类型约束为特定类，并且尝试使用不能隐式装箱到该类的值类型构造该类的实例，则会发生此错误。

更正此错误

  • 一种解决方案是将该结构重新定义为类。

示例

下面的示例生成 CS0315：

复制代码
// cs0315.cs
public class ClassConstraint { }
public struct ViolateClassConstraint { }

public class Gen<T> where T : ClassConstraint
{
}
public class Test
{
    public static int Main()
    {
        Gen<ViolateClassConstraint> g = new Gen<ViolateClassConstraint>(); //CS0315
        return 1;
    }
}

请参见

参考

类型参数的约束（C# 编程指南）
装箱和取消装箱（C# 编程指南）


编译器错误 CS0316

[image]

Visual C# 参考：错误和警告
编译器错误 CS0316
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

参数名称“name”与自动生成的参数名称冲突。

保留字不能用作参数名称。在下面的示例中，value 是默认属性或索引器访问器的上下文中的保留字。

更正此错误

  • 更改该参数的名称。

示例

下面的代码生成 CS0316：

复制代码
// cs0316.cs
// Compile with: /target:library
public class Test
{
    public int this[int value] // CS0316
    {
        get { return 1; }
        set { }
    }
}

请参见

参考

索引器（C# 编程指南）
C# 关键字


编译器错误 CS0400

[image]

Visual C# 参考：错误和警告
编译器错误 CS0400
发送反馈意见

更新：2007 年 11 月

错误消息

未能在全局命名空间中找到类型或命名空间名称“identifier”（是否缺少程序集引用？）

在全局命名空间中未找到由全局范围运算符 (::) 所定范围的标识符。您可能缺少包含该标识符的程序集引用，或者该标识符可能在全局命名空间以外的类或命名空间中声明。如果全局范围标识符没有被声明或拼写错误，可能也会发生此错误。

若要避免此错误，请找到标识符的声明，验证其拼写是否正确，并且如果该声明是在单独的程序集中，请确保您具有适当的程序集引用。如果标识符在另一个类型或命名空间中声明，请在 :: 之后使用完全限定名。下面的示例生成 CS0400：

复制代码
// CS0400.cs
class C
{
    public static void Main()
    {
        // CS0400 - D could not be found
        // in the global namespace.
        global::D d = new global::D();
   }
}


编译器错误 CS0401

[image]

Visual C# 参考：错误和警告
编译器错误 CS0401
示例 发送反馈意见

更新：2007 年 11 月

错误消息

new() 约束必须是指定的最后一个约束

使用多个约束时，请将所有其他约束列在 new() 约束之前。

示例

下面的示例生成 CS0401。

复制代码
// CS0401.cs
// compile with: /target:library
using System;
 class C<T> where T : new(), IDisposable {}  // CS0401

class D<T> where T : IDisposable
{
   static void F<U>() where U : new(), IDisposable{}   // CS0401
}


编译器错误 CS0403

[image]

Visual C# 参考：错误和警告
编译器错误 CS0403
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将 null 转换为类型参数“name”，因为它可能是不可以为 null 的值类型。请考虑改用 default('T')。

给指定的未知类型的赋值不能为空，因为它可能是一个不允许空赋值的值类型。如果您的泛型类不用来接受值类型，请使用类类型约束。如果它可以接受值类型（例如内置类型），您也许能够用表达式 default(T) 替换空赋值，如下面的示例所示。

示例

下面的示例生成 CS0403。

复制代码
// CS0403.cs
// compile with: /target:library
class C<T>
{
   public void f()
   {
      T t = null;  // CS0403
      T t2 = default(T);   // OK
    }
}

class D<T> where T : class
{
   public void f()
   {
      T t = null;  // OK
    }
}


编译器错误 CS0404

[image]

Visual C# 参考：错误和警告
编译器错误 CS0404
发送反馈意见

更新：2007 年 11 月

错误消息

不应为“<”：属性不能为泛型

属性中不允许使用泛型类型参数。移除类型参数和尖括号。

下面的示例生成 CS0404：

复制代码
// CS0404.cs
[MyAttrib<int>]  // CS0404
class C
{
   public static void Main()
   {

   }
}


编译器错误 CS0405

[image]

Visual C# 参考：错误和警告
编译器错误 CS0405
发送反馈意见

更新：2007 年 11 月

错误消息

类型参数“type parameter”的重复约束“constraint”

泛型声明的两个约束完全相同。若要消除此错误，请移除重复的约束。

下面的示例生成 CS0405：

复制代码
// CS0405.cs
interface I
{
}

class C<T> where T : I, I  // CS0405.cs
{
}


编译器错误 CS0406

[image]

Visual C# 参考：错误和警告
编译器错误 CS0406
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类类型约束“constraint”必须在其他任何约束之前

当泛型类型或方法具有类类型约束时，该约束必须首先列出。若要避免此错误，请将类类型约束移到约束列表的开头。

示例

下面的示例生成 CS0406。

复制代码
// CS0406.cs
// compile with: /target:library
interface I {}
class C {}
class D<T> where T : I, C {}   // CS0406
class D2<T> where T : C, I {}   // OK


编译器错误 CS0407

[image]

Visual C# 参考：错误和警告
编译器错误 CS0407
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“return-type method”具有错误的返回类型

此方法与委托类型不兼容。参数类型匹配，但返回类型不是该委托的正确返回类型。若要避免此错误，请使用其他方法、更改方法的返回类型或更改委托的返回类型。

示例

下面的示例生成 CS0407：

复制代码
// CS0407.cs
public delegate int MyDelegate();

class C
{
    MyDelegate d;

    public C()
    {
        d = new MyDelegate(F);  // OK: F returns int
        d = new MyDelegate(G);  // CS0407 – G doesn't return int
    }

    public int F()
    {
        return 1;
    }

    public void G()
    {
    }

    public static void Main()
    {
        C c1 = new C();
    }
}


编译器错误 CS0409

[image]

Visual C# 参考：错误和警告
编译器错误 CS0409
发送反馈意见

更新：2007 年 11 月

错误消息

已经为类型参数“type parameter”指定了约束子句。类型参数的所有约束必须在单个 where 子句中指定。

已找到单个类型参数的多个约束子句（where 子句）。移除无关的 where 子句，或对 where 子句进行更正，以便每个子句中有一个唯一的类型参数。

复制代码
// CS0409.cs
interface I
{
}

class C<T1, T2> where T1 : I where T1 : I  // CS0409 – T1 used twice
{
}


编译器错误 CS0410

[image]

Visual C# 参考：错误和警告
编译器错误 CS0410
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“method”的重载没有正确的参数类型和返回类型

如果您试图使用参数类型不正确的函数来实例化委托，就会发生此错误。委托的参数类型必须与您打算分配给委托的函数匹配。

示例

下面的示例生成 CS0410：

复制代码
// CS0410.cs
// compile with: /langversion:ISO-1

class Test
{
    delegate void D(double d );
    static void F(int i) { }

    static void Main()
    {
        D d = new D(F);  // CS0410
    }
}


编译器错误 CS0411

[image]

Visual C# 参考：错误和警告
编译器错误 CS0411
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法从用法推断出方法“method”的类型参数。请尝试显式指定类型参数。

如果您调用某个泛型方法但没有显式提供类型参数，导致编译器无法推断出哪些是要使用的类型参数，则会发生此错误。若要避免此错误，请将要使用的类型参数添加到尖括号中。

示例

下面的示例生成 CS0411：

复制代码
// CS0411.cs
class C
{
    void G<T>()
    {
    }

    public static void Main()
    {
        G();  // CS0411
        // Try this instead:
        // G<int>();
    }
}

其他可能的错误情况包括当参数为 null 时（没有类型信息）：

复制代码
// CS0411b.cs
class C
{
    public void F<T>(T t) where T : C
    {
    }

    public static void Main()
    {
        C c = new C();
        c.F(null);  // CS0411
    }
}


编译器错误 CS0412

[image]

Visual C# 参考：错误和警告
编译器错误 CS0412
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“generic”：参数或局部变量不能与方法类型参数具有相同的名称

在泛型方法的类型参数与该方法的一个局部变量或该方法的一个参数之间存在名称冲突。若要避免此错误，请重命名任何有冲突的参数或局部变量。

示例

下面的示例生成 CS0412：

复制代码
// CS0412.cs
using System;

class C
{
    // Parameter name is the same as method type parameter name
    public void G<T>(int T)  // CS0412
    {
    }
    public void F<T>()
    {
        // Method local variable name is the same as method type
        // parameter name
        double T = 0.0;  // CS0412
        Console.WriteLine(T);
    }

    public static void Main()
    {
    }
}


编译器错误 CS0413

[image]

Visual C# 参考：错误和警告
编译器错误 CS0413
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于类型参数“type parameter”既没有类类型约束，也没有“class”约束，因此不能与“as”运算符一起使用

如果某个泛型类型使用了 as 运算符，但该泛型类型不具有类类型约束，则发生此错误。as 运算符只允许用于引用类型，因此必须约束类型参数以保证它不是值类型。要避免此错误，请使用类类型约束或引用类型约束。

这是因为 as 运算符能够返回 null（它不可能是值类型的值），并且类型参数必须被视为值类型，除非它是类类型约束或引用类型约束。

示例

下面的示例生成 CS0413。

复制代码
// CS0413.cs
// compile with: /target:library
class A {}
class B : A {}

class CMain
{
   A a = null;
   public void G<T>()
   {
      a = new A();
      System.Console.WriteLine (a as T);  // CS0413
   }

   // OK
   public void H<T>() where T : A
   {
      a = new A();
      System.Console.WriteLine (a as T);
   }
}


编译器错误 CS0415

[image]

Visual C# 参考：错误和警告
编译器错误 CS0415
发送反馈意见

更新：2007 年 11 月

错误消息

“IndexerName”属性仅在不是显式接口成员声明的索引器上有效

如果您在是接口的显式实现的索引器上使用 IndexerName 属性，则将出现此错误。如果有可能，通过从索引器的声明中移除接口名称，可以避免此错误。有关更多信息，请参见 IndexerNameAttribute 类。

下面的示例生成 CS0415：

复制代码
// CS0415.cs
using System;
using System.Runtime.CompilerServices;

public interface IA
{
    int this[int index]
    {
        get;
        set;
    }
}

public class A : IA
{
    [IndexerName("Item")]  // CS0415
    int IA.this[int index]
    // Try this line instead:
    // public int this[int index]
    {
        get { return 0; }
        set { }
    }

    static void Main()
    {
    }
}


编译器错误 CS0416

[image]

Visual C# 参考：错误和警告
编译器错误 CS0416
发送反馈意见

更新：2007 年 11 月

错误消息

“type parameter”：属性参数不能使用类型参数

类型参数被用作属性参数，这是不允许的。请使用非泛型类型。

下面的示例生成 CS0416：

复制代码
// CS0416.cs
public class MyAttribute : System.Attribute
{
   public MyAttribute(System.Type t)
   {
   }
}

class G<T>
{

   [MyAttribute(typeof(G<T>))]  // CS0416
   public void F()
   {
   }

}


编译器错误 CS0417

[image]

Visual C# 参考：错误和警告
编译器错误 CS0417
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“identifier”：创建变量类型的实例时无法提供参数

如果对类型参数上的 new 运算符的调用带有参数，则会发生此错误。可以使用未知参数类型上的 new 运算符调用的唯一构造函数是不带参数的构造函数。如果需要调用另一个构造函数，请考虑使用类类型约束或接口约束。

示例

下面的示例生成 CS0417：

复制代码
// CS0417
class C<T> where T : new()
{
    T type = new T(1);   // CS0417
}


编译器错误 CS0418

[image]

Visual C# 参考：错误和警告
编译器错误 CS0418
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“class name”：抽象类不能是密封的或静态的

除非从抽象类继承，否则抽象类不能用于创建对象，所以它成为密封的没有意义。抽象类成为静态也没有意义；设计抽象类是为了支持将抽象类用作基类的对象层次结构。

示例

下面的示例生成 CS0418：

复制代码
// CS0418.cs
public abstract sealed class C  // CS0418
{
}

sealed static class S  // CS0418
{
}

public class MyClass
{
    public static void Main()
    {
    }
}


编译器错误 CS0423

[image]

Visual C# 参考：错误和警告
编译器错误 CS0423
发送反馈意见

更新：2007 年 11 月

错误消息

由于“class”具有 ComImport 属性，“method”必须为外部的或抽象的

指定 ComImport 属性意味着类的实现将从 COM 模块导入。不能定义额外的方法。

下面的示例生成 CS0423：

复制代码
// CS0423.cs

using System.Runtime.InteropServices;

[
  ComImport,
  Guid("7ab770c7-0e23-4d7a-8aa2-19bfad479829")
]
class ImageProperties
{
  public static void Main()  // CS0423
  {
    ImageProperties i = new ImageProperties();
  }
}


编译器错误 CS0424

[image]

Visual C# 参考：错误和警告
编译器错误 CS0424
发送反馈意见

更新：2007 年 11 月

错误消息

“class”: 具有 ComImport 属性的类不能指定基类

指定 ComImportAttribute 属性意味着类的实现将从 COM 模块导入。不允许将从基类继承的其他方法或字段添加到在 COM 模块中定义的实现中。

下面的示例生成 CS0424：

复制代码
// CS0424.cs
// compile with: /target:library
using System.Runtime.InteropServices;
public class A {}

[ ComImport, Guid("7ab770c7-0e23-4d7a-8aa2-19bfad479829") ]
class B : A {}   // CS0424 error


编译器错误 CS0425

[image]

Visual C# 参考：错误和警告
编译器错误 CS0425
示例 发送反馈意见

更新：2007 年 11 月

错误消息

方法“method”的类型参数“type parameter”的约束必须和接口方法“method”的类型参数“type parameter”的约束匹配。请考虑改用显式接口实现。

如果一个虚泛型方法在派生类中被重写，并且该方法在派生类中的约束与它在基类中的约束不匹配，则会发生此错误。若要避免此错误，请确保 where 子句在两个声明中相同，或者显式实现接口。

示例

下面的示例生成 CS0425：

复制代码
// CS0425.cs

class C1
{
}

class C2
{
}

interface IBase
{
    void F<ItemType>(ItemType item) where ItemType : C1;
}

class Derived : IBase
{
    public void F<ItemType>(ItemType item) where ItemType : C2  // CS0425
    {
    }
}

class CMain
{
    public static void Main()
    {
    }
}

约束不一定要在字面上匹配，只要约束的设置有相同的含义即可。例如，以下形式是可行的：

复制代码
// CS0425b.cs

interface J<Z>
{
}

interface I<S>
{
    void F<T>(S s, T t) where T: J<S>, J<int>;
}

class C : I<int>
{
    public void F<X>(int s, X x) where X : J<int>
    {
    }

    public static void Main()
    {
    }
}


编译器错误 CS0426

[image]

Visual C# 参考：错误和警告
编译器错误 CS0426
发送反馈意见

更新：2007 年 11 月

错误消息

类型“type”中不存在类型名称“identifier”

未能在指定的类型中找到类型名称。检查所使用的名称的拼写，并验证引用的类型是否具有预期的成员。

下面的示例生成 CS0426：

复制代码
// CS0426.cs

class C
{
}

class D
{
   public static void Main()
   {
      C.A a;  // CS0426
   }
}


编译器错误 CS0428

[image]

Visual C# 参考：错误和警告
编译器错误 CS0428
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将方法组“Identifier”转换为非委托类型“type”。 您是要调用方法吗？

将方法组转换为非委托类型时，或试图调用不带括号的方法时，会发生此错误。

示例

下面的示例生成 CS0428：

复制代码
// CS0428.cs

delegate object Del1();
delegate int Del2();

public class C
{
    public static C Method() { return null; }
    public int Foo() { return 1; }

    public static void Main()
    {
        C c = Method; // CS0428, C is not a delegate type.
        int i = (new C()).Foo; // CS0428, int is not a delegate type.

        Del1 d1 = Method; // OK, assign to the delegate type.
        Del2 d2 = (new C()).Foo; // OK, assign to the delegate type.
        // or you might mean to invoke method
        // C c = Method();
        // int i = (new C()).Foo();
    }
}


编译器错误 CS0430

[image]

Visual C# 参考：错误和警告
编译器错误 CS0430
示例 发送反馈意见

更新：2007 年 11 月

错误消息

/reference 选项中未指定外部别名“alias”

当遇到外部别名，而别名未在命令行上被指定为引用时，会发生此错误。若要解决 CS0430，请使用 /reference 进行编译。

示例

复制代码
// CS0430_a.cs
// compile with: /target:library
public class MyClass {}

使用 /reference:MyType=cs0430_a.dll 编译来引用上面的示例中创建的 DLL 可解决此错误。下面的示例生成 CS0430。

复制代码
// CS0430_b.cs
extern alias MyType;   // CS0430
public class Test
{
   public static void Main() {}
}


编译器错误 CS0431

[image]

Visual C# 参考：错误和警告
编译器错误 CS0431
发送反馈意见

更新：2007 年 11 月

错误消息

别名“identifier”引用的是一个类型，因此不能与“::”一起使用。请改用“.”。

您将“::”与一个引用类型的别名一起使用了。若要解决此错误，请使用“.”运算符。

下面的示例生成 CS0431：

复制代码
// CS0431.cs
using A = Outer;

public class Outer
{
   public class Inner
   {
      public static void Meth() {}
   }
}

public class MyClass
{
   public static void Main()
   {
      A::Inner.Meth();   // CS0431
      A.Inner.Meth();   // OK
   }
}


编译器错误 CS0432

[image]

Visual C# 参考：错误和警告
编译器错误 CS0432
发送反馈意见

更新：2007 年 11 月

错误消息

未找到别名“identifier”

在不是别名的标识符的右侧使用“::”时将发生此错误。若要解决此错误，请改用“.”。

下面的示例生成 CS0432：

复制代码
// CS0432.cs
namespace A {
    public class B {
        public static void Meth() { }
    }
}

public class Test
{
    public static void Main()
    {
        A::B.Meth();   // CS0432
       // To resolve, use the following line instead:
       // A.B.Meth();
    }
}


编译器错误 CS0433

[image]

Visual C# 参考：错误和警告
编译器错误 CS0433
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型 TypeName1 同时存在于 TypeName2 和 TypeName3 中。

在应用程序中引用的两个不同的程序集包含相同的命名空间和类型，这会产生混乱。

若要解决此错误，请使用 /reference（导入元数据）（C# 编译器选项） 编译器选项的别名功能，或者不引用您的程序集。

示例

此代码用歧义类型的第一个副本创建 DLL。

复制代码
// CS0433_1.cs
// compile with: /target:library
namespace TypeBindConflicts
{
   public class AggPubImpAggPubImp {}
}

此代码用歧义类型的第二个副本创建 DLL。

复制代码
// CS0433_2.cs
// compile with: /target:library
namespace TypeBindConflicts
{
   public class AggPubImpAggPubImp {}
}

下面的示例生成 CS0433。

复制代码
// CS0433_3.cs
// compile with: /reference:cs0433_1.dll /reference:cs0433_2.dll
using TypeBindConflicts;
public class Test
{
   public static void Main()
   {
      AggPubImpAggPubImp n6 = new AggPubImpAggPubImp();   // CS0433
   }
}

下面的示例演示如何使用 /reference 编译器选项的别名功能来解决此 CS0433 错误。

复制代码
// CS0433_4.cs
// compile with: /reference:cs0433_1.dll /reference:TypeBindConflicts=cs0433_2.dll
using TypeBindConflicts;
public class Test
{
   public static void Main()
   {
      AggPubImpAggPubImp n6 = new AggPubImpAggPubImp();
   }
}


编译器错误 CS0434

[image]

Visual C# 参考：错误和警告
编译器错误 CS0434
示例 发送反馈意见

更新：2007 年 11 月

错误消息

NamespaceName2 中的命名空间 NamespaceName1 与 NamespaceName3 中的类型 TypeName1 冲突。

当导入的类型和导入的命名空间具有相同的完全限定名时，将出现此错误。当引用该名称时，编译器无法区分这两者。

下面的代码生成错误 CS0434。

示例

此代码用相同的完全限定名创建类型的第一个副本。

复制代码
// CS0434_1.cs
// compile with: /t:library
namespace TypeBindConflicts
{
    namespace NsImpAggPubImp
    {
        public class X { }
    }
}

此代码用相同的完全限定名创建类型的第二个副本。

复制代码
// CS0434_2.cs
// compile with: /t:library
namespace TypeBindConflicts {
    // Conflicts with another import (import2.cs).
    public class NsImpAggPubImp { }
    // Try this instead:
    // public class UniqueClassName { }
}

此代码用相同的完全限定名引用类型。

复制代码
// CS0434.cs
// compile with: /r:cs0434_1.dll /r:cs0434_2.dll
using TypeBindConflicts;
public class Test
{
    public TypeBindConflicts.NsImpAggPubImp.X n2 = null; // CS0434
}


编译器错误 CS0438

[image]

Visual C# 参考：错误和警告
编译器错误 CS0438
发送反馈意见

更新：2007 年 11 月

错误消息

“module_1”中的类型“type”与“module_2”中的命名空间“namespace”冲突。

当某个源文件中的类型与其他源文件中的命名空间冲突时会发生此错误。当它们中的一个或两者都来自于某个添加的模块时，通常会发生此情况。若要解决此错误，请重命名导致此冲突的类型或命名空间。

下面的示例生成 CS0438：

首先编译此文件：

复制代码
// CS0438_1.cs
// compile with: /target:module
public class Util
{
   public class A { }
}

然后，编译此文件：

复制代码
// CS0438_2.cs
// compile with: /target:module
namespace Util
{
   public class A { }
}

然后，再编译此文件：

复制代码
// CS0438_3.cs
// compile with: /addmodule:CS0438_1.netmodule /addmodule:CS0438_2.netmodule
using System;
public class Test
{
   public static void Main() {
      Console.WriteLine(typeof(Util.A));   // CS0438
   }
}


编译器错误 CS0439

[image]

Visual C# 参考：错误和警告
编译器错误 CS0439
示例 发送反馈意见

更新：2007 年 11 月

错误消息

外部别名声明必须位于命名空间中定义的所有其他元素之前

当 extern 声明位于同一命名空间中某些其他元素（如 using 声明）之后时会发生此错误。 extern 声明必须位于所有其他命名空间元素之前。

示例

下面的示例生成 CS0439：

复制代码
// CS0439.cs
using System;

extern alias MyType;   // CS0439
// To resolve the error, make the extern alias the first line in the file.

public class Test
{
    public static void Main()
    {
    }
}


编译器错误 CS0441

[image]

Visual C# 参考：错误和警告
编译器错误 CS0441
发送反馈意见

更新：2007 年 11 月

错误消息

“class”：类不能同时是静态的和密封的

当您声明一个同时是静态和密封的类时，将出现此错误。静态类的密封是继承性的，所以不需要 sealed 修饰符。若要解决此问题，请仅使用一个修饰符。

下面的示例生成 CS0441：

复制代码
// CS0441.cs
sealed static class MyClass { }   // CS0441


编译器错误 CS0442

[image]

Visual C# 参考：错误和警告
编译器错误 CS0442
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“Property”：抽象属性不能有专用访问器

使用访问修饰符“private”修改抽象访问器时会发生此错误。若要解决此错误，请使用其他访问修饰符，或使属性成为非抽象的。

示例

下面的示例生成 CS0442：

复制代码
// CS0442.cs
public abstract class MyClass
{
    public abstract int AbstractProperty
    {
        get;
        private set;   // CS0442
        // Try this instead:
        // set;
    }
}


编译器错误 CS0443

[image]

Visual C# 参考：错误和警告
编译器错误 CS0443
示例 发送反馈意见

更新：2007 年 11 月

错误消息

语法错误，需要值

如果您在引用数组时未指定数组索引的值，将发生此错误。

示例

下面的代码生成 CS0443。

复制代码
// CS0443.cs
using System;
class MyClass
{
    public static void Main()
    {
        int[,] x = new int[1,5];
        if (x[] == 5) {} // CS0443
        // if (x[0, 0] == 5) {}
    }
}


编译器错误 CS0445

[image]

Visual C# 参考：错误和警告
编译器错误 CS0445
发送反馈意见

更新：2007 年 11 月

错误消息

无法修改取消装箱转换的结果

取消装箱转换的结果是一个临时变量，编译器禁止您修改这样的变量，因为当临时变量消失时，任何修改也随之消失。若要修复此错误，请使用一个值类型来存储中间表达式。然后您可以给值类型赋值。

下面的代码生成 CS0455：

复制代码
// CS0445.CS
public struct Point
{
    public int x;
    public static void SetX(object obj, int x)
    {
        ((Point)obj).x = x; // CS0445
    }
}
class UnboxingTest{public static void Main(){}}


编译器错误 CS0446

[image]

Visual C# 参考：错误和警告
编译器错误 CS0446
示例 发送反馈意见

更新：2007 年 11 月

错误消息

Foreach 不能在“方法或委托”上运行。您是要调用“方法或委托”吗？

指定一个没有括号的方法或指定一个在 foreach 语句部分（通常放入集合类）没有括号的匿名方法会导致此错误。请注意，如果方法返回一个集合类，则将该方法调用放在那个位置是有效的，尽管这不常见。

示例

下面的代码将生成 CS0446。

复制代码
// CS0446.cs
using System;
class Tester
{
    static void Main()
    {
        int[] intArray = new int[5];
        foreach (int i in M) { } // CS0446
    }
    static void M() { }
}


编译器错误 CS0447

[image]

Visual C# 参考：错误和警告
编译器错误 CS0447
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性不能用于类型变量，只能用于类型参数

将属性应用于出现在调用语句中的类型变量时会发生此错误。将属性应用于类或方法声明语句中的类型参数是可以接受的，如下所示：

复制代码
class C<[some attribute] T> {…}

下面这行代码将生成此错误。假定在上一行代码中定义的类 C 有一个名为 MyStaticMethod 的静态方法。

复制代码
C<[some attribute] T>.MyStaticMethod();

示例

下面的代码生成错误 CS0447。

复制代码
// CS0447.cs
using System;
namespace Test41
{
    public interface I<A>
    {
        void Meth<B>();
    }
    public class B : I<int>
    {
        void I<[Test] int>.Meth<X>() { }  // CS0447
    }
}


编译器错误 CS0448

[image]

Visual C# 参考：错误和警告
编译器错误 CS0448
示例 发送反馈意见

更新：2007 年 11 月

错误消息

++ 或 -- 运算符的返回类型必须是包含类型或者是从包含类型派生的类型。

当您重写 ++ 或 -- 运算符时，它们必须返回与包含类型相同的类型，或者返回从包含类型派生的类型。

示例

下面的示例生成 CS0448。

复制代码
// CS0448.cs
class C5
{
   public static int operator ++(C5 c) { return null; }   // CS0448
   public static C5 operator --(C5 c) { return null; }   // OK
   public static void Main() {}
}

下面的示例生成 CS0448。

复制代码
// CS0448_b.cs
public struct S
{
   public static S? operator ++(S s) { return new S(); }   // CS0448
   public static S? operator --(S s) { return new S(); }   // CS0448
}

public struct T
{
// OK
   public static T operator --(T t) { return new T(); }
   public static T operator ++(T t) { return new T(); }

   public static T? operator --(T? t) { return new T(); }
   public static T? operator ++(T? t) { return new T(); }

   public static void Main() {}
}


编译器错误 CS0449

[image]

Visual C# 参考：错误和警告
编译器错误 CS0449
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“class”或“struct”约束必须在其他任何约束之前

对泛型类型或方法的类型参数的约束必须以某一指定的顺序发生：class 或 struct 必须是第一个（如果存在），然后是任何接口约束，最后是任何构造函数约束。此错误是由于 class 或 struct 约束没有先出现所致。要解决此错误，请重新排列约束子句。

示例

下面的示例生成 CS0449。

复制代码
// CS0449.cs
// compile with: /target:library
interface I {}
public class C4
{
   public void F1<T>() where T : class, struct, I {}   // CS0449
   public void F2<T>() where T : I, struct {}   // CS0449
   public void F3<T>() where T : I, class {}   // CS0449

   // OK
   public void F4<T>() where T : class {}
   public void F5<T>() where T : struct {}
   public void F6<T>() where T : I {}
}


编译器错误 CS0450

[image]

Visual C# 参考：错误和警告
编译器错误 CS0450
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“Type Parameter Name”: 不能既指定约束类又指定“class”或“struct”约束

如果类型参数受 struct 类型约束的约束，则它也受特定 class 类型的约束在逻辑上是矛盾的，因为 struct 和 class 属于互相排斥的类别。如果类型参数受特定 class 类型约束的约束，则按照定义它应该受 class 类型约束的约束，因此指定 class 类型约束是多余的。

示例

复制代码
// CS0450.cs
// compile with: /t:library
public class GenericsErrors
{
    public class B { }
    public class G3<T> where T : struct, B { } // CS0450
// To resolve, use the following line instead:
// public class G3<T> where T : B { }
}


编译器错误 CS0451

[image]

Visual C# 参考：错误和警告
编译器错误 CS0451
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“new()”约束不能与“struct”约束一起使用

当指定泛型类型的约束时，new() 约束只能与类类型约束、接口类型约束、引用类型约束和类型参数约束一起使用，而不能与值类型约束一起使用。

示例

下面的示例生成 CS0451。

复制代码
// CS0451.cs
using System;
public class C4
{
   public void F4<T>() where T : struct, new() {}   // CS0451
}

// OK
public class C5
{
   public void F5<T>() where T : struct {}
}

public class C6
{
   public void F6<T>() where T : new() {}
}


编译器错误 CS0452

[image]

Visual C# 参考：错误和警告
编译器错误 CS0452
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型“type name”必须是引用类型才能用作泛型类型或方法“identifier of generic”中的参数“parameter name”

当您将值类型（如 struct 或 int）作为参数传递给具有引用类型约束的泛型类型或方法时发生此错误。

示例

以下代码生成错误 CS0452。

复制代码
// CS0452.cs
using System;
public class BaseClass<S> where S : class { }
public class Derived1 : BaseClass<int> { } // CS0452
public class Derived2<S> : BaseClass<S> where S : struct { } // CS0452


编译器错误 CS0453

[image]

Visual C# 参考：错误和警告
编译器错误 CS0453
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型“Type Name”必须是不可为 null 的值类型才能用作泛型类型中的参数“Parameter Name”或方法“Generic Identifier”

如果您在实例化具有 value 约束的泛型类型或方法时使用非值类型参数，就会发生此错误。使用可为 null 的值类型参数时也可能发生此错误。请参见下面的代码示例的最后两行。

示例

下面的代码生成此错误。

复制代码
// CS0453.cs
using System;
public class HV<S> where S : struct { }
public class H1 : HV<string> { }                   // CS0453
public class H2 : HV<H1> { }                       // CS0453
public class H3<S> : HV<S> where S : class { }     // CS0453
public class H4 : HV<int?> { }                     // CS0453
public class H5 : HV<Nullable<Nullable<int>>> { }  // CS0453


编译器错误 CS0454

[image]

Visual C# 参考：错误和警告
编译器错误 CS0454
示例 发送反馈意见

更新：2007 年 11 月

错误消息

涉及“Type Parameter 1”和“Type Parameter 2”的循环约束依赖项

由于有时一个类型参数引用另一个，而第二个参数反过来又引用第一个，因此会出现此错误。要修复此错误，请移除其中一个约束以中断循环依赖项。请注意，循环约束依赖项可以是间接的。

示例

下面的代码生成错误 CS0454。

复制代码
// CS0554
using System;
public class GenericsErrors
{
    public class G4<T> where T : T { } // CS0454
}

下面的示例显示了一个在两个类型约束之间的循环依赖项。

复制代码
public class Gen<T,U> where T : U where U : T  // CS0454
{
}


编译器错误 CS0455

[image]

Visual C# 参考：错误和警告
编译器错误 CS0455
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型参数“Type Parameter Name”继承有冲突的约束“Constraint Name 1”和“Constraint Name 2”

产生此错误的两种常见情况是：设置约束，使类型参数从两个不相关的类派生，或者使类型参数从类类型或引用类型约束以及 struct 类型或值类型约束派生。要解决此错误，请移除您的继承层次结构中的冲突。

示例

以下代码生成错误 CS0455。

复制代码
// CS0455.cs
using System;

public class GenericsErrors {
    public class B { }
    public class B2 { }
    public class G6<T> where T : B { public class N<U> where U : B2, T { } } // CS0455
}


编译器错误 CS0456

[image]

Visual C# 参考：错误和警告
编译器错误 CS0456
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型参数“Type Parameter Name 1”具有“struct”约束，因此“Type Parameter Name 1”不能用作“Type Parameter Name 2”的约束

值类型约束被隐式密封，因此那些约束不能用作另一个类型参数的约束。这是因为无法重写值类型。要解决此错误，请直接在第二个类型参数上设置值类型约束，而不是通过第一个类型参数间接这样做。

示例

下面的示例生成 CS0456。

复制代码
// CS0456.cs
// compile with: /target:library
public class GenericsErrors
{
   public class G5<T> where T : struct
   {
      public class N<U> where U : T {}   // CS0456
      public class N2<U> where U : struct {}   // OK
   }
}


编译器错误 CS0457

[image]

Visual C# 参考：错误和警告
编译器错误 CS0457
示例 发送反馈意见

更新：2007 年 11 月

错误消息

当从“type name 1”转换为“type name 2”时，用户定义的转换“Conversion method name 1”和“Conversion method name 2”不明确。

有两个转换方法可以使用，编译器无法确定使用哪一个。

可能导致此错误的一种情况如下：

  • 您想从类 A 转换到类 B，其中 A 和 B 不相关。

  • A 从 A0 派生，有一个从 A0 转换到 B 的方法。

  • B 有一个子类 B1，有一个从 A 转换到 B1 的方法。

编译器将认为这两个转换方法是等价的，因为第一个转换方法提供了最好的目标类型，而第二个转换方法提供了最好的源类型。因为编译器无法选择，所以将生成此错误。要解决此问题，请编写一个从 A 转换到 B 的新的显式方法。

另一个导致此错误的情况是有两个从 A 转换到 B 的方法。要修复此错误，请指定显式的强制转换。

示例

下面的示例生成 CS0457。

复制代码
// CS0457.cs
using System;
public class A { }

public class G0 {  }
public class G1<R> : G0 {  }

public class H0 {
   public static implicit operator G0(H0 h) {
      return new G0();
   }
}
public class H1<R> : H0 {
   public static implicit operator G1<R>(H1<R> h) {
      return new G1<R>();
   }
}

public class Test
{
   public static void F0(G0 g) {  }
   public static void Main()
   {
      H1<A> h1a = new H1<A>();
      F0(h1a);   // CS0457
   }
}


编译器错误 CS0459

[image]

Visual C# 参考：错误和警告
编译器错误 CS0459
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能采用只读局部变量的地址

在 C# 语言中有三种生成只读局部变量的常见情况：foreach、using 和 fixed。在每一种情况中，您都不能写入只读局部变量或者采用它的地址。当编译器意识到您正在试图采用只读局部变量的地址时，将产生此错误。

示例

当您试图在 foreach 循环和 fixed 语句块中采用只读局部变量的地址时，下面的示例将生成 CS0459。

复制代码
// CS0459.cs
// compile with: /unsafe

class A
{
    public unsafe void M1()
    {
        int[] ints = new int[] { 1, 2, 3 };
        foreach (int i in ints)
        {
            int *j = &i;  // CS0459
        }

        fixed (int *i = &_i)
        {
            int **j = &i;  // CS0459
        }
    }

    private int _i = 0;
}


编译器错误 CS0460

[image]

Visual C# 参考：错误和警告
编译器错误 CS0460
示例 发送反馈意见

更新：2007 年 11 月

错误消息

重写和显式接口实现方法的约束是从基方法继承的，因此不能直接指定这些约束

当作为派生类一部分的泛型方法重写基类中的方法时，不能对重写方法指定约束。派生类中的重写方法从基类的方法中继承它的约束。

示例

下面的示例生成 CS0460。

复制代码
// CS0460.cs
// compile with: /target:library
class BaseClass
{
   BaseClass() { }
}

interface I
{
   void F1<T>() where T : BaseClass;
   void F2<T>() where T : struct;
   void F3<T>() where T : BaseClass;
}

class ExpImpl : I
{
   void I.F1<T>() where T : BaseClass {}   // CS0460
   void I.F2<T>() where T : class {}  // CS0460
}


编译器错误 CS0462

[image]

Visual C# 参考：错误和警告
编译器错误 CS0462
示例 发送反馈意见

更新：2007 年 11 月

错误消息

继承的成员“member1”和“member2”在类型“type”中具有相同的签名，因此不能重写这些成员

此错误是由于引入泛型而引起的。正常情况下，类中的方法不能有两个具有相同签名的版本。但是对于泛型，如果泛型方法使用某个特定的类型实例化，则可以指定一个可能与另一个方法重复的泛型方法。

示例

当实例化 C<int> 时，将为方法 F 创建两个具有相同签名的版本，所以类 D 中的重写无法确定对哪个方法应用重写。

下面的示例生成 CS0462。

复制代码
// CS0462.cs
// compile with: /target:library
class C<T>
{
   public virtual void F(T t) {}
   public virtual void F(int t) {}
}

class D : C<int>
{
   public override void F(int t) {}   // CS0462
}


编译器错误 CS0463

[image]

Visual C# 参考：错误和警告
编译器错误 CS0463
示例 发送反馈意见

更新：2007 年 11 月

错误消息

计算十进制常数表达式失败，错误为：“error”

当十进制常数表达式在编译时溢出时会发生此错误。

溢出错误通常是在运行时发生。在本例中，您是这样定义常数表达式的：编译器可以计算结果并且知道可能会发生溢出。

示例

下面的代码生成错误 CS0463。

复制代码
// CS0463.cs
using System;
class MyClass
{
    public static void Main()
    {
        const decimal myDec = 79000000000000000000000000000.0m + 79000000000000000000000000000.0m; // CS0463
        Console.WriteLine(myDec.ToString());
    }
}


编译器错误 CS0466

[image]

Visual C# 参考：错误和警告
编译器错误 CS0466
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于“method2”不具有 params 参数，因此“method1”也不应具有该参数

如果实现的接口不使用 params 参数，则不能对类成员使用该参数。

示例

下面的示例生成 CS0466。

复制代码
// CS0466.cs
interface I
{
   void F1(params int[] a);
   void F2(int[] a);
}

class C : I
{
   void I.F1(params int[] a) {}
   void I.F2(params int[] a) {}   // CS0466
   void I.F2(int[] a) {}   // OK

   public static void Main()
   {
      I i = (I) new C();

      i.F1(new int[] {1, 2} );
      i.F2(new int[] {1, 2} );
   }
}


编译器错误 CS0468

[image]

Visual C# 参考：错误和警告
编译器错误 CS0468
发送反馈意见

更新：2007 年 11 月

错误消息

类型“type1”和类型“type2”之间存在多义性

编译程序集时，如果程序集中有两个类型具有相同的完全限定名，则会生成此错误。如果两个类型都位于添加的模块中，或者一个位于添加的模块中，一个位于源代码中，则可能出现此错误。


编译器错误 CS0470

[image]

Visual C# 参考：错误和警告
编译器错误 CS0470
示例 发送反馈意见

更新：2007 年 11 月

错误消息

方法“method”无法实现类型“type”的接口访问器“accessor”。请使用显式接口实现。

访问器尝试实现接口时会生成此错误。必须使用显式接口实现。

示例

下面的示例生成 CS0470。

复制代码
// CS0470.cs
// compile with: /target:library

interface I
{
   int P { get; }
}

class MyClass : I
{
   public int get_P() { return 0; }   // CS0470
   public int P2 { get { return 0;} }   // OK
}


编译器错误 CS0471

[image]

Visual C# 参考：错误和警告
编译器错误 CS0471
示例 发送反馈意见

更新：2007 年 11 月

错误消息

方法“name”不是泛型方法。如果原打算使用表达式列表，请用括号将 < 表达式括起来。

如果代码中包含的表达式列表不带括号，则会生成此错误。

示例

下面的示例生成 CS0471。

复制代码
// CS0471.cs
// compile with: /t:library
class Test
{
    public void F(bool x, bool y) {}
    public void F1()
    {
        int a = 1, b = 2, c = 3;
        F(a<b, c>(3));    // CS0471
        // To resolve, try the following instead:
        // F((a<b), c>(3));
    }
}


编译器错误 CS0473

[image]

Visual C# 参考：错误和警告
编译器错误 CS0473
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

显式接口实现“method name”与多个接口成员匹配。实际选择哪个接口成员将取决于实现。请考虑改用非显式接口实现。

在有些情况下，泛型方法可能会获取与非泛型方法相同的签名。问题在于 Common Language Infrastructure (CLI) 元数据系统无法明确规定将哪个方法绑定到哪个槽。这需要 CLI 做出判断。

说明：

在 Visual Studio 2008 中的有些地方会引发此错误，而在 Visual Studio 2005 中的这些地方则不会引发此错误。

更正此错误

  • 消除显式实现，并且只包含实现这两个接口方法的隐式实现 public int TestMethod(int)。

示例

下面的代码生成 CS0473：

复制代码
// cs0473.cs
public interface ITest<T>
{
    int TestMethod(int i);
    int TestMethod(T i);
}

public class ImplementingClass : ITest<int>
{
    int ITest<int>.TestMethod(int i) // CS0473
    {
        return i + 1;
    }

    public int TestMethod(int i)
    {
        return i - 1;
    }
}

class T
{
    static int Main()
    {
        ImplementingClass a = new ImplementingClass();
        if (a.TestMethod(0) != -1)
            return -1;

        ITest<int> i_a = a;
        System.Console.WriteLine(i_a.TestMethod(0).ToString());
        if (i_a.TestMethod(0) != 1)
            return -1;

        return 0;
    }
}

请参见

其他资源

Fabulous Adventures in Coding（编写代码时的奇特体验）


编译器错误 CS0500

[image]

Visual C# 参考：错误和警告
编译器错误 CS0500
发送反馈意见

更新：2007 年 11 月

错误消息

“class member”无法声明主体，因为它被标记为 abstract

abstract 方法不能包含自己的实现。

下面的示例生成 CS0500：

复制代码
// CS0500.cs
namespace x
{
   abstract public class clx
   {
      abstract public void f(){}   // CS0500
      // try the following line instead
      // abstract public void f();
   }

   public class cly
   {
      public static int Main()
      {
         return 0;
      }
   }
}


编译器错误 CS0501

[image]

Visual C# 参考：错误和警告
编译器错误 CS0501
发送反馈意见

更新：2007 年 11 月

错误消息

“member function”必须声明主体，因为它未标记为 abstract、extern 或 partial

非抽象方法必须有实现。

下面的示例生成 CS0501：

复制代码
// CS0501.cs
// compile with: /target:library
public class clx
{
   public void f();   // CS0501 declared not defined
   public void g() {}   // OK
}


编译器错误 CS0502

[image]

Visual C# 参考：错误和警告
编译器错误 CS0502
发送反馈意见

更新：2007 年 11 月

错误消息

“member”不能既是抽象的又是密封的

一个类不能同时是 abstract 和 sealed 的。

下面的示例生成 CS0502：

复制代码
// CS0502.cs
public class B
{
   abstract public void F();
}

public class C : B
{
   abstract sealed override public void F()   // CS0502
   {
   }
}

public class CMain
{
    public static void Main()
    { }
}


编译器错误 CS0503

[image]

Visual C# 参考：错误和警告
编译器错误 CS0503
发送反馈意见

更新：2007 年 11 月

错误消息

抽象方法“method”不能标记为 virtual

将成员方法既标记为 abstract 又标记为 virtual 是多余的，因为 abstract 暗指 virtual。

下面的示例生成 CS0503：

复制代码
// CS0503.cs
namespace x
{
   abstract public class clx
   {
      abstract virtual public void f();   // CS0503
   }
}


编译器错误 CS0504

[image]

Visual C# 参考：错误和警告
编译器错误 CS0504
发送反馈意见

更新：2007 年 11 月

错误消息

常数“variable”无法标记为 static

如果变量是 const，它也就是 static。如果需要一个既是 const 又是 static 的变量，只需将该变量声明为 const；如果需要的只是 static 变量，只需将其标记为 static。

下面的示例生成 CS0504：

复制代码
// CS0504.cs
namespace x
{
   abstract public class clx
   {
      static const int i = 0;   // CS0504, cannot be both static and const
      abstract public void f();
   }
}


编译器错误 CS0505

[image]

Visual C# 参考：错误和警告
编译器错误 CS0505
发送反馈意见

更新：2007 年 11 月

错误消息

“member1”: 无法重写，因为“member2”不是函数

类声明试图重写基类中非方法的成员。重写必须与成员类型相匹配。如果希望使用与基类中的方法同名的方法，请在基类的方法声明中使用 new（而不使用 override）。

下面的示例生成 CS0505：

复制代码
// CS0505.cs
// compile with: /target:library
public class clx
{
   public int i;
}

public class cly : clx
{
   public override int i() { return 0; }   // CS0505
}


编译器错误 CS0506

[image]

Visual C# 参考：错误和警告
编译器错误 CS0506
发送反馈意见

更新：2007 年 11 月

错误消息

“function1”：无法重写继承成员“function2”，因为它未标记为“virtual”、“abstract”或“override”

将未显式标记为 virtual、abstract 或 override 的方法进行了重写。

下面的示例生成 CS0506：

复制代码
// CS0506.cs
namespace MyNameSpace
{
   abstract public class ClassX
   {
      public int i = 0;

      public int f()
      {
         return 0;
      }
      // Try the following definition for f() instead:
      // abstract public int f();
   }

   public class ClassY : ClassX
   {
      public override int f()   // CS0506
      {
         return 0;
      }

      public static int Main()
      {
         return 0;
      }
   }
}


编译器错误 CS0507

[image]

Visual C# 参考：错误和警告
编译器错误 CS0507
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“function1”：当重写“access”继承成员“function2”时，无法更改访问修饰符

试图在方法重写中更改访问规范。

示例

下面的示例生成 CS0507。

复制代码
// CS0507.cs
abstract public class clx
{
   virtual protected void f() {}
}

public class cly : clx
{
   public override void f() {}   // CS0507
   public static void Main() {}
}

当类试图重写在引用的元数据中定义的标记为 protected internal 的方法时，也会发生 CS0507。在这种情况下，重写方法应标记为 protected。

复制代码
// CS0507_b.cs
// compile with: /target:library
abstract public class clx
{
   virtual protected internal void f() {}
}

下面的示例生成 CS0507。

复制代码
// CS0507_c.cs
// compile with: /reference:cs0507_b.dll
public class cly : clx
{
   protected internal override void f() {}   // CS0507
   // try the following line instead
   // protected override void f() {}   // OK

   public static void Main() {}
}


编译器错误 CS0508

[image]

Visual C# 参考：错误和警告
编译器错误 CS0508
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“Type 1”：返回类型必须是“Type 2”才能与重写成员“Member Name”匹配

试图在方法重写中更改返回类型。若要解决此错误，请确保两个方法都声明相同的返回类型。

示例

下面的示例生成 CS0508。

复制代码
// CS0508.cs
// compile with: /target:library
abstract public class Clx
{
   public int i = 0;
   // Return type is int.
   abstract public int F();
}

public class Cly : Clx
{
   public override double F()
   {
      return 0.0;   // CS0508
   }
}


编译器错误 CS0509

[image]

Visual C# 参考：错误和警告
编译器错误 CS0509
发送反馈意见

更新：2007 年 11 月

错误消息

“class1”: 无法从密封类型“class2”派生

sealed 类不能用作 base 类。默认情况下，结构被密封。

下面的示例生成 CS0509：

复制代码
// CS0509.cs
// compile with: /target:library
sealed public class clx {}
public class cly : clx {}   // CS0509


编译器错误 CS0513

[image]

Visual C# 参考：错误和警告
编译器错误 CS0513
发送反馈意见

更新：2007 年 11 月

错误消息

“function”是抽象的，但它包含在非抽象类“class”中

方法不能是非抽象类的 abstract 成员。

下面的示例生成 CS0513：

复制代码
// CS0513.cs
namespace x
{
   public class clx
   {
      abstract public void f();   // CS0513, abstract member of nonabstract class
   }
}


编译器错误 CS0514

[image]

Visual C# 参考：错误和警告
编译器错误 CS0514
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“constructor”：静态构造函数不能具有显式的“this”或“base”构造函数调用

在静态构造函数中不允许调用 this，因为在创建类的任何实例之前都会自动调用静态构造函数。另外，静态构造函数是不可继承的，而且不能被直接调用。

有关更多信息，请参见this（C# 参考）和base（C# 参考）。

示例

下面的示例生成 CS0514：

复制代码
// CS0514.cs
class A
{
    static A() : base(0) // CS0514
    {
    }

    public A(object o)
    {
    }
}

class B
{
    static B() : this(null) // CS0514
    {
    }

    public B(object o)
    {
    }
}


编译器错误 CS0515

[image]

Visual C# 参考：错误和警告
编译器错误 CS0515
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“function”: 静态构造函数中不允许出现访问修饰符

静态构造函数不能有访问修饰符。

示例

下面的示例生成 CS0515：

复制代码
// CS0515.cs
public class Clx
{
    public static void Main()
    {
    }
}

public class Clz
{
    public static Clz()   // CS0515, remove public keyword
    {
    }
}


编译器错误 CS0516

[image]

Visual C# 参考：错误和警告
编译器错误 CS0516
发送反馈意见

更新：2007 年 11 月

错误消息

构造函数“constructor”不能调用自身

程序不能递归调用构造函数。

下面的示例生成 CS0516：

复制代码
// CS0516.cs
namespace x
{
   public class clx
   {
      public clx() : this()   // CS0516, delete "this()"
      {
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0517

[image]

Visual C# 参考：错误和警告
编译器错误 CS0517
发送反馈意见

更新：2007 年 11 月

错误消息

“class”没有基类，无法调用基构造函数

仅当 .NET Framework 公共语言运行库编译对象类的源代码而该类是唯一没有基类的类时才发生 CS0517。


编译器错误 CS0518

[image]

Visual C# 参考：错误和警告
编译器错误 CS0518
发送反馈意见

更新：2007 年 11 月

错误消息

预定义的类型“type”未定义或未导入

引起该问题的主要原因是项目没有导入 mscorlib.dll，该文件定义整个 System 命名空间。这可能由以下原因之一导致：

  • 指定了命令行编译器的 /nostdlib 选项。/nostdlib 选项将禁止导入 mscorlib.dll。如果想要定义或创建用户特定的 System 命名空间，请使用该选项。

  • 引用了错误的 mscorlib.dll。

  • Visual Studio .NET 或 .NET Framework 公共语言运行库安装存在损坏。

  • 早期安装所剩的组件与最新安装的其余组件不兼容。

要解决此问题，请采用以下操作之一：

  • 不指定命令行编译器的 /nostdlib 选项。

  • 确保项目引用正确的 mscorlib.dll。

  • 重新安装 .NET Framework 公共语言运行库（若以上解决方案均未能解决此问题）。


编译器错误 CS0520

[image]

Visual C# 参考：错误和警告
编译器错误 CS0520
发送反馈意见

更新：2007 年 11 月

错误消息

预定义类型“name”未正确声明

未找到编译器所需的一个或多个文件。重新安装 .NET Framework 公共语言运行库。


编译器错误 CS0522

[image]

Visual C# 参考：错误和警告
编译器错误 CS0522
发送反馈意见

更新：2007 年 11 月

错误消息

“constructor”：结构无法调用基类构造函数

struct 不能调用基类构造函数；请移除对基类构造函数的调用。

下面的示例生成 CS0522：

复制代码
// CS0522.cs
public class clx
{
   public clx(int i)
   {
   }

   public static void Main()
   {
   }
}

public struct cly
{
   public cly(int i):base(0)   // CS0522
   // try the following line instead
   // public cly(int i)
   {
   }
}


编译器错误 CS0523

[image]

Visual C# 参考：错误和警告
编译器错误 CS0523
发送反馈意见

更新：2007 年 11 月

错误消息

“struct1”类型的结构成员“struct2 field”在结构布局中导致循环

两个结构的定义包含递归引用。更改 struct 定义以便每个结构都不会在另一结构上定义自身。此限制仅适用于结构，因为结构是值类型。如果使用递归引用，请将类型声明为类。

下面的示例生成 CS0523：

复制代码
// CS0523.cs
// compile with: /target:library
struct RecursiveLayoutStruct1
{
   public RecursiveLayoutStruct2 field;
}

struct RecursiveLayoutStruct2
{
   public RecursiveLayoutStruct1 field;   // CS0523
}


编译器错误 CS0524

[image]

Visual C# 参考：错误和警告
编译器错误 CS0524
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”：接口不能声明类型

接口不能包含用户定义的类型，只能包含方法和属性。

示例

下面的示例生成 CS0524：

复制代码
// CS0524.cs
public interface Clx
{
    public class Cly   // CS0524, delete user-defined type
    {
    }
}


编译器错误 CS0525

[image]

Visual C# 参考：错误和警告
编译器错误 CS0525
发送反馈意见

更新：2007 年 11 月

错误消息

接口不能包含字段

interface 可以包含方法和属性，但不能包含字段。

下面的示例生成 CS0525：

复制代码
// CS0525.cs
namespace x
{
   public interface clx
   {
      public int i;   // CS0525
   }
}


编译器错误 CS0526

[image]

Visual C# 参考：错误和警告
编译器错误 CS0526
发送反馈意见

更新：2007 年 11 月

错误消息

接口不能包含构造函数

不能为 interface 定义构造函数。如果方法与类同名且没有返回类型，则将其视为构造函数。

下面的示例生成 CS0526：

复制代码
// CS0526.cs
namespace x
{
   public interface clx
   {
      public clx()   // CS0526
      {
      }
   }

   public class cly
   {
      public static void Main()
      {
      }
   }
}


编译器错误 CS0527

[image]

Visual C# 参考：错误和警告
编译器错误 CS0527
发送反馈意见

更新：2007 年 11 月

错误消息

接口列表中的类型“type”不是接口

对于 struct 或 interface，从其他接口而不是其他任何类型继承是可能的。

下面的示例生成 CS0527：

复制代码
// CS0527.cs
// compile with: /target:library
public struct clx : int {}   // CS0527 int not an interface


编译器错误 CS0528

[image]

Visual C# 参考：错误和警告
编译器错误 CS0528
发送反馈意见

更新：2007 年 11 月

错误消息

“interface”已经在接口列表中列出

接口继承列表中有重复接口。 interface 在继承列表中只能指定一次。

下面的示例生成 CS0528：

复制代码
// CS0528.cs
namespace x
{
   public interface a
   {
   }

   public class b : a, a   // CS0528
   {
      public void Main()
      {
      }
   }
}


编译器错误 CS0529

[image]

Visual C# 参考：错误和警告
编译器错误 CS0529
发送反馈意见

更新：2007 年 11 月

错误消息

继承接口“interface1”在“interface2”的接口层次结构中导致一个循环

interface 的继承列表包括对自身的直接或间接引用。接口不能从自身继承。

下面的示例生成 CS0529：

复制代码
// CS0529.cs
namespace x
{
   public interface a
   {
   }

   public interface b : a, c
   {
   }

   public interface c : b   // CS0529, b inherits from c
   {
   }
}


编译器错误 CS0531

[image]

Visual C# 参考：错误和警告
编译器错误 CS0531
发送反馈意见

更新：2007 年 11 月

错误消息

“member”：接口成员不能有定义

interface 中声明的方法必须从该接口继承的类中实现而不是在接口自身中实现。

下面的示例生成 CS0531：

复制代码
// CS0531.cs
namespace x
{
   public interface clx
   {
      int xclx()   // CS0531, cannot define xclx
      // Try the following declaration instead:
      // int xclx();
      {
         return 0;
      }
   }

   public class cly
   {
      public static void Main()
      {
      }
   }
}


编译器错误 CS0533

[image]

Visual C# 参考：错误和警告
编译器错误 CS0533
发送反馈意见

更新：2007 年 11 月

错误消息

“derived-class member”隐藏继承的抽象成员“base-class member”

基 class 方法被隐藏。请检查声明语法以查看它是否正确。

有关更多信息，请参见 base。

下面的示例生成 CS0533：

复制代码
// CS0533.cs
namespace x
{
   abstract public class a
   {
      abstract public void f();
   }

   abstract public class b : a
   {
      new abstract public void f();   // CS0533
      // try the following lines instead
      // override public void f()
      // {
      // }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0534

[image]

Visual C# 参考：错误和警告
编译器错误 CS0534
发送反馈意见

更新：2007 年 11 月

错误消息

“function1”不会实现继承的抽象成员“function2”

一个类应该实现基类中的所有 abstract 成员，除非该类也是抽象的。

下面的示例生成 CS0534：

复制代码
// CS0534.cs
namespace x
{
   abstract public class clx
   {
      public abstract void f();
   }

   public class cly : clx   // CS0534, no override for clx::f
   {
      // uncomment the following sample override to resolve CS0534
      // override public void f()
      // {
      // }

      public static int Main()
      {
         return 0;
      }
   }
}


编译器错误 CS0535

[image]

Visual C# 参考：错误和警告
编译器错误 CS0535
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“class”不会实现接口成员“member”

class 从 interface 派生，但该类未实现该接口的一个或多个成员。类必须实现它从中派生的接口的所有成员，否则必须声明为 abstract。

示例

下面的示例生成 CS0535。

复制代码
// CS0535.cs
public interface A
{
   void F();
}

public class B : A {}   // CS0535 A::F is not implemented

// OK
public class C : A {
   public void F() {}
   public static void Main() {}
}

下面的示例生成 CS0535。

复制代码
// CS0535_b.cs
using System;
class C : IDisposable {}   // CS0535

// OK
class D : IDisposable {
   void IDisposable.Dispose() {}
   public void Dispose() {}

   static void Main() {
      using (D d = new D()) {}
   }
}


编译器错误 CS0537

[image]

Visual C# 参考：错误和警告
编译器错误 CS0537
发送反馈意见

更新：2007 年 11 月

错误消息

类 System.Object 不能有基类也不能实现接口

重新生成 System 类库时，以及在 Object 从其他类派生时，将发生 CS0537。您不大可能会遇到此错误。如果您确实遇到此错误，请不要从类或接口派生 Object：它是整个 .NET Framework 类层次结构的根，因此不从任何其他类或接口派生。


编译器错误 CS0538

[image]

Visual C# 参考：错误和警告
编译器错误 CS0538
发送反馈意见

更新：2007 年 11 月

错误消息

显式接口声明中的“name”不是接口

试图显式声明 interface，但未指定接口。

下面的示例生成 CS0538：

复制代码
// CS0538.cs
interface MyIFace
{
   void F();
}

public class MyClass
{
   public void G()
   {
   }
}

class C: MyIFace
{
   void MyIFace.F()
   {
   }

   void MyClass.G()   // CS0538, MyClass not an interface
   {
   }
}


编译器错误 CS0539

[image]

Visual C# 参考：错误和警告
编译器错误 CS0539
发送反馈意见

更新：2007 年 11 月

错误消息

显式接口声明中的“member”不是接口成员

试图显式声明不存在的 interface 成员。应删除声明或对其进行更改，以便它引用有效的接口成员。

下面的示例生成 CS0539：

复制代码
// CS0539.cs
namespace x
{
   interface I
   {
      void m();
   }

   public class clx : I
   {
      void I.x()   // CS0539
      {
      }

      public static int Main()
      {
         return 0;
      }
   }
}


编译器错误 CS0540

[image]

Visual C# 参考：错误和警告
编译器错误 CS0540
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“interface member”：包含类型不实现接口“interface”

试图实现不是从 interface 派生的 class 中的接口成员。应删除接口成员的实现，或将接口添加到类的基类列表中。

示例

下面的示例生成 CS0540。

复制代码
// CS0540.cs
interface I
{
   void m();
}

public class Clx
{
   void I.m() {}   // CS0540
}

// OK
public class Cly : I
{
   void I.m() {}
   public static void Main() {}
}

下面的示例生成 CS0540。

复制代码
// CS0540_b.cs
using System;
class C {
   void IDisposable.Dispose() {}   // CS0540
}

class D : IDisposable {
   void IDisposable.Dispose() {}
   public void Dispose() {}

   static void Main() {
      using (D d = new D()) {}
   }
}


编译器错误 CS0541

[image]

Visual C# 参考：错误和警告
编译器错误 CS0541
发送反馈意见

更新：2007 年 11 月

错误消息

“declaration”：显式接口声明只能在类或结构中声明

在 class 或 struct 之外找到了显式的 interface 声明。

下面的示例生成 CS0541：

复制代码
// CS0541.cs
namespace x
{
   interface IFace
   {
      void F();
   }

   interface IFace2 : IFace
   {
      void IFace.F();   // CS0541
   }
}


编译器错误 CS0542

[image]

Visual C# 参考：错误和警告
编译器错误 CS0542
发送反馈意见

更新：2007 年 11 月

错误消息

“user-defined type”：成员名称不能与它们的封闭类型相同

某一名称在同一构造中多次使用。造成此错误的原因可能是因疏忽而将返回类型放在了构造函数中。

下面的示例生成 CS0542：

复制代码
// CS0542.cs
class F
{
   // Remove void from F() to resolve the problem.
   void F()   // CS0542, same name as the class
   {
   }
}

class MyClass
{
   public static void Main()
   {
   }
}

如果您的类被命名为“Item”，并且有一个声明为 this 的索引器，您可能会遇到此错误。在发出的代码中为默认索引器给出的名称是“Item”，从而导致冲突。

复制代码
// CS0542b.cs
class Item
{
   public int this[int i]  // CS0542
   {
      get
      {
         return 0;
      }
   }
}

class CMain
{
   public static void Main()
   {
   }
}


编译器错误 CS0543

[image]

Visual C# 参考：错误和警告
编译器错误 CS0543
发送反馈意见

更新：2007 年 11 月

错误消息

“enumeration”：枚举数值太大，不能适应它的类型

赋给 enumeration 中元素的值在数据类型范围之外。

下面的示例生成 CS0543：

复制代码
// CS0543.cs
namespace x
{
   enum I : byte
   {a = 255, b, c}   // CS0543
   public class clx
   {
      public static int Main()
      {
         return 0;
      }
   }
}


编译器错误 CS0544

[image]

Visual C# 参考：错误和警告
编译器错误 CS0544
发送反馈意见

更新：2007 年 11 月

错误消息

“property override”: 无法重写，因为“non-property”不是属性

试图将非属性数据类型重写为属性，这是不允许的。

下面的示例生成 CS0544：

复制代码
// CS0544.cs
// compile with: /target:library
public class a
{
   public int i;
}

public class b : a
{
   public override int i {   // CS0544
   // try the following line instead
   // public new int i {
      get
      {
         return 0;
      }
   }
}


编译器错误 CS0545

[image]

Visual C# 参考：错误和警告
编译器错误 CS0545
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“function”：无法重写，因为“property”没有可重写的 get 访问器

试图定义属性访问器的重写，而此时基类没有这样的定义可供重写。可以通过下面的方法解决该错误：

  • 在基类中添加 set 访问器。

  • 从派生类中移除 set 访问器。

  • 通过在派生类的属性中添加 new 关键字来隐藏基类属性。

  • 生成基类属性 virtual。

有关更多信息，请参见使用属性（C# 编程指南）。

示例

下面的示例生成 CS0545。

复制代码
// CS0545.cs
// compile with: /target:library
// CS0545
public class a
{
   public virtual int i
   {
      set {}

      // Uncomment the following line to resolve.
      // get { return 0; }
   }
}

public class b : a
{
   public override int i
   {
      get { return 0; }
      set {}   // OK
   }
}


编译器错误 CS0546

[image]

Visual C# 参考：错误和警告
编译器错误 CS0546
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“accessor”：无法重写，因为“property”没有可重写的 set 访问器

试图对属性的访问器方法之一进行重写时失败，因为访问器不能重写。可以通过下面的方法解决该错误：

  • 在基类中添加 set 访问器。

  • 从派生类中移除 set 访问器。

  • 通过在派生类的属性中添加 new 关键字来隐藏基类属性。

  • 生成基类属性 virtual。

有关更多信息，请参见使用属性（C# 编程指南）。

示例

下面的示例生成 CS0546。

复制代码
// CS0546.cs
// compile with: /target:library
public class a
{
   public virtual int i
   {
      get
      {
         return 0;
      }
   }

   public virtual int i2
   {
      get
      {
         return 0;
      }

      set {}
   }
}

public class b : a
{
   public override int i
   {
      set {}   // CS0546 error no set
   }

   public override int i2
   {
      set {}   // OK
   }
}


编译器错误 CS0547

[image]

Visual C# 参考：错误和警告
编译器错误 CS0547
发送反馈意见

更新：2007 年 11 月

错误消息

“property”：属性或索引器不能有 void 类型

void 作为属性的返回值无效。

有关更多信息，请参见属性。

下面的示例生成 CS0547：

复制代码
// CS0547.cs
public class a
{
   public void i   // CS0547
   // Try the following declaration instead:
   // public int i
   {
      get
      {
         return 0;
      }
   }
}

public class b : a
{
   public static void Main()
   {
   }
}


编译器错误 CS0548

[image]

Visual C# 参考：错误和警告
编译器错误 CS0548
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“property”：属性或索引器必须至少有一个访问器

属性必须至少有一个访问器（get 或 set）方法。

有关更多信息，请参见 使用属性（C# 编程指南）。

示例

下面的示例生成 CS0548。

复制代码
// CS0548.cs
// compile with: /target:library
public class b
{
   public int MyProp {}   // CS0548

   public int MyProp2   // OK
   {
      get
      {
         return 0;
      }
      set {}
   }
}


编译器错误 CS0549

[image]

Visual C# 参考：错误和警告
编译器错误 CS0549
发送反馈意见

更新：2007 年 11 月

错误消息

“function”是密封类“class”中的新虚拟成员

密封类不能用作基类。 因此，密封类中的虚方法是没用的。

下面的示例生成 CS0549：

复制代码
// CS0549.cs
// compile with: /target:library
sealed public class MyClass
{
   virtual public void TestMethod() {}   // CS0549
   public void TestMethod2() {}   // OK
}


编译器错误 CS0550

[image]

Visual C# 参考：错误和警告
编译器错误 CS0550
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“accessor”添加了接口成员“property”中没有的访问器

派生类中属性的实现包含未在基接口中指定的访问器。

有关更多信息，请参见使用属性（C# 编程指南）。

示例

下面的示例生成 CS0550。

复制代码
// CS0550.cs
namespace x
{
   interface ii
   {
      int i
      {
         get;
         // add the following accessor to resolve this CS0550
         // set;
      }
   }

   public class a : ii
   {
      int ii.i
      {
         get
         {
            return 0;
         }
         set {}   // CS0550  no set in interface
      }

      public static void Main() {}
   }
}


编译器错误 CS0551

[image]

Visual C# 参考：错误和警告
编译器错误 CS0551
示例 发送反馈意见

更新：2007 年 11 月

错误消息

显式接口实现“implementation”缺少访问器“accessor”

显式实现接口属性的类必须实现接口所定义的所有访问器。

有关更多信息，请参见使用属性（C# 编程指南）。

示例

下面的示例生成 CS0551。

复制代码
// CS0551.cs
// compile with: /target:library
interface ii
{
   int i
   {
      get;
      set;
   }
}

public class a : ii
{
   int ii.i { set {} }   // CS0551

   // OK
   int ii.i
   {
      set {}
      get { return 0; }
   }
}


编译器错误 CS0552

[image]

Visual C# 参考：错误和警告
编译器错误 CS0552
发送反馈意见

更新：2007 年 11 月

错误消息

“conversion routine”：与接口之间的用户定义转换

不能创建用户定义的接口转换。如果需要转换例程，则使接口成为类来解决该错误，或者从接口派生类。

下面的示例生成 CS0552：

复制代码
// CS0552.cs
public interface ii
{
}

public class a
{
   // delete the routine to resolve CS0552
   public static implicit operator ii(a aa) // CS0552
   {
      return new ii();
   }

   public static void Main()
   {
   }
}


编译器错误 CS0553

[image]

Visual C# 参考：错误和警告
编译器错误 CS0553
发送反馈意见

更新：2007 年 11 月

错误消息

“conversion routine”：与基类之间的用户定义转换

不允许用户定义的到基类值的转换；您不需要这样的运算符。

下面的示例生成 CS0553：

复制代码
// CS0553.cs
namespace x
{
   public class ii
   {
   }

   public class a : ii
   {
      // delete the conversion routine to resolve CS0553
      public static implicit operator ii(a aa) // CS0553
      {
         return new ii();
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0554

[image]

Visual C# 参考：错误和警告
编译器错误 CS0554
发送反馈意见

更新：2007 年 11 月

错误消息

“conversion routine”：与派生类之间的用户定义转换

不允许用户定义的到派生类值的转换；您不需要这样的运算符。

有关用户定义的转换的更多信息，请参见 C# 语言规范的第 6 章。

下面的示例生成 CS0554：

复制代码
// CS0554.cs
namespace x
{
   public class ii
   {
      // delete the conversion routine to resolve CS0554
      public static implicit operator ii(a aa) // CS0554
      {
         return new ii();
      }
   }

   public class a : ii
   {
      public static void Main()
      {
      }
   }
}


编译器错误 CS0555

[image]

Visual C# 参考：错误和警告
编译器错误 CS0555
发送反馈意见

更新：2007 年 11 月

错误消息

用户定义的运算符不能采用封闭类型的对象，也不能转换成封闭类型的对象

不允许用户定义的到封闭类值的转换；您不需要这样的运算符。

下面的示例生成 CS0555：

复制代码
// CS0555.cs
public class MyClass
{
   // delete the following operator to resolve this CS0555
   public static implicit operator MyClass(MyClass aa)   // CS0555
   {
      return new MyClass();
   }

   public static void Main()
   {
   }
}


编译器错误 CS0556

[image]

Visual C# 参考：错误和警告
编译器错误 CS0556
发送反馈意见

更新：2007 年 11 月

错误消息

用户定义的转换必须是转换成封闭类型，或者从封闭类型转换

用户定义的转换例程必须转换成包含该例程的类，或者从包含该例程的类进行转换。

下面的示例生成 CS0556：

复制代码
// CS0556.cs
namespace x
{
   public class ii
   {
      public class iii
      {
         public static implicit operator int(byte aa)   // CS0556
         // try the following line instead
         // public static implicit operator int(iii aa)
         {
            return 0;
         }
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0557

[image]

Visual C# 参考：错误和警告
编译器错误 CS0557
发送反馈意见

更新：2007 年 11 月

错误消息

类型“class”中有重复的用户定义转换

类中不允许有重复的转换例程。

下面的示例生成 CS0557：

复制代码
// CS0557.cs
namespace x
{
    public class ii
    {
        public class iii
        {
        public static implicit operator int(iii aa)
        {
            return 0;
        }

    // CS0557, delete duplicate
        public static explicit operator int(iii aa)
        {
            return 0;
        }
        }

        public static void Main()
        {
        }
    }
}


编译器错误 CS0558

[image]

Visual C# 参考：错误和警告
编译器错误 CS0558
发送反馈意见

更新：2007 年 11 月

错误消息

用户定义的运算符“operator”必须声明为 static 和 public

static 和 public 访问修饰符必须在用户定义的运算符上指定。

下面的示例生成 CS0558：

复制代码
// CS0558.cs
namespace x
{
   public class ii
   {
      public class iii
      {
         static implicit operator int(iii aa)   // CS0558, add public
         {
            return 0;
         }
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0559

[image]

Visual C# 参考：错误和警告
编译器错误 CS0559
示例 发送反馈意见

更新：2007 年 11 月

错误消息

++ 或 -- 运算符的参数类型必须是包含类型

运算符重载的方法声明必须遵循一定的准则。 ++ 和 -- 运算符要求参数的类型与重载该运算符的类型相同。

示例

下面的示例生成 CS0559：

复制代码
// CS0559.cs
// compile with: /target:library
public class iii
{
   public static implicit operator int(iii x)
   {
      return 0;
   }

   public static implicit operator iii(int x)
   {
      return null;
   }

   public static int operator ++(int aa)   // CS0559
   // try the following line instead
   // public static iii operator ++(iii aa)
   {
      return (iii)0;
   }
}

下面的示例生成 CS0559。

复制代码
// CS0559_b.cs
// compile with: /target:library
public struct S
{
   public static S operator ++(S? s) { return new S(); }   // CS0559
   public static S operator --(S? s) { return new S(); }   // CS0559
}

public struct T
{
// OK
   public static T operator --(T t) { return new T(); }
   public static T operator ++(T t) { return new T(); }

   public static T? operator --(T? t) { return new T(); }
   public static T? operator ++(T? t) { return new T(); }
}


编译器错误 CS0562

[image]

Visual C# 参考：错误和警告
编译器错误 CS0562
示例 发送反馈意见

更新：2007 年 11 月

错误消息

一元运算符的参数必须是包含类型

运算符重载的方法声明必须遵循一定的准则。有关更多信息，请参见可重载运算符和“运算符重载”示例。

示例

下面的示例生成 CS0562：

复制代码
// CS0562.cs
public class iii
{
    public static implicit operator int(iii x)
    {
        return 0;
    }

    public static implicit operator iii(int x)
    {
        return null;
    }

    public static iii operator +(int aa)   // CS0562
    // try the following line instead
    // public static iii operator +(iii aa)
    {
        return (iii)0;
    }

    public static void Main()
    {
    }
}


编译器错误 CS0563

[image]

Visual C# 参考：错误和警告
编译器错误 CS0563
示例 发送反馈意见

更新：2007 年 11 月

错误消息

二元运算符的参数之一必须是包含类型

运算符重载的方法声明必须遵循一定的准则。有关更多信息，请参见“运算符重载”示例。

示例

下面的示例生成 CS0563：

复制代码
// CS0563.cs
public class iii
{
    public static implicit operator int(iii x)
    {
        return 0;
    }
    public static implicit operator iii(int x)
    {
        return null;
    }
    public static int operator +(int aa, int bb)   // CS0563
    // Use the following line instead:
    // public static int operator +(int aa, iii bb)
    {
        return 0;
    }
    public static void Main()
    {
    }
}


编译器错误 CS0564

[image]

Visual C# 参考：错误和警告
编译器错误 CS0564
发送反馈意见

更新：2007 年 11 月

错误消息

重载移位运算符的第一个操作数的类型必须具有与包含类型相同的类型，并且第二个操作数的类型必须为 int 类型

试图用错误类型的操作数重载移位运算符（<< 或 >>）。第一个操作数必须是该包含类型，并且第二个操作数必须是 int 类型的。

下面的示例生成 CS0564：

复制代码
// CS0564.cs
using System;
class C
{
   public static int operator << (C c1, C c2) // CS0564
// To correct, change second operand to int, like so:
// public static int operator << (C c1, int c2)
   {
      return 0;
   }
   static void Main()
   {
   }
}


编译器错误 CS0567

[image]

Visual C# 参考：错误和警告
编译器错误 CS0567
发送反馈意见

更新：2007 年 11 月

错误消息

接口不能包含运算符

interface 定义中不允许有运算符。

下面的示例生成 CS0567：

复制代码
// CS0567.cs
interface IA
{
   int operator +(int aa, int bb);   // CS0567
}

class Sample
{
   public static void Main()
   {
   }
}


编译器错误 CS0568

[image]

Visual C# 参考：错误和警告
编译器错误 CS0568
发送反馈意见

更新：2007 年 11 月

错误消息

结构不能包含显式的无参数构造函数

每个 struct 均已有一个将对象初始化为零的默认构造函数。因此，可以为结构创建的构造函数必须带有一个或多个参数。

下面的示例生成 CS0568：

复制代码
// CS0568.cs
public struct ClassY
{
   public int field1;
   public ClassY(){}   // CS0568, cannot have no param constructor
   // Try following instead:
   // public ClassY(int i)
   // {
   //    field1 = i;
   // }
}

public class ClassX
{
   public static void Main()
   {
   }
}


编译器错误 CS0569

[image]

Visual C# 参考：错误和警告
编译器错误 CS0569
发送反馈意见

更新：2007 年 11 月

错误消息

“method2”：无法重写“method1”，因为该语言不支持它

当您从以另一种语言编写的基类进行派生时，并且当编译器不理解您正试图重写的方法时发生此错误。


编译器错误 CS0570

[image]

Visual C# 参考：错误和警告
编译器错误 CS0570
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性、索引器或事件“name”不受该语言支持；请尝试直接调用访问器方法“name!”

当使用由另一编译器生成的导入元数据时发生此错误。您的代码试图使用编译器无法处理的类成员。

示例

下面的 C++ 程序使用了一种其他语言不能使用的属性 RequiredAttributeAttribute。

复制代码
// CPP0570.cpp
// compile with: /clr /LD

using namespace System;
using namespace System::Runtime::CompilerServices;

namespace CS0570_Server {
   [RequiredAttributeAttribute(Int32::typeid)]
   public ref struct Scenario1 {
      int intVar;
   };

   public ref struct CS0570Class {
      Scenario1 ^ sc1_field;

      property virtual Scenario1 ^ sc1_prop {
         Scenario1 ^ get() { return sc1_field; }
      }

      Scenario1 ^ sc1_method() { return sc1_field; }
   };
};

下面的示例生成 CS0570。

复制代码
// CS0570.cs
// compile with: /reference:CPP0570.dll
using System;
using CS0570_Server;

public class C {
   public static int Main() {
      CS0570Class r = new CS0570Class();
      r.sc1_field = null;   // CS0570
      object o = r.sc1_prop;   // CS0570
      r.sc1_method();   // CS0570
   }
}


编译器错误 CS0571

[image]

Visual C# 参考：错误和警告
编译器错误 CS0571
发送反馈意见

更新：2007 年 11 月

错误消息

“function”：无法显式调用运算符或访问器

某些运算符具有内部名称。例如，op_Increment 是 ++ 运算符的内部名称。不应使用或显式调用这样的方法名称。

下面的示例生成 CS0571：

复制代码
// CS0571.cs
public class MyClass
{
   public static MyClass operator ++ (MyClass c)
   {
      return null;
   }

   public static int prop
   {
      get
      {
         return 1;
      }
      set
      {
      }
   }

   public static void Main()
   {
      op_Increment(null);   // CS0571
      // use the increment operator as follows
      // MyClass x = new MyClass();
      // x++;

      set_prop(1);      // CS0571
      // try the following line instead
      // prop = 1;
   }
}


编译器错误 CS0572

[image]

Visual C# 参考：错误和警告
编译器错误 CS0572
发送反馈意见

更新：2007 年 11 月

错误消息

“type”：无法通过表达式引用类型；请尝试改用“path_to_type”

试图通过标识符访问类的成员，这在此情况中是不允许的。

下面的示例生成 CS0572：

复制代码
// CS0572.cs
using System;
class C
{
   public class Inner
   {
      public static int v = 9;
   }
}

class D : C
{
   public static void Main()
   {
      C cValue = new C();
      Console.WriteLine(cValue.Inner.v);   // CS0572
      // try the following line instead
      // Console.WriteLine(C.Inner.v);
   }
}


编译器错误 CS0573

[image]

Visual C# 参考：错误和警告
编译器错误 CS0573
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“field declaration”: 结构中不能有实例字段初始值设定项

不能对 struct 的实例字段进行初始化。值类型的字段将被初始化为其默认值，而引用类型字段将被初始化为 null。

示例

下面的示例生成 CS0573：

复制代码
// CS0573.cs
namespace x
{
    public class clx
    {
        public static void Main()
        {
        }
    }

    public struct cly
    {
        clx a = new clx();   // CS0573
        // clx a;            // OK
        int i = 7;           // CS0573
        // int i;            // OK
    }
}


编译器错误 CS0574

[image]

Visual C# 参考：错误和警告
编译器错误 CS0574
发送反馈意见

更新：2007 年 11 月

错误消息

析构函数的名称必须与类的名称匹配

析构函数的名称必须是带代字号 (~) 前缀的类名称。

下面的示例生成 CS0574：

复制代码
// CS0574.cs
namespace x
{
   public class iii
   {
      ~iiii()   // CS0574
      {
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0575

[image]

Visual C# 参考：错误和警告
编译器错误 CS0575
发送反馈意见

更新：2007 年 11 月

错误消息

只有类类型才能包含析构函数

struct 不能包含析构函数。

下面的示例生成 CS0575：

复制代码
// CS0575.cs
namespace x
{
   public struct iii
   {
      ~iii()   // CS0575
      {
      }

      public static void Main()
      {
      }
   }
}


编译器错误 CS0576

[image]

Visual C# 参考：错误和警告
编译器错误 CS0576
示例 发送反馈意见

更新：2007 年 11 月

错误消息

命名空间“namespace”包含的定义与别名“identifier”冲突

试图第二次使用同一命名空间。

示例

下面的示例生成 CS0576：

复制代码
// CS0576.cs
using SysIO = System.IO;
public class SysIO
{
   public void MyMethod() {}
}

public class Test
{
   public static void Main()
   {
      SysIO.Stream s;   // CS0576
   }
}


编译器错误 CS0577

[image]

Visual C# 参考：错误和警告
编译器错误 CS0577
发送反馈意见

更新：2007 年 11 月

错误消息

Conditional 属性在“function”上无效，因为它是构造函数、析构函数、运算符或显式接口实现

Conditional 无法应用于指定的方法。

例如，您无法对显式接口定义使用某些属性。下面的示例生成 CS0577：

复制代码
// CS0577.cs
// compile with: /target:library
interface I
{
   void m();
}

public class MyClass : I
{
   [System.Diagnostics.Conditional("a")]   // CS0577
   void I.m() {}
}


编译器错误 CS0578

[image]

Visual C# 参考：错误和警告
编译器错误 CS0578
示例 发送反馈意见

更新：2007 年 11 月

错误消息

Conditional 属性在“function”上无效，因为其返回类型不是 void

ConditionalAttribute 不能应用于返回类型不是 void 的方法。其原因在于：程序的其他部分可能需要方法的任何其他返回类型。

示例

下面的示例生成 CS0578。若要解决此错误，必须删除 ConditionalAttribute，或者将方法的返回值更改为 void。

复制代码
// CS0578.cs
// compile with: /target:library
public class MyClass
{
   [System.Diagnostics.ConditionalAttribute("a")]   // CS0578
   public int TestMethod()
   {
      return 0;
   }
}


编译器错误 CS0579

[image]

Visual C# 参考：错误和警告
编译器错误 CS0579
示例 发送反馈意见

更新：2007 年 11 月

错误消息

重复“attribute”属性

不可能多次指定相同的属性，除非属性在其 AttributeUsage 中指定 AllowMultiple=true。

示例

下面的示例生成 CS0579。

复制代码
// CS0579.cs
using System;
public class MyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.All,AllowMultiple=true)]
public class MyAttribute2 : Attribute
{
}

public class z
{
    [MyAttribute, MyAttribute]     // CS0579
    public void zz()
    {
    }

    [MyAttribute2, MyAttribute2]   // OK
    public void zzz()
    {
    }

    public static void Main()
    {
    }
}


编译器错误 CS0582

[image]

Visual C# 参考：错误和警告
编译器错误 CS0582
发送反馈意见

更新：2007 年 11 月

错误消息

Conditional 在接口成员上无效

ConditionalAttribute 在接口成员上无效。

下面的示例生成 CS0582：

复制代码
// CS0582.cs
// compile with: /target:library
using System.Diagnostics;
interface MyIFace
{
   [ConditionalAttribute("DEBUG")]   // CS0582
   void zz();
}


编译器错误 CS0583

[image]

Visual C# 参考：错误和警告
编译器错误 CS0583
发送反馈意见

更新：2007 年 11 月

错误消息

内部编译器错误。编译器中发生了内部错误。若要解决此问题，请尝试简化或更改上面所列位置附近的程序。列表顶部的位置比较接近发生内部错误的位置。可使用 /errorreport 选项将此类错误报告给 Microsoft。

请使用“/bugreport”选项创建一个 bug 报告文件，并将其提交给为您指派的问题报告联系人。

使用 /bugreport 编译器选项捕获问题。


编译器错误 CS0584

[image]

Visual C# 参考：错误和警告
编译器错误 CS0584
发送反馈意见

更新：2007 年 11 月

错误消息

内部编译器错误：步骤“stage”符号“symbol”

尝试确定编译器是否由于无法分析意外的语法而失败。否则，请尝试从 Microsoft 支持获得帮助 (Visual Studio)。


编译器错误 CS0585

[image]

Visual C# 参考：错误和警告
编译器错误 CS0585
发送反馈意见

更新：2007 年 11 月

错误消息

内部编译器错误：步骤“stage”

试图确定编译器是否由于无法分析意外的语法而失败。否则，请尝试从 Microsoft 支持获得帮助 (Visual Studio)。


编译器错误 CS0586

[image]

Visual C# 参考：错误和警告
编译器错误 CS0586
发送反馈意见

更新：2007 年 11 月

错误消息

内部编译器错误：步骤“stage”

试图确定编译器是否由于无法分析意外的语法而失败。否则，请尝试从 Microsoft 支持获得帮助 (Visual Studio)。


编译器错误 CS0587

[image]

Visual C# 参考：错误和警告
编译器错误 CS0587
发送反馈意见

更新：2007 年 11 月

错误消息

内部编译器错误：步骤“stage”

试图确定编译器是否由于无法分析意外的语法而失败。否则，请尝试从 Microsoft 支持获得帮助 (Visual Studio)。


编译器错误 CS0588

[image]

Visual C# 参考：错误和警告
编译器错误 CS0588
发送反馈意见

更新：2007 年 11 月

错误消息

内部编译器错误: 步骤“LEX”

尝试确定编译器是否由于无法分析意外的语法而失败。否则，请尝试从 Microsoft 支持获得帮助 (Visual Studio)。


编译器错误 CS0589

[image]

Visual C# 参考：错误和警告
编译器错误 CS0589
发送反馈意见

更新：2007 年 11 月

错误消息

内部编译器错误: 步骤“PARSE”

试图确定编译器是否由于无法分析意外的语法而失败。否则，请尝试从 Microsoft 支持获得帮助 (Visual Studio)。


编译器错误 CS0590

[image]

Visual C# 参考：错误和警告
编译器错误 CS0590
发送反馈意见

更新：2007 年 11 月

错误消息

用户定义的运算符不能返回 void

用户定义的运算符旨在返回对象。

下面的示例生成 CS0590：

复制代码
// CS0590.cs
namespace x
{
   public class a
   {
      public static void operator+(a A1, a A2)   // CS0590
      {
      }

      // try the following user-defined operator
      /*
      public static a operator+(a A1, a A2)
      {
         return A2;
      }
      */

      public static int Main()
      {
         return 1;
      }
   }
}


编译器错误 CS0591

[image]

Visual C# 参考：错误和警告
编译器错误 CS0591
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“attribute”属性的参数值无效

传递给属性的是一个无效参数或两个互斥参数。

示例

下面的示例生成 CS0591：

复制代码
// CS0591.cs
using System;

[AttributeUsage(0)]   // CS0591
class I: Attribute
{
}

public class a
{
    public static void Main()
    {
    }
}


编译器错误 CS0592

[image]

Visual C# 参考：错误和警告
编译器错误 CS0592
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性“attribute”在此声明类型上无效。它只在“type”声明中有效。

对声明应用了一个对它不适用的属性。

示例

下面的示例生成 CS0592：

复制代码
// CS0592.cs
using System;

[AttributeUsage(AttributeTargets.Interface)]
public class MyAttribute : Attribute
{
}

[MyAttribute]
public class A  // CS0592, MyAttribute is not valid for a class
{
    public static void Main()
    {
    }
}


编译器错误 CS0594

[image]

Visual C# 参考：错误和警告
编译器错误 CS0594
发送反馈意见

更新：2007 年 11 月

错误消息

浮点常数超出“type”类型的范围

给浮点型变量所赋的值对于此数据类型的变量而言太长。有关数据类型允许的值范围的信息，请参见整型表。

下面的示例生成 CS0594：

复制代码
// CS0594.cs
namespace MyNamespace
{
   public class MyClass
   {
      public static void Main()
      {
         float f = 6.77777777777E400;   // CS0594, value too large
      }
   }
}


编译器错误 CS0596

[image]

Visual C# 参考：错误和警告
编译器错误 CS0596
发送反馈意见

更新：2007 年 11 月

错误消息

Guid 属性必须用 ComImport属性指定

在使用 ComImport 属性时必须有 Guid 属性。

下面的示例生成 CS0596：

复制代码
// CS0596.cs
using System.Runtime.InteropServices;

namespace x
{
   [ComImport]   // CS0596
   // try the following line to resolve this CS0596
   // [ComImport, Guid("00000000-0000-0000-0000-000000000001")]
   public class a
   {
   }

   public class b
   {
      public static void Main()
      {
      }
   }
}


编译器错误 CS0599

[image]

Visual C# 参考：错误和警告
编译器错误 CS0599
发送反馈意见

更新：2007 年 11 月

错误消息

命名属性参数“argument”的值无效

传递给属性的一个参数无效。


编译器错误 CS0601

[image]

Visual C# 参考：错误和警告
编译器错误 CS0601
发送反馈意见

更新：2007 年 11 月

错误消息

必须在标记为“static”和“extern”的方法上指定 DllImport 属性

DllImport 属性用在了不具有正确访问关键字的方法中。

下面的示例生成 CS0601：

复制代码
// CS0601.cs
using System.Runtime.InteropServices;
using System.Text;

public class C
{
   [DllImport("KERNEL32.DLL")]
   extern int GetCurDirectory(int bufSize, StringBuilder buf);   // CS0601
   // Try the following line instead:
   // static extern int GetCurDirectory(int bufSize, StringBuilder buf);
}

public class MainClass
{
   public static void Main ()
   {
   }
}


编译器错误 CS0609

[image]

Visual C# 参考：错误和警告
编译器错误 CS0609
发送反馈意见

更新：2007 年 11 月

错误消息

不能在标记为 override 的索引器上设置 IndexerName 属性

名称特性 (IndexerNameAttribute) 不能应用于本身为重写的索引属性。有关更多信息，请参见索引器。

下面的示例生成 CS0609：

复制代码
// CS0609.cs
using System;
using System.Runtime.CompilerServices;

public class idx
{
   public virtual int this[int iPropIndex]
   {
      get
      {
         return 0;
      }
      set
      {
      }
   }
}

public class MonthDays : idx
{
   [IndexerName("MonthInfoIndexer")]   // CS0609, delete to resolve this CS0609
   public override int this[int iPropIndex]
   {
      get
      {
         return 0;
      }
      set
      {
      }
   }
}

public class test
{
   public static void Main( string[] args )
   {
   }
}


编译器错误 CS0610

[image]

Visual C# 参考：错误和警告
编译器错误 CS0610
发送反馈意见

更新：2007 年 11 月

错误消息

字段或属性不能是“type”类型

有些类型不能用作字段或属性。这些类型包括 System.ArgIterator 和 System.TypedReference。

下面的示例由于将 System.TypedReference 用作字段而生成 CS0610：

复制代码
// CS0610.cs
public class MainClass
{
   System.TypedReference i;   // CS0610
   public static void Main ()
   {
   }

   public static void Test(System.TypedReference i)   // OK
   {
   }
}


编译器错误 CS0611

[image]

Visual C# 参考：错误和警告
编译器错误 CS0611
发送反馈意见

更新：2007 年 11 月

错误消息

数组元素不能是“type”类型

有些类型不能用作数组的类型。这些类型包括 System.TypedReference 和 System.ArgIterator。

下面的示例由于将 System.TypedReference 用作数据元素而生成 CS0611：

复制代码
// CS0611.cs
public class a
{
   public static void Main()
   {
      System.TypedReference[] ao = new System.TypedReference [10];   // CS0611
      // try the following line instead
      // int[] ao = new int[10];
   }
}


编译器错误 CS0616

[image]

Visual C# 参考：错误和警告
编译器错误 CS0616
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“class”不是属性类

试图在属性块中使用非属性类。所有属性类型都必须从 System..::.Attribute 继承。

示例

下面的示例生成 CS0616。

复制代码
// CS0616.cs
// compile with: /target:library
[CMyClass(i = 5)]   // CS0616
public class CMyClass {}

下面的示例显示您可以如何定义属性：

复制代码
// CreateAttrib.cs
// compile with: /target:library
using System;

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]
public class MyAttr : Attribute
{
   public int Name = 0;
   public int Count = 0;

   public MyAttr (int iCount, int sName)
   {
      Count = iCount;
      Name = sName;
   }
}

[MyAttr(5, 50)]
class Class1 {}

[MyAttr(6, 60)]
interface Interface1 {}


编译器错误 CS0617

[image]

Visual C# 参考：错误和警告
编译器错误 CS0617
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“reference”不是有效的属性参数类型，因此不是有效的命名属性参数

试图访问属性类的私有成员。

示例

下面的示例生成 CS0617。

复制代码
// CS0617.cs
using System;

[AttributeUsage(AttributeTargets.Struct |
                AttributeTargets.Class |
                AttributeTargets.Interface)]
public class MyClass : Attribute
{
   public int Name;

   public MyClass (int sName)
   {
      Name = sName;
      Bad = -1;
      Bad2 = -1;
   }

   public readonly int Bad;
   public int Bad2;
}

[MyClass(5, Bad=0)] class Class1 {}   // CS0617
[MyClass(5, Bad2=0)] class Class2 {}


编译器错误 CS0619

[image]

Visual C# 参考：错误和警告
编译器错误 CS0619
发送反馈意见

更新：2007 年 11 月

错误消息

“member”已过时：“text”

类成员是用 Obsolete 属性标记的，以致在引用该类成员时会发出错误。

下面的示例生成 CS0619：

复制代码
// CS0619.cs
using System;

public class C
{
   [Obsolete("Use newMethod instead", true)]   // generates an error on use
   public static void m()
   {
   }

   // this is the method you should be using
   public static void newMethod()
   {
   }
}

class MyClass
{
   public static void Main()
   {
      C.m();   // CS0619
   }
}


编译器错误 CS0620

[image]

Visual C# 参考：错误和警告
编译器错误 CS0620
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

索引器不能有 void 类型

索引器的返回类型不能是 void。索引器必须返回某个值。

下面的示例生成 CS0620：

复制代码
// CS0620.cs
class MyClass
{
   public static void Main()
   {
      MyClass test = new MyClass();
      System.Console.WriteLine(test[2]);
   }

   void this [int intI]   // CS0620, return type cannot be void
   {
      get
      {
         // will need to return some value
      }
   }
}

请参见

参考

void（C# 参考）


编译器错误 CS0621

[image]

Visual C# 参考：错误和警告
编译器错误 CS0621
发送反馈意见

更新：2007 年 11 月

错误消息

“member”: 虚拟成员或抽象成员不能是私有的

不允许使用私有的 virtual 或 abstract 成员。

下面的示例生成 CS0621：

复制代码
// CS0621.cs
abstract class MyClass
{
   private virtual void DoNothing1()   // CS0621
   {
   }

   private abstract void DoNothing2();   // CS0621

   public static void Main()
   {
   }
}


编译器错误 CS0622

[image]

Visual C# 参考：错误和警告
编译器错误 CS0622
示例 发送反馈意见

更新：2007 年 11 月

错误消息

只能使用数组初始值设定项表达式为数组类型赋值。请尝试改用新的表达式。

在非数组的声明中使用了适合于初始化数组的语法。

示例

下面的示例生成 CS0622：

复制代码
// CS0622.cs
using System;

public class Test
{
    public static void Main ()
    {
        Test t = { new Test() };   // CS0622
        // Try the following instead:
        // Test[] t = { new Test() };
    }
}


编译器错误 CS0623

[image]

Visual C# 参考：错误和警告
编译器错误 CS0623
发送反馈意见

更新：2007 年 11 月

错误消息

数组初始值设定项只能在变量或字段初始值设定项中使用。请尝试改用新的表达式。

数组未正确初始化。


编译器错误 CS0625

[image]

Visual C# 参考：错误和警告
编译器错误 CS0625
发送反馈意见

更新：2007 年 11 月

错误消息

“field”: 标记为 StructLayout(LayoutKind.Explicit) 的实例字段类型必须具有 FieldOffset 属性

当结构标记为显式 StructLayout 属性时，结构中的所有字段必须具有 FieldOffset 属性。有关更多信息，请参见 StructLayoutAttribute 类。

下面的示例生成 CS0625：

复制代码
// CS0625.cs
// compile with: /target:library
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
struct A
{
   public int i;   // CS0625 not static; an instance field
}

// OK
[StructLayout(LayoutKind.Explicit)]
struct B
{
   [FieldOffset(5)]
   public int i;
}


编译器错误 CS0629

[image]

Visual C# 参考：错误和警告
编译器错误 CS0629
发送反馈意见

更新：2007 年 11 月

错误消息

条件成员“member”无法在类型“Type Name”中实现接口成员“base class member”

Conditional 属性不能用在接口的实现中。

下面的示例生成 CS0629：

复制代码
// CS0629.cs
interface MyInterface
{
   void MyMethod();
}

public class MyClass : MyInterface
{
   [System.Diagnostics.Conditional("debug")]
   public void MyMethod()    // CS0629, remove the Conditional attribute
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0631

[image]

Visual C# 参考：错误和警告
编译器错误 CS0631
示例 发送反馈意见

更新：2007 年 11 月

错误消息

ref 和 out 参数在此上下文中无效

indexer 的声明不能包括对 ref 或 out 参数的使用。

示例

下面的示例生成 CS0631：

复制代码
// CS0631.cs
public class MyClass
{
    public int this[ref int i]   // CS0631
    // try the following line instead
    // public int this[int i]
    {
        get
        {
            return 0;
        }
    }
}

public class MyClass2
{
    public static void Main()
    {
    }
}


编译器错误 CS0633

[image]

Visual C# 参考：错误和警告
编译器错误 CS0633
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“attribute”属性的参数必须是有效的标识符

传递给 ConditionalAttribute 或 IndexerNameAttribute 属性的任何参数都必须是有效的标识符。这意味着它不能包含出现在标识符中时非法的字符，如“+”。

示例

此示例使用 ConditionalAttribute 阐释了 CS0633。下面的示例生成 CS0633。

复制代码
// CS0633a.cs
#define DEBUG
using System.Diagnostics;
public class Test
{
   [Conditional("DEB+UG")]   // CS0633
   // try the following line instead
   // [Conditional("DEBUG")]
   public static void Main() { }
}

此示例使用 IndexerNameAttribute 阐释了 CS0633。

复制代码
// CS0633b.cs
// compile with: /target:module
#define DEBUG
using System.Runtime.CompilerServices;
public class Test
{
   [IndexerName("Invalid+Identifier")]   // CS0633
   // try the following line instead
   // [IndexerName("DEBUG")]
   public int this[int i]
   {
      get { return i; }
   }
}


编译器错误 CS0635

[image]

Visual C# 参考：错误和警告
编译器错误 CS0635
发送反馈意见

更新：2007 年 11 月

错误消息

“attribute”：System.Interop.UnmanagedType.CustomMarshaller 需要命名参数 ComType 和 Marshal

ComType 和 Marshal 参数必须在封送格式为 System.Interop.UnmanagedType.CustomMarshaller 时指定。


编译器错误 CS0636

[image]

Visual C# 参考：错误和警告
编译器错误 CS0636
发送反馈意见

更新：2007 年 11 月

错误消息

FieldOffset 属性只能放置在标记为 StructLayout(LayoutKind.Explicit) 的类型的成员上

如果结构包含标记为 FieldOffset 属性的任何成员，必须在该属性的声明上使用 StructLayout(LayoutKind.Explicit) 属性。有关更多信息，请参见 FieldOffset。

下面的示例生成 CS0636：

复制代码
// CS0636.cs
using System;
using System.Runtime.InteropServices;

// To resolve the error, uncomment the following line:
// [StructLayout(LayoutKind.Explicit)]
struct Worksheet
{
   [FieldOffset(4)]public int i;   // CS0636
}

public class MainClass
{
   public static void Main ()
   {
   }
}


编译器错误 CS0637

[image]

Visual C# 参考：错误和警告
编译器错误 CS0637
发送反馈意见

更新：2007 年 11 月

错误消息

静态或常数字段上不允许 FieldOffset 属性

FieldOffset 属性不能用在使用 static 或 const 标记的字段上。

下面的示例生成 CS0637：

复制代码
// CS0637.cs
using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Explicit)]
public class MainClass
{
   [FieldOffset(3)]   // CS0637
   public static int i;
   public static void Main ()
   {
   }
}


编译器错误 CS0641

[image]

Visual C# 参考：错误和警告
编译器错误 CS0641
发送反馈意见

更新：2007 年 11 月

错误消息

“attribute”: 属性只在由 System.Attribute 派生的类中有效

使用的属性只能用在从 System.Attribute 派生的类上。

下面的示例生成 CS0641：

复制代码
// CS0641.cs
using System;

[AttributeUsage(AttributeTargets.All)]
public class NonAttrClass   // CS0641
// try the following line instead
// public class NonAttrClass : Attribute
{
}

class MyClass
{
   public static void Main()
   {
   }
}


编译器错误 CS0643

[image]

Visual C# 参考：错误和警告
编译器错误 CS0643
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“arg”重复命名属性参数

参数 arg 在用户定义的属性上被指定了两次。有关更多信息，请参见属性（C# 编程指南）。

示例

下面的示例生成 CS0643：

复制代码
// CS0643.cs
using System;
using System.Runtime.InteropServices;

[AttributeUsage(AttributeTargets.Class)]
public class MyAttribute : Attribute
{
    public MyAttribute()
    {
    }

    public int x;
}

[MyAttribute(x = 5, x = 6)]   // CS0643, error setting x twice
// try the following line instead
// [MyAttribute(x = 5)]
class MyClass
{
}

public class MainClass
{
    public static void Main ()
    {
    }
}


编译器错误 CS0644

[image]

Visual C# 参考：错误和警告
编译器错误 CS0644
发送反馈意见

更新：2007 年 11 月

错误消息

“class1”不能从特殊类“class2”派生

类不能从下列任何基类中显式继承：

  • System.Enum

  • System.ValueType

  • System.Delegate

  • System.Array

这些基类被编译器用作隐式基类。例如，System.ValueType 是结构的隐式基类。

下面的示例生成 CS0644：

复制代码
// CS0644.cs
class MyClass : System.ValueType   // CS0644
{
}


编译器错误 CS0645

[image]

Visual C# 参考：错误和警告
编译器错误 CS0645
发送反馈意见

更新：2007 年 11 月

错误消息

标识符太长

类名称或其他标识符的长度不能超过 512 个字符。


编译器错误 CS0646

[image]

Visual C# 参考：错误和警告
编译器错误 CS0646
发送反馈意见

更新：2007 年 11 月

错误消息

不能对包含索引器的类型指定 DefaultMember 属性

如果类或其他类型指定了 System.Reflection.DefaultMemberAttribute，则不能包含索引器。有关更多信息，请参见属性。

下面的示例生成 CS0646：

复制代码
// CS0646.cs
// compile with: /target:library
[System.Reflection.DefaultMemberAttribute("x")]   // CS0646
class MyClass
{
   public int this[int index]   // an indexer
   {
      get
      {
         return 0;
      }
   }

   public int x = 0;
}

// OK
[System.Reflection.DefaultMemberAttribute("x")]
class MyClass2
{
   public int prop
   {
      get
      {
         return 0;
      }
   }

   public int x = 0;
}

class MyClass3
{
   public int this[int index]   // an indexer
   {
      get
      {
         return 0;
      }
   }

   public int x = 0;
}


编译器错误 CS0647

[image]

Visual C# 参考：错误和警告
编译器错误 CS0647
发送反馈意见

更新：2007 年 11 月

错误消息

发出“attribute”属性时出错 --“reason”

下面的示例生成 CS0647：

复制代码
// CS0647.cs
using System.Runtime.InteropServices;

[Guid("z")]   // CS0647, incorrect uuid format.
// try the following line instead
// [Guid("00000000-0000-0000-0000-000000000001")]
public class MyClass
{
   public static void Main()
   {
   }
}


编译器错误 CS0648

[image]

Visual C# 参考：错误和警告
编译器错误 CS0648
发送反馈意见

更新：2007 年 11 月

错误消息

“type”是该语言不支持的类型

从其他语言（可能为 C++）生成的元数据包含了未标记为托管的类型。有关该类型的信息包含在元数据中，但该类型不能用于用 C# 编写的程序。


编译器错误 CS0650

[image]

Visual C# 参考：错误和警告
编译器错误 CS0650
示例 发送反馈意见

更新：2007 年 11 月

错误消息

错误的数组声明符: 若要声明托管数组，秩说明符应位于变量的标识符之前。若要声明固定大小缓冲区字段，应在字段类型之前使用 fixed 关键字。

数组未正确声明。请注意，固定大小缓冲区的语法与数组的不同。

示例

下面的示例生成 CS0650。

复制代码
// CS0650.cs
public class MyClass
{
   public static void Main()
   {
      int myarray[2];   // CS0650

      // OK
      int[] myarray2 = new int[2] {1,2};
      myarray2[0] = 0;
    }
}


编译器错误 CS0653

[image]

Visual C# 参考：错误和警告
编译器错误 CS0653
发送反馈意见

更新：2007 年 11 月

错误消息

无法应用属性类“class”，因为它是抽象的

abstract 自定义属性类不能用作属性。

下面的示例生成 CS0653：

复制代码
// CS0653.cs
using System;

public abstract class MyAttribute : Attribute
{
}

public class My2Attribute : MyAttribute
{
}

[My]   // CS0653
// try the following line instead
// [My2]
class MyClass
{
   public static void Main()
   {
   }
}


编译器错误 CS0655

[image]

Visual C# 参考：错误和警告
编译器错误 CS0655
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“parameter”不是有效的命名属性参数，因为它不是有效的属性参数类型

有关属性的有效参数类型的讨论，请参见属性（C# 编程指南）。

示例

下面的示例生成 CS0655：

复制代码
// CS0655.cs
using System;

class MyAttribute : Attribute
{
    // decimal is not valid attribute parameter type
    public decimal d = 0;
    public int e = 0;
}

[My(d = 0)]   // CS0655
// Try the following line instead:
// [My(e = 0)]
class C
{
    public static void Main()
    {
    }
}


编译器错误 CS0656

[image]

Visual C# 参考：错误和警告
编译器错误 CS0656
发送反馈意见

更新：2007 年 11 月

错误消息

缺少编译器要求的成员“object.member”

存在以下问题之一：

  • 公共语言运行库安装损坏。

  • 引用的程序集定义了也可在公共语言运行库中找到的类型。但程序集的类型没有按照 C# 编译器需要的方式进行定义。

检查引用以确保正使用正确版本的公共语言运行库。


编译器错误 CS0662

[image]

Visual C# 参考：错误和警告
编译器错误 CS0662
发送反馈意见

更新：2007 年 11 月

错误消息

“method”不能在 ref 参数上仅指定 Out 属性。请同时使用 In 和 Out 属性，或者两者都不使用。

接口方法的一个参数使用了只有 Out 属性的 ref。使用 Out 属性的 ref 参数还必须使用 In 属性。

下面的示例生成 CS0662：

复制代码
// CS0662.cs
using System.Runtime.InteropServices;

interface I
{
   void method([Out] ref int i);   // CS0662
   // try one of the following lines instead
   // void method(ref int i);
   // void method([Out, In]ref int i);
}

class test
{
   public static void Main()
   {
   }
}


编译器错误 CS0663

[image]

Visual C# 参考：错误和警告
编译器错误 CS0663
发送反馈意见

更新：2007 年 11 月

错误消息

不能定义仅在 ref 和 out 上有差别的重载方法。

不允许使用仅在参数的 ref 和 out 用法上有差异的方法。

下面的示例生成 CS0663：

复制代码
// CS0663.cs
class TestClass
{
   public static void Main()
   {
   }

   public void Test(ref int i)
   {
   }

   public void Test(out int i)   // CS0663
   {
   }
}


编译器错误 CS0664

[image]

Visual C# 参考：错误和警告
编译器错误 CS0664
发送反馈意见

更新：2007 年 11 月

错误消息

不能隐式地将 Double 类型转换为“type”类型；请使用“suffix”后缀创建此类型

赋值无法完成，请使用后缀更正指令。每个类型的文档确定该类型的相应后缀。

下面的示例生成 CS0664：

复制代码
// CS0664.cs
class M
{
   static void Main()
   {
      decimal m = 1.0;   // CS0664
      // try the following line instead
      // decimal m = 1.0M;
      System.Console.WriteLine(m);
   }
}


编译器错误 CS0666

[image]

Visual C# 参考：错误和警告
编译器错误 CS0666
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“member”：结构中已声明新的保护成员

struct 不能为 abstract，而应始终为隐式 sealed。由于结构不支持继承，因此结构中的 protected 成员的概念没有意义。有关更多信息，请参见继承（C# 编程指南）。

示例

下面的示例生成 CS0666：

复制代码
// CS0666.cs
class M
{
    static void Main()
    {
    }
}

struct S
{
    protected int x;   // CS0666
}


编译器错误 CS0667

[image]

Visual C# 参考：错误和警告
编译器错误 CS0667
发送反馈意见

更新：2007 年 11 月

错误消息

“无效功能”这一功能已不推荐使用。请改用“有效功能”。

您所尝试使用的功能已不推荐使用。请更新代码以改用有效功能。


编译器错误 CS0668

[image]

Visual C# 参考：错误和警告
编译器错误 CS0668
发送反馈意见

更新：2007 年 11 月

错误消息

两个索引器的名称不同；类型中的每个索引器上都必须用相同的名称使用 IndexerName 属性

对于类型中的所有索引器，传递给 IndexerName 属性的值必须是相同的。有关 IndexerName 属性的更多信息，请参见 IndexerNameAttribute 类。

下面的示例生成 CS0668：

复制代码
// CS0668.cs
using System;
using System.Runtime.CompilerServices;

class IndexerClass
{
   [IndexerName("IName1")]
   public int this [int index]   // indexer declaration
   {
      get
      {
         return index;
      }
      set
      {
      }
   }

   [IndexerName("IName2")]
   public int this [string s]    // CS0668, change IName2 to IName1
   {
      get
      {
         return int.Parse(s);
      }
      set
      {
      }
   }

   void Main()
   {
   }
}


编译器错误 CS0669

[image]

Visual C# 参考：错误和警告
编译器错误 CS0669
发送反馈意见

更新：2007 年 11 月

错误消息

具有 ComImport 属性的类不能有用户定义的构造函数

公共语言运行库中的 COM 互操作层为 ComImport 类提供构造函数。因此，COM 对象可以在运行库中作为托管对象使用。

下面的示例生成 CS0669：

复制代码
// CS0669.cs
using System.Runtime.InteropServices;
[ComImport, Guid("00000000-0000-0000-0000-000000000001")]
class TestClass
{
   TestClass()   // CS0669, delete constructor to resolve
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0670

[image]

Visual C# 参考：错误和警告
编译器错误 CS0670
发送反馈意见

更新：2007 年 11 月

错误消息

字段不能有 void 类型

字段被声明成了 void 类型。

下面的示例生成 CS0670：

复制代码
// CS0670.cs
class C
{
   void f;   // CS0670
   // try the following line instead
   // public int f;

   public static void Main()
   {
      C myc = new C();
      myc.f = 0;
   }
}


编译器错误 CS0673

[image]

Visual C# 参考：错误和警告
编译器错误 CS0673
发送反馈意见

更新：2007 年 11 月

错误消息

在 C# 中无法使用 System.Void - 使用 typeof(void) 获取 void 类型对象。

System.Void 不能用在 C# 中。

下面的示例生成 CS0673：

复制代码
// CS0673.cs
class MyClass
{
   public static void Main()
   {
      System.Type t = typeof(System.Void);   // CS0673
      // try the following line instead
      // System.Type t = typeof(void);
   }
}


编译器错误 CS0674

[image]

Visual C# 参考：错误和警告
编译器错误 CS0674
发送反馈意见

更新：2007 年 11 月

错误消息

不要使用“System.ParamArrayAttribute”。而是使用“params”关键字。

C# 编译器不允许使用 System..::.ParamArrayAttribute，应使用 params。

下面的示例生成 CS0674：

复制代码
// CS0674.cs
using System;
public class MyClass
{

   public static void UseParams([ParamArray] int[] list)   // CS0674
   // try the following line instead
   // public static void UseParams(params int[] list)
   {
      for ( int i = 0 ; i < list.Length ; i++ )
         Console.WriteLine(list[i]);
      Console.WriteLine();
   }

   public static void Main()
   {
      UseParams(1, 2, 3);
   }
}


编译器错误 CS0677

[image]

Visual C# 参考：错误和警告
编译器错误 CS0677
发送反馈意见

更新：2007 年 11 月

错误消息

“variable”: volatile 字段不能是“type”类型

用 volatile 关键字声明的字段必须是下列类型之一：

  • 任何引用类型

  • 任何指针类型（在 unsafe 的上下文中）

  • sbyte、byte、short、ushort、int、uint、char、float、bool 等类型

  • 基于上述任何一种类型的枚举类型

下面的示例生成 CS0677：

复制代码
// CS0677.cs
class TestClass
{
   private volatile long i;   // CS0677

   public static void Main()
   {
   }
}


编译器错误 CS0678

[image]

Visual C# 参考：错误和警告
编译器错误 CS0678
发送反馈意见

更新：2007 年 11 月

错误消息

“variable”：一个字段不能同时为 volatile 和 readonly

volatile 和 readonly 关键字的使用是互斥的。

下面的示例生成 CS0678：

复制代码
// CS0678.cs
using System;

class TestClass
{
   private readonly volatile int i;   // CS0678
   // try the following line instead
   // private volatile int i;

   public static void Main()
   {
   }
}


编译器错误 CS0681

[image]

Visual C# 参考：错误和警告
编译器错误 CS0681
示例 发送反馈意见

更新：2007 年 11 月

错误消息

修饰符“abstract”对字段无效。请尝试改用属性

您不能使字段成为抽象的。不过，您可以有访问该字段的抽象属性。

示例

下面的示例生成 CS0681：

复制代码
// CS0681.cs
// compile with: /target:library
abstract class C
{
    abstract int num;  // CS0681
}

请尝试改用以下代码：

复制代码
// CS0681b.cs
// compile with: /target:library
abstract class C
{
    public abstract int num
    {
       get;
       set;
    }
}


编译器错误 CS0682

[image]

Visual C# 参考：错误和警告
编译器错误 CS0682
发送反馈意见

更新：2007 年 11 月

错误消息

“type1”无法实现“type2”，因为该语言不支持它

当您试图实现用其他语言编写的接口，而编译器不支持该接口时，将发生此错误。


编译器错误 CS0683

[image]

Visual C# 参考：错误和警告
编译器错误 CS0683
发送反馈意见

更新：2007 年 11 月

错误消息

“explicitmethod”显式方法实现无法实现“method”，因为它是一个访问器

下面的示例生成 CS0683：

复制代码
// CS0683.cs
interface IExample
{
   int Test { get; }
}

class CExample : IExample
{
   int IExample.get_Test() { return 0; } // CS0683
   int IExample.Test { get{ return 0; } } // correct
}


编译器错误 CS0685

[image]

Visual C# 参考：错误和警告
编译器错误 CS0685
示例 发送反馈意见

更新：2007 年 11 月

错误消息

条件成员“member”不能有 out 参数

在方法上使用 ConditionalAttribute 属性时，该方法不能有 out 参数。这是因为，如果方法调用编译为 nothing，则不会定义用于 out 参数的变量的值。若要避免此错误，请将 out 参数从条件方法声明中移除，或者不使用条件属性。

示例

下面的示例生成 CS0685：

复制代码
// CS0685.cs
using System.Diagnostics;

class C
{
    [Conditional("DEBUG")]
    void trace(out int i)  // CS0685
    {
        i = 1;
    }
}


编译器错误 CS0686

[image]

Visual C# 参考：错误和警告
编译器错误 CS0686
示例 发送反馈意见

更新：2007 年 11 月

错误消息

访问符“accessor”无法实现类型“type”的接口成员“member”。使用显式接口实现。

提示：当实现一个接口时，如果其中包含的方法名称与属性或事件关联的自动生成的方法相冲突，会出现此错误。属性的 get/set 方法生成为 get_property 和 set_property，事件的 add/remove 方法生成为 add_event 和 remove_event。如果接口包含其中的任何一个方法，则发生冲突。要避免此错误，请使用显式接口实现来实现这些方法。要这样做，请将函数指定为：

复制代码
Interface.get_property() { /* */ }
Interface.set_property() { /* */ }

示例

以下示例生成 CS0686：

复制代码
// CS0686.cs
interface I
{
    int get_P();
}

class C : I
{
    public int P
    {
        get { return 1; }  // CS0686
    }
}
// But the following is valid:
class D : I
{
    int I.get_P() { return 1; }
    public static void Main() {}
}

声明事件时也会发生此错误。 事件结构自动生成 add_event 和 remove_event 方法，这可能和接口中的同名方法相冲突，如下示例所示：

复制代码
// CS0686b.cs
using System;

interface I
{
    void add_OnMyEvent(EventHandler e);
}

class C : I
{
    public event EventHandler OnMyEvent
    {
        add { }  // CS0686
        remove { }
    }
}

// Correct (using explicit interface implementation):
class D : I
{
    void I.add_OnMyEvent(EventHandler e) {}
    public static void Main() {}
}


编译器错误 CS0687

[image]

Visual C# 参考：错误和警告
编译器错误 CS0687
示例 发送反馈意见

更新：2007 年 11 月

错误消息

命名空间别名限定符“::”始终解析为类型或命名空间，因此在这里是非法的。请考虑改用“.”。

如果您使用的某些内容在意外的位置被分析器解释为类型，将出现此错误。类型或命名空间名称只有在成员访问表达式中使用成员访问 (.) 运算符时才有效。如果您在另一个上下文中使用了全局范围运算符 (::)，可能会出现此错误。

示例

下面的示例生成 CS0687：

复制代码
// CS0687.cs

using M = Test;
using System;

public class Test
{
    public static int x = 77;

    public static void Main()
    {
        Console.WriteLine(M::x); // CS0687
        // To resolve use the following line instead:
        // Console.WriteLine(M.x);
    }
}


编译器错误 CS0689

[image]

Visual C# 参考：错误和警告
编译器错误 CS0689
发送反馈意见

更新：2007 年 11 月

错误消息

不能从“identifier”派生，因为它是类型参数

泛型类的基类或接口不能由类型参数指定。请从一个特定的类或接口派生，或者从一个特定的泛型类派生，或者包含一个未知的类型作为成员。

下面的示例生成 CS0689：

复制代码
// CS0689.cs
class A<T> : T   // CS0689
{
}


编译器错误 CS0690

[image]

Visual C# 参考：错误和警告
编译器错误 CS0690
发送反馈意见

更新：2007 年 11 月

错误消息

输入文件“file”包含无效的元数据。

可以打开元数据文件，但由于某种本地化的问题，元数据已损坏。这与错误 CS0009 相似，只是此时您能够打开元数据文件。

PE (Process Executable) 是一个可执行文件。


编译器错误 CS0692

[image]

Visual C# 参考：错误和警告
编译器错误 CS0692
示例 发送反馈意见

更新：2007 年 11 月

错误消息

重复的类型参数“identifier”

同一名称不能在类型参数列表中使用一次以上。请重命名或移除重复的类型参数。

示例

下面的示例生成 CS0692：

复制代码
// CS0692.cs
// compile with: /target:library
class C <T, A, T>   // CS0692
{
}

class D <T, T>   // CS0692
{
}


编译器错误 CS0694

[image]

Visual C# 参考：错误和警告
编译器错误 CS0694
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型参数“identifier”与包含类型或方法同名

由于类型参数的名称不能与包含该类型参数的类型或方法同名，因此必须为该类型参数使用其他名称。

示例

下面的示例生成 CS0694。

复制代码
// CS0694.cs
// compile with: /target:library
class C<C> {}   // CS0694

除以上涉及泛型类的情况外，方法也可能发生此错误：

复制代码
// CS0694_2.cs
// compile with: /target:library
class A
{
   public void F<F>(F arg);   // CS0694
}


编译器错误 CS0695

[image]

Visual C# 参考：错误和警告
编译器错误 CS0695
发送反馈意见

更新：2007 年 11 月

错误消息

“generic type”无法同时实现“generic interface”和“generic interface”，因为它们对于某些类型参数替换可以统一

当一个泛型类实现同一泛型接口的多个参数化，并且存在一个会使两个接口完全相同的类型参数替换时，将出现此错误。若要避免此错误，请仅实现一个接口，或者更改类型参数以避免冲突。

下面的示例生成 CS0695：

复制代码
// CS0695.cs
// compile with: /target:library

interface I<T>
{
}

class G<T1, T2> : I<T1>, I<T2>  // CS0695
{
}


编译器错误 CS0698

[image]

Visual C# 参考：错误和警告
编译器错误 CS0698
发送反馈意见

更新：2007 年 11 月

错误消息

“class”是一个属性类，无法从它派生泛型类型

从属性类派生的任何类都是属性。属性不能是泛型类型。

下面的示例生成 CS0698：

复制代码
// CS0698.cs
class C<T> : System.Attribute  // CS0698
{
}


编译器错误 CS0699

[image]

Visual C# 参考：错误和警告
编译器错误 CS0699
发送反馈意见

更新：2007 年 11 月

错误消息

“generic”未定义类型参数“identifier”

泛型定义中使用了一个类型参数，但在该泛型的类型参数的声明列表中未找到该类型参数。如果用于该类型参数的名称不一致，则会发生这种情况。

下面的示例生成 CS0699：

复制代码
// CS0699.cs
class C<T> where U : I   // CS0699 – U is not a valid type parameter
{
}


编译器错误 CS0701

[image]

Visual C# 参考：错误和警告
编译器错误 CS0701
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“identifier”不是有效的约束。作为约束使用的类型必须是接口、非密封类或类型参数。

如果将密封类型用作约束，则会发生此错误。若要解决此错误，请仅将非密封类型用作约束。

示例

下面的示例生成 CS0701。

复制代码
// CS0701.cs
// compile with: /target:library
class C<T> where T : System.String {}   // CS0701
class D<T> where T : System.Attribute {}   // OK


编译器错误 CS0702

[image]

Visual C# 参考：错误和警告
编译器错误 CS0702
示例 发送反馈意见

更新：2007 年 11 月

错误消息

约束不能是特殊类“identifier”

下面的类型不能用作约束：System.Object、System.Array、System.Delegate、System.Enum 和 System.ValueType。

示例

下面的示例生成 CS0702：

复制代码
// CS0702.cs
class C<T> where T : System.Array  // CS0702
{
}


编译器错误 CS0703

[image]

Visual C# 参考：错误和警告
编译器错误 CS0703
发送反馈意见

更新：2007 年 11 月

错误消息

可访问性不一致：约束类型“identifier”的可访问性比“identifier”低。

一个约束不能强制泛型参数比泛型类本身的可访问性低。在下面的示例中，虽然泛型类 C<T> 被声明为 public，但约束尝试强制 T 实现内部接口。即使允许这样做，也只有具有内部访问的客户端才能为该类创建参数，因此实际上只有具有内部访问的客户端才能使用该类。

为消除此错误，请确保泛型类的访问级别的限制性不低于界限中出现的任何类或接口。

下面的示例生成 CS0703：

复制代码
// CS0703.cs
internal interface I {}
public class C<T> where T : I  // CS0703 – I is internal; C<T> is public
{
}


编译器错误 CS0704

[image]

Visual C# 参考：错误和警告
编译器错误 CS0704
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”是一个类型参数，无法在其中执行成员查找

无法通过类型参数指定内部类型。请尝试显式使用所需的类型。

示例

下面的示例生成 CS0704：

复制代码
// CS0704.cs
class B
{
    public class I { }
}

class C<T> where T : B
{
    T.I f;  // CS0704 – member lookup on type parameter
    // Try this instead:
    // B.I f;
}

class CMain
{
    public static void Main() {}
}


编译器错误 CS0706

[image]

Visual C# 参考：错误和警告
编译器错误 CS0706
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无效的约束类型。作为约束使用的类型必须是接口、非密封类或类型参数。

当在约束子句中使用无效的构造时，将出现此错误。若要避免此错误，请使用接口或非密封类取代导致此错误的构造。

示例

下面的示例生成 CS0706。

复制代码
// CS0706.cs
// compile with: /target:library
class A {}
class C<T> where T : int[] {}  // CS0706
class D<T> where T : A {}  // OK


编译器错误 CS0708

[image]

Visual C# 参考：错误和警告
编译器错误 CS0708
发送反馈意见

更新：2007 年 11 月

错误消息

“field”：不能在静态类中声明实例成员

如果在声明为静态的类中声明非静态成员，则会发生此错误。不可能创建静态类的实例，因此实例变量没有意义。static 关键字应该应用于静态类的所有成员。

下面的示例生成 CS0708：

复制代码
// CS0708.cs
// compile with: /target:library
public static class C
{
   int i;  // CS0708
   static int j;  // OK
}


编译器错误 CS0709

[image]

Visual C# 参考：错误和警告
编译器错误 CS0709
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“derived class”: 无法从静态类“base class”派生

无法实例化静态类或从其派生。即，静态类不能作为任何其他类的基类。

示例

下面的示例生成 CS0709。

复制代码
// CS0709.cs
// compile with: /target:library
public static class Base {}
public class Derived : Base {}   // CS0709


编译器错误 CS0710

[image]

Visual C# 参考：错误和警告
编译器错误 CS0710
发送反馈意见

更新：2007 年 11 月

错误消息

静态类不能有实例构造函数

静态类无法实例化，因此它不需要构造函数。若要避免此错误，请从静态类中移除所有构造函数，或者如果您确实希望构造实例，请将类设为非静态类。

下面的示例生成 CS0710：

复制代码
// CS0710.cs
public static class C
{
   public C()  // CS0710
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0711

[image]

Visual C# 参考：错误和警告
编译器错误 CS0711
发送反馈意见

更新：2007 年 11 月

错误消息

静态类不能包含析构函数

静态类不能实例化，因为它不需要构造函数或析构函数。若要避免此错误，请从静态类中移除任何析构函数，或者，如果您确实希望构造并销毁实例，请使类成为非静态。

下面的示例生成 CS0711：

复制代码
// CS0711.cs
public static class C
{
   ~C()  // CS0711
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0712

[image]

Visual C# 参考：错误和警告
编译器错误 CS0712
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法创建静态类“static class”的实例

不可能创建静态类的实例。静态类被设计为包含静态字段和方法，但不能实例化。

示例

下面的示例生成 CS0712：

复制代码
// CS0712.cs
public static class SC
{
}

public class CMain
{
    public static void Main()
    {
        SC sc = new SC();  // CS0712
    }
}


编译器错误 CS0713

[image]

Visual C# 参考：错误和警告
编译器错误 CS0713
发送反馈意见

更新：2007 年 11 月

错误消息

静态类“static type”不能从类型“type”派生。静态类必须从对象派生。

如果允许这样做，静态类将从基类继承方法和非静态成员，这样它就不是静态的了。因此不允许这样做。

下面的示例生成 CS0713：

复制代码
// CS0713.cs
public class Base
{
}

public static class Derived : Base  // CS0713
{
}

public class CMain
{
   public static void Main()
   {
   }
}


编译器错误 CS0714

[image]

Visual C# 参考：错误和警告
编译器错误 CS0714
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“static type”：静态类不能实现接口

接口可能在对象上定义非静态方法，因此不能由静态类实现。若要解决此错误，请确信您的类没有试图实现任何接口。

示例

下面的示例生成 CS0714：

复制代码
// CS0714.cs
interface I
{
}

public static class C : I  // CS0714
{
    public static void Main()
    {
    }
}


编译器错误 CS0715

[image]

Visual C# 参考：错误和警告
编译器错误 CS0715
发送反馈意见

更新：2007 年 11 月

错误消息

“static class”：静态类不能包含用户定义的运算符

用户定义的运算符作用于类的实例。静态类无法实例化，所以不可能创建运算符作用于的实例。因此，不允许对静态类使用用户定义的运算符。

下面的示例生成 CS0715：

复制代码
// CS0715.cs
public static class C
{
   public static C operator+(C c)  // CS0715
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0716

[image]

Visual C# 参考：错误和警告
编译器错误 CS0716
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法转换为静态类型“type”

如果您的代码使用强制转换来转换为静态类型，则会发生此错误。由于一个对象不可能成为静态类型的实例，因此强制转换为静态类型永远没有意义。

示例

下面的示例生成 CS0716：

复制代码
// CS0716.cs

public static class SC
{
    static void F() { }
}

public class Test
{
    public static void Main()
    {
        object o = new object();
        System.Console.WriteLine((SC)o);  // CS0716
    }
}


编译器错误 CS0717

[image]

Visual C# 参考：错误和警告
编译器错误 CS0717
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“static class”：静态类不能用作约束

静态类仅包含静态成员而不包含实例成员，因此不能扩展。由于静态类不能扩展，这使它们作为类型参数和约束时没有意义，因为不存在可以实现静态类专用化的类型。

示例

下面的示例生成 CS0717：

复制代码
// CS0717.cs

public static class SC
{
    public static void F()
    {
    }
}

public class G<T> where T : SC  // CS0717
{
    public static void Main()
    {
    }
}


编译器错误 CS0718

[image]

Visual C# 参考：错误和警告
编译器错误 CS0718
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”: 静态类型不能用作类型参数

静态类型无法实例化，因此不能用作泛型参数。若要解决此错误，请从泛型参数中移除静态类型。

示例

下面的示例生成 CS0718：

复制代码
// CS0718.cs
public static class SC
{
    public static void F()
    {
    }
}

public class G<T>
{
}

public class CMain
{
    public static void Main()
    {
        G<SC> gsc = new G<SC>();  // CS0718
    }
}


编译器错误 CS0719

[image]

Visual C# 参考：错误和警告
编译器错误 CS0719
发送反馈意见

更新：2007 年 11 月

错误消息

“type”：数组元素不能是静态类型

静态类型的数组没有意义，因为数组元素是实例，而不可能创建静态类型的实例。

下面的示例生成 CS0719：

复制代码
// CS0719.cs
public static class SC
{
   public static void F()
   {
   }
}

public class CMain
{
   public static void Main()
   {
      SC[] sca = new SC[10];  // CS0719
   }
}


编译器错误 CS0720

[image]

Visual C# 参考：错误和警告
编译器错误 CS0720
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“static class”：不能在静态类中声明索引器

索引器在静态类中没有意义，因为它们只能和实例一起使用，而且不可能创建静态类型的实例。

示例

下面的示例生成 CS0720：

复制代码
// CS0720.cs

public static class Test
{
    public int this[int index]  // CS0720
    {
        get { return 1; }
        set {}
    }

    static void Main() {}
}


编译器错误 CS0721

[image]

Visual C# 参考：错误和警告
编译器错误 CS0721
发送反馈意见

更新：2007 年 11 月

错误消息

“type”：静态类型不能用作参数

静态类型作为参数没有意义。由于不能创建静态类型的实例，所以没有实例可以作为参数传递。

下面的示例生成 CS0721：

复制代码
// CS0721.cs
public static class SC
{
}

public class CMain
{
   public void F(SC sc)  // CS0721
   {
   }

   public static void Main()
   {
   }
}


编译器错误 CS0722

[image]

Visual C# 参考：错误和警告
编译器错误 CS0722
发送反馈意见

更新：2007 年 11 月

错误消息

“type”：静态类型不能用作返回类型

由于无法创建静态类型的实例，所以将静态类型用作返回类型没有意义。

下面的示例生成 CS0722：

复制代码
// CS0722.cs
public static class SC
{
}

public class CMain
{
   public SC F()  // CS0722
   {
      return null;
   }

   public static void Main()
   {
   }
}


编译器错误 CS0723

[image]

Visual C# 参考：错误和警告
编译器错误 CS0723
发送反馈意见

更新：2007 年 11 月

错误消息

无法声明静态类型“type”的变量

无法创建静态类型的实例。

下面的示例生成 CS0723：

复制代码
// CS0723.cs
public static class SC
{
}

public class CMain
{
   public static void Main()
   {
      SC sc = null;  // CS0723
   }
}


编译器错误 CS0724

[image]

Visual C# 参考：错误和警告
编译器错误 CS0724
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于程序集没有 CLSCompliant 属性，因此不需要 CLSCompliant 属性

下面的示例生成 CS0724，原因是 throw 语句在 finally 子句块内。

示例

下面的示例生成 CS0724。

复制代码
// CS0724.cs
using System;

class X
{
    static void Test()
    {
        try
        {
            throw new Exception();
        }
        catch
        {
            try
            {
            }
            finally
            {
                throw; // CS0724
            }
        }
    }

    static void Main()
    {
    }
}


编译器错误 CS0726

[image]

Visual C# 参考：错误和警告
编译器错误 CS0726
发送反馈意见

更新：2007 年 11 月

错误消息

“format specifier”不是有效的格式说明符

在调试器中发生此错误。当您向某个调试器窗口键入变量名称时，您可以在其后跟一个逗号，然后再跟一个格式说明符。例如：myInt, h 或 myString,nq。当编译器不识别 C# 中的格式说明符 时，会发生此错误。


编译器错误 CS0727

[image]

Visual C# 参考：错误和警告
编译器错误 CS0727
发送反馈意见

更新：2007 年 11 月

错误消息

无效的格式说明符

在调试器中发生此错误。当您向某个调试器窗口键入变量名称时，您可以在其后跟一个逗号，然后再跟一个格式说明符。例如：myInt, h；或 myString,nq。当编译器完全无法分析您所键入的内容时会出现此错误。要解决此错误，请重新键入变量名称，您可以选择在其后跟一个逗号和有效的格式说明符。


编译器错误 CS0729

[image]

Visual C# 参考：错误和警告
编译器错误 CS0729
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型“type”在此程序集中定义，但为其指定了类型转发器

不能对在同一程序集中定义的类型使用类型转发器。

示例

下面的示例生成 CS0729。

复制代码
// CS0729.cs
// compile with: /target:library
using System.Runtime.CompilerServices;
[assembly:TypeForwardedTo(typeof(TestClass))]   // CS0729
class TestClass {}


编译器错误 CS0730

[image]

Visual C# 参考：错误和警告
编译器错误 CS0730
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于类型“type”是“type”的嵌套类型，因此无法转发该类型

尝试转发嵌套类时会生成此错误。

示例

下面的示例生成 CS0730。它由两个源文件组成。首先，编译库文件 CS0730a.cs，然后编译引用该库文件的文件 CS0730.cs。

复制代码
// CS0730a.cs
// compile with: /t:library
public class Outer
{
   public class Nested {}
}
复制代码
// CS0730.cs
// compile with: /t:library /r:CS0730a.dll
using System.Runtime.CompilerServices;

[assembly:TypeForwardedToAttribute(typeof(Outer.Nested))]   // CS0730

[assembly:TypeForwardedToAttribute(typeof(Outer))]   // OK


编译器错误 CS0731

[image]

Visual C# 参考：错误和警告
编译器错误 CS0731
示例 发送反馈意见

更新：2007 年 11 月

错误消息

程序集“assembly”中类型“type”的类型转发器导致循环

仅当导入的源数据格式错误时才会发生此错误。仅使用 C# 源代码不会出现此错误。

示例

下面的示例生成 CS0731。此示例由三个文件组成：

1. Circular.IL

2. Circular2.IL

3. CS0731.cs

首先将 .IL 文件编译为库，然后编译引用这两个文件的 .cs 代码。

复制代码
// Circular.il
// compile with: /DLL /out=Circular.dll
.assembly extern circular2
{
  .ver 0:0:0:0
}
.assembly extern circular3
{
  .ver 0:0:0:0
}
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )   // .z\V.4..
  .ver 2:0:0:0
}
.assembly Circular
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.class extern forwarder Circular.Referenced.TypeForwarder
{
  .assembly extern circular2
}
.module Circular.dll
// MVID: {880C2329-C915-42A0-83E9-9D10C3E6DBD0}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x04E40000
// ======== CLASS MEMBERS DECLARATION =========
.class public abstract auto ansi sealed beforefieldinit User
       extends [mscorlib]System.Object
{
  .method public hidebysig static class [circular2]Circular.Referenced.TypeForwarder
          F() cil managed
  {
    .maxstack  1
    newobj     instance void [circular2]Circular.Referenced.TypeForwarder::.ctor()
    ret
  }
}
复制代码
// Circular2.il
// compile with: /DLL /out=Circular2.dll
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )   // .z\V.4..
  .ver 2:0:0:0
}
.assembly extern Circular
{
  .ver 0:0:0:0
}
.assembly circular2
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.class extern forwarder Circular.Referenced.TypeForwarder
{
  .assembly extern Circular
}
.module circular2.dll
// MVID: {8B3BE5C8-DBE1-49C4-BC72-DF35F0387C21}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       //   WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x04E40000
复制代码
// CS0731.cs
// compile with: /reference:circular.dll /reference:circular2.dll
// CS0731 expected
class A {
   public static void Main() {
      User.F();
   }
}


编译器错误 CS0733

[image]

Visual C# 参考：错误和警告
编译器错误 CS0733
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法转发泛型类型“GenericType<>”

示例

下面的示例生成 CS0733。将第一个文件编译为库，然后在编译第二个文件时引用它。

复制代码
// CS0733a.cs
// compile with: /target:library
public class GenericType<T>
{
}
复制代码
// CS0733.cs
// compile with: /target:library /r:CS0733a.dll
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(GenericType<int>))]   // CS0733


编译器错误 CS0734

[image]

Visual C# 参考：错误和警告
编译器错误 CS0734
示例 发送反馈意见

更新：2007 年 11 月

错误消息

仅在生成“module”的目标类型时才可以指定 /moduleassemblyname 选项

仅在生成 .netmodule 时才可以使用编译器选项 /moduleassemblyname。有关更多信息，请参见 /moduleassemblyname（指定模块的友元程序集）（C# 编译器选项）。

有关生成 .netmodule 的更多信息，请参见 /target:module（创建模块以添加到程序集）（C# 编译器选项）。

示例

下面的示例生成 CS0734。若要解决此问题，请将 /target:module 添加到编译中。

复制代码
// CS0734.cs
// compile with: /moduleassemblyname:A
// CS0734 expected
public class Test {}


编译器错误 CS0735

[image]

Visual C# 参考：错误和警告
编译器错误 CS0735
发送反馈意见

更新：2007 年 11 月

错误消息

指定为 TypeForwardedTo 属性的参数的类型无效

下面的示例生成 CS0735。

复制代码
// CS735.cs
// compile with: /target:library
using System.Runtime.CompilerServices;
[assembly:TypeForwardedTo(typeof(int[]))]   // CS0735
[assembly:TypeForwardedTo(typeof(string))]   // OK


编译器错误 CS0736

[image]

Visual C# 参考：错误和警告
编译器错误 CS0736
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“type name”不实现接口成员“member name”。“method name”无法实现接口成员，因为它是静态的。

在将静态方法隐式或显式声明为接口成员的实现时，会生成此错误。

更正此错误

  • 从方法声明中移除 static 修饰符。

  • 更改接口方法的名称。

  • 重新定义包含类型，使其不从接口继承。

示例

下面的代码生成 CS0736，因为 Program.testMethod 被声明为静态的：

复制代码
// cs0736.cs
namespace CS0736
{

    interface ITest
    {
        int testMethod(int x);
    }

    class Program : ITest // CS0736
    {
        public static int testMethod(int x) { return 0; }
        // Try the following line instead.
        // public int testMethod(int x) { return 0; }
        public static void Main() { }
    }
}

请参见

参考

接口（C# 编程指南）


编译器错误 CS0737

[image]

Visual C# 参考：错误和警告
编译器错误 CS0737
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“type name”不实现接口成员“member name”。“method name”无法实现接口成员，因为它不是公共的。

实现接口成员的方法必须具有公共可访问性。所有接口成员均为 public。

更正此错误

  • 向该方法添加 public 访问修饰符。

示例

下面的代码生成 CS0737：

复制代码
// cs0737.cs
interface ITest
{
    int Return42();
    // Try the following line instead.
    // public int Return42();
}

struct Struct1 : ITest // CS0737
{
    int Return42() { return (42); }
}

public class Test
{
    public static int Main(string[] args)
    {
        Struct1 s1 = new Struct1();

        return (1);
    }

}

请参见

参考

接口（C# 编程指南）


编译器错误 CS0738

[image]

Visual C# 参考：错误和警告
编译器错误 CS0738
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“type name”不实现接口成员“member name”。“method name”无法实现“interface member”，因为它没有匹配的返回类型“type name”。

类中的实现方法的返回值必须与该方法所实现的接口成员的返回值相匹配。

更正此错误

  • 更改方法的返回类型，以便与接口成员的返回类型相匹配。

示例

下面的代码生成 CS0738，因为类方法返回 void，而同名的接口成员却返回 int：

复制代码
using System;

interface ITest
{
    int TestMethod();
}
public class Test: ITest
{
    public void TestMethod() { } // CS0738
    // Try the following line instead.
    // public int TestMethod();
}

请参见

参考

接口（C# 编程指南）


编译器错误 CS0739

[image]

Visual C# 参考：错误和警告
编译器错误 CS0739
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“type name”与 TypeForwardedToAttribute 重复。

对于某个外部类型，程序集只能有一个 TypeForwardedToAttribute。

更正此错误

  • 找到并移除重复的 TypeForwardedToAttribute。

示例

下面的代码生成 CS0739：

复制代码
// CS0739.cs
// CS0739
// Assume that a class Test is declared in a separate dll
// with a namespace that is named cs739dll.
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(cs739dll.Test))]
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(cs739dll.Test))]
namespace cs0739
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

请参见

参考

TypeForwardedToAttribute


编译器错误 CS0742

[image]

Visual C# 参考：错误和警告
编译器错误 CS0742
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

查询正文必须以 select 或 group 子句结尾

查询表达式必须以不包含继续符的 select 子句或 group 子句结尾。

更正此错误

  • 向查询中添加一个 select 子句或 group 子句。

示例

下面的代码生成 CS0742：

复制代码
// cs0742.cs
using System.Linq;
public class Test
{
    public static int Main()
    {
        int[] array = { 1, 2, 3 };
        var query = from num in array; // CS0742
        return 1;
    }
}

如果 group 子句使用 into 关键字将分组结果存储到临时标识符，则它不能用作查询中的最后一个子句。仍需要使用 select 或另一个 group 子句。

请参见

概念

LINQ 查询表达式（C# 编程指南）


编译器错误 CS0743

[image]

Visual C# 参考：错误和警告
编译器错误 CS0743
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

需要上下文关键字“on”

join 子句的格式为 join...in...on...equals，如下面的示例所示：

复制代码
var query = from x in array1
            join y in array2 on x equals y
            select x;

更正此错误

  • 将 on 关键字添加到 join 子句中。

示例

下面的代码生成 CS0743：

复制代码
// cs0743.cs
using System;
using System.Linq;

public class C
{
    public static int Main()
    {
        int[] array1 = { 1, 2, 3 ,4, 5, 6,};
        int[] array2 = { 5, 6, 7, 8, 9 };
        var c = from x in array1
                join y in array2 x equals y // CS0743
                select x;
        return 1;
    }
}

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

join 子句（C# 参考）


编译器错误 CS0744

[image]

Visual C# 参考：错误和警告
编译器错误 CS0744
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

需要上下文关键字“equals”

join 子句的格式为 join...in...on...equals，如下面的示例所示：

复制代码
var query = from x in array1
            join y in array2 on x equals y
            select x;

更正此错误

  • 将 equals 关键字添加到 join 子句中。

示例

下面的代码生成 CS0744：

复制代码
// cs0744.cs
using System;
using System.Linq;

public class C
{
    public static int Main()
    {
        int[] array1 = { 1, 2, 3 ,4, 5, 6,};
        int[] array2 = { 5, 6, 7, 8, 9 };
        var c = from x in array1
                join y in array2 on x y // CS0744
                select x;
        return 1;
    }
}

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

join 子句（C# 参考）


编译器错误 CS0745

[image]

Visual C# 参考：错误和警告
编译器错误 CS0745
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

需要上下文关键字“by”

group 子句的格式为 group...by 后跟一个可选的 into，如下面的示例所示：

复制代码
string[] names = { "Bob", "Bill", "Jonetta", "Mary" };

var query = from name in names
            group name by name[0];

或者

复制代码
var query2 = from name in names
             group name by name[0] into g
             //...additional query clauses

更正此错误

  • 将 by 关键字添加到 group 子句中。

示例

下面的代码生成 CS0745：

复制代码
// cs0745.cs
using System;
using System.Linq;

public class C
{
    public static int Main()
    {
        string[] names = { "Bob", "Bill", "Jonetta", "Mary" };

        var query = from name in names
                    group name name[0]; // CS0745

        return 1;
    }
}

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

group 子句（C# 参考）


编译器错误 CS0746

[image]

Visual C# 参考：错误和警告
编译器错误 CS0746
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无效的匿名类型成员声明符。匿名类型成员必须使用成员赋值、简单名称或成员访问来声明。

匿名类型必须使用成员赋值、简单名称或成员访问来声明。

更正此错误

  • 请确保声明仅使用成员赋值、简单名称或成员访问表达式。

示例

下面的代码在使用 incorrect_1 和 incorrect_2 的声明时生成 CS0746。下面的声明显示了两种用于声明匿名类型的正确方法。

复制代码
// cs0746.cs
public class C
{
    public static int Main()
    {
        int i = 100;
        string s = "Bottles of beer.";

        var incorrect_1 = new { a.b = 1 }; // CS0746
        var incorrect_2 = new {100, "Bottles of beer."}; // CS0746
        var correct_1 = new { i, s }; //OK
        var correct_2 = new {num = i, message = s}; // OK

        return 1;
    }
}

请参见

参考

匿名类型（C# 编程指南）


编译器错误 CS0747

[image]

Visual C# 参考：错误和警告
编译器错误 CS0747
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

初始值设定项成员声明符无效。

对象初始值设定项用于为属性或字段赋值。不是属性或字段赋值表达式的任意表达式都会生成编译时错误。

更正此错误

  • 请确保初始值设定项中的所有表达式都是该类型的属性或字段的赋值表达式。在下面的示例中，第二个表达式就是一个错误，因为没有为 List<int> 的任意属性或字段赋予值 1。

示例

下面的代码生成 CS0747：

复制代码
// cs0747.cs
using System.Collections.Generic;

public class C
{
    public static int Main()
    {
        var t = new List<int> { Capacity = 2, 1 }; // CS0747
        return 1;
    }
}

请参见

参考

对象和集合初始值设定项（C# 编程指南）


编译器错误 CS0748

[image]

Visual C# 参考：错误和警告
编译器错误 CS0748
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

lambda 参数的用法不一致；参数类型必须全部为显式或全部为隐式。

如果 lambda 表达式有多个输入参数，则某些参数在其他参数使用显式类型时不能使用隐式类型。

更正此错误

  • 为所有输入参数提供隐式类型或显式类型。

示例

下面的代码将生成 CS0748，因为在 lambda 表达式中，只为 alpha 提供了显式类型：

复制代码
// cs0748.cs
class CS0748
{
    delegate double D(int x, int y);
    D d = (int alpha, beta) => { return beta / alpha; }; // CS0748
}

请参见

参考

Lambda 表达式（C# 编程指南）


编译器错误 CS0750

[image]

Visual C# 参考：错误和警告
编译器错误 CS0750
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

分部方法不能具有访问修饰符或 virtual、abstract、override、new、sealed 或 extern 修饰符。

由于分部方法具有特殊行为，因此其可接受的修饰符是受限制的。

更正此错误

  • 从方法声明中移除未经授权的修饰符。

示例

下面的示例生成 CS0750：

复制代码
// cs0750.cs
using System;

public class Base
{
    protected virtual void PartG()
    {
    }

    protected void PartH()
    {
    }
    protected virtual void PartI()
    {
    }
}

public partial class C:Base
{
    // All these partial method declarations
    // will generate CS0750.
    public partial void PartA();
    private partial void PartB();
    protected partial void PartC();
    internal partial void PartD();
    virtual partial void PartE();
    abstract partial void PartF();
    override partial void PartG();
    new partial void PartH();
    sealed override partial void PartI();
    extern partial void PartJ();

    public static int Main()
    {
        return 1;
    }
}

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0751

[image]

Visual C# 参考：错误和警告
编译器错误 CS0751
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

分部方法必须在分部类或分部结构内声明

只有当分部方法封装在分部类或分部结构中时，才能声明该方法。

更正此错误

  • 从该方法中移除 partial 修饰符并提供一个实现，或者将 partial 修饰符添加到封闭类或结构中。

示例

下面的示例生成 CS0751：

复制代码
// cs0751.cs
using System;

public class C
{
    partial void Part(); // CS0751
    public static int Main()
    {
        return 1;
    }
}

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0752

[image]

Visual C# 参考：错误和警告
编译器错误 CS0752
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

分部方法不能有 out 参数

分部方法不能有 out 参数。不允许有 out 参数是因为，如果分部方法被编译器移除，就无法保证会为 out 参数赋值。

更正此错误

  • 从参数中移除 out 修饰符并改用方法的返回值，或从方法声明中移除 partial 修饰符。

示例

下面的代码生成 CS0752：

复制代码
// cs0752.cs
public partial class C
{
    partial void Part(out int num); // CS0752
    // try the following line instead
    // partial void Part(int num);

    public static int Main()
    {
        return 1;
    }
}

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0753

[image]

Visual C# 参考：错误和警告
编译器错误 CS0753
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

只有方法、类、结构或接口可以是分部的。

partial 修饰符只能用于类、结构、接口和方法。

更正此错误

  • 从变量或语言构造中移除 partial 修饰符。

示例

下面的代码生成 CS0753：

复制代码
// cs0753.cs
using System;

    public partial class C
    {
        partial int num; // CS0753
        public static int Main()
        {
            return 1;
        }
    }

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0754

[image]

Visual C# 参考：错误和警告
编译器错误 CS0754
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

分部方法不能显式实现接口方法。

分部方法不能声明为接口中定义的方法的显式实现。

更正此错误

  • 从方法声明中移除显式接口限定。

示例

下面的代码生成 CS0754：

复制代码
// cs0754.cs
using System;

    public interface IF
    {
        void Part();
    }

    public partial class C : IF
    {
        partial void IF.Part(); //CS0754
        public static int Main()
        {
            return 1;
        }
    }

请参见

参考

显式接口实现（C# 编程指南）
分部类和方法（C# 编程指南）


编译器错误 CS0755

[image]

Visual C# 参考：错误和警告
编译器错误 CS0755
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

两个分部方法声明都必须是扩展方法，或者都不能是扩展方法。

分部方法由定义声明（签名）和可选的实现声明（体）组成。如果定义声明为扩展方法，则实现声明（如果定义）也必须为扩展方法。同样，如果定义方法不是扩展方法，则实现方法也不能为扩展方法。

更正此错误

  • 从其中一部分中移除 this 修饰符，或将该修饰符添加到另一部分中。

示例

下面的示例生成 CS0755：

复制代码
// cs0755.cs
    public static partial class Ext
    {
        static partial void Part(this C c); //Extension method

        // Typically the implementing declaration is in a separate file.
        static partial void Part(C c) //CS0755
        {
        }
    }

    public partial class C
    {
        public static int Main()
        {
            return 1;
        }
    }

请参见

参考

扩展方法（C# 编程指南）


编译器错误 CS0756

[image]

Visual C# 参考：错误和警告
编译器错误 CS0756
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

分部方法不能有多个定义声明。

分部方法的定义声明是指定方法签名的部分，而不是指定实现（方法体）的部分。分部方法的每个唯一签名都必须具有且仅具有一个定义声明。分部方法的每个重载版本都必须具有其自己的定义声明。

更正此错误

  • 从分部方法中移除所有多余的定义声明，只保留一个定义声明。

示例

复制代码
// cs0756.cs
using System;

    public partial class C
    {
        partial void Part();
        partial void Part(); // CS0756
        public static int Main()
        {
            return 1;
        }
    }

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0757

[image]

Visual C# 参考：错误和警告
编译器错误 CS0757
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

分部方法不能有多个实现声明。

分部方法必须包含一个（且仅限一个）定义声明（签名），并可包含一个或零个实现声明（体）。同一定义声明不允许有多个实现声明。分部方法可以重载，并且每个重载版本都可以有一个或零个实现方法。

更正此错误

  • 从分部方法中移除所有多余的实现声明，只保留一个实现声明。

示例

下面的示例生成 CS0757：

复制代码
// cs0757.cs
using System;

    public partial class C
    {
        // Defining declaration.
        partial void Part();

        // Implementing declaration.
        partial void Part()
        {
            //...Do something.
        }

        // Second implementing declaration.
        partial void Part() // CS0757
        {
            //...Do something.
        }

        public static int Main()
        {
            return 1;
        }
    }

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0758

[image]

Visual C# 参考：错误和警告
编译器错误 CS0758
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

两个分部方法声明都必须使用 params 参数，或者都不能使用 params 参数

如果分部方法的一部分指定了 params 参数，则另一部分也必须指定该参数。

更正此错误

  • 在该方法的一部分中添加 params 修饰符，或从另一部分中移除该修饰符。

示例

下面的代码生成 CS0758：

复制代码
using System;

    public partial class C
    {
        partial void Part(int i, params char[] array);
        partial void Part(int i, char[] array) // CS0758
        {
        }

        public static int Main()
        {
            return 1;
        }

    }

请参见

参考

分部类和方法（C# 编程指南）
params（C# 参考）


编译器错误 CS0759

[image]

Visual C# 参考：错误和警告
编译器错误 CS0759
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

未找到用于实现分部方法“method”的声明的定义声明。

分部方法必须具有定义该方法的签名（名称、返回类型和参数）的定义声明。实现体或方法体是可选的。

更正此错误

  • 在分部类或结构的其他部分中为分部方法提供定义声明。

示例

下面的示例生成 CS0759：

复制代码
// cs0759.cs
using System;

    public partial class C
    {
        partial void Part() // CS0759
        {
        }

        public static int Main()
        {
            return 1;
        }
    }

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0761

[image]

Visual C# 参考：错误和警告
编译器错误 CS0761
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“method<T>”的分部方法声明具有不一致的类型参数约束。

如果分部方法具有实现，则泛型类型约束必须与该方法签名上定义的约束相同。

更正此错误

  • 使分部方法的每个部分的泛型类型约束都相同。

示例

下面的代码生成 CS0761：

复制代码
// cs0761.cs
using System;

public partial class C
{
    partial void Part<T>() where T : class;
    partial void Part<T>() where T : struct // CS0761
    {
    }

    public static int Main()
    {
        return 1;
    }
}

请参见

参考

分部类和方法（C# 编程指南）
类型参数的约束（C# 编程指南）


编译器错误 CS0762

[image]

Visual C# 参考：错误和警告
编译器错误 CS0762
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法通过方法“method”创建委托，因为该方法是没有实现声明的分部方法

分部方法不要求具有实现声明。但委托要求其封装方法具有实现。

更正此错误

  • 为用于初始化委托的方法提供一个实现。

示例

复制代码
public delegate void TestDel();

    public partial class C
    {
        partial void Part();

        public static int Main()
        {
            C c = new C();
            TestDel td = new TestDel(c.Part); // CS0762
            return 1;
        }

    }


编译器错误 CS0763

[image]

Visual C# 参考：错误和警告
编译器错误 CS0763
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

两个分部方法声明都必须是静态的，或者都不能是静态的。

在分部方法声明中，不能有一个部分是静态的，而另一个部分是非静态的。

更正此错误

  • 使两个部分都是静态的或都是非静态的。

示例

下面的代码生成 CS0763：

复制代码
// cs0763.cs
using System;

    public partial class C
    {
        static partial void Part();
        partial void Part() // CS0763
        {
        }

        public static int Main()
        {
            return 1;
        }

    }

请参见

参考

分部类和方法（C# 编程指南）
static（C# 参考）


编译器错误 CS0764

[image]

Visual C# 参考：错误和警告
编译器错误 CS0764
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

两个分部方法声明都必须是不安全的，或者都不能是不安全的

分部方法由定义声明（签名）和可选的实现声明（体）组成。如果定义声明具有 unsafe 修饰符，则实现声明也必须具有该修饰符。相反，如果实现声明具有 unsafe 修饰符，定义声明也必须具有该修饰符。

更正此错误

  • 如果定义声明是正确的，则在实现声明中相应地添加或移除 unsafe 修饰符以匹配定义声明。

示例

复制代码
// cs0764.cs
//  Compile with: /target:library /unsafe
public partial class C
{
    partial void Part();
    unsafe partial void Part() //CS0764
    {
    }

    public static int Main()
    {
        return 1;
    }
}

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0765

[image]

Visual C# 参考：错误和警告
编译器错误 CS0765
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

不能在表达式目录树中使用只有定义声明的分部方法或已移除的条件方法

虽然对已移除的分部方法的调用是一个表达式，但它是表达式目录树中不可接受的表达式。

更正此错误

  • 为分部方法添加实现声明，或移除导致条件方法无法编译的代码。

示例

下面的代码在两个位置生成 CS0765：

复制代码
// cs0765.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

public delegate void dele();

public class ConClass
{
    [Conditional("CONDITION")]
    public static void TestMethod() { }
}

public partial class PartClass : IEnumerable
{
    List<object> list = new List<object>();

    partial void Add(int x);

    public IEnumerator GetEnumerator()
    {
        for (int i = 0; i < list.Count; i++)
            yield return list[i];
    }

    static void Main()
    {
        Expression<Func<PartClass>> testExpr1 = () => new PartClass { 1, 2 }; // CS0765
        Expression<dele> testExpr2 = () => ConClass.TestMethod(); // CS0765
    }
}

请参见

概念

表达式目录树

参考

分部类和方法（C# 编程指南）


编译器错误 CS0766

[image]

Visual C# 参考：错误和警告
编译器错误 CS0766
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

分部方法必须具有 void 返回类型。

分部方法不能返回值。其返回类型必须为 void。

更正此错误

  • 为分部方法指定 void 返回类型，或将该方法转换为常规（非分部）方法。

示例

下面的示例生成 CS0766：

复制代码
// cs0766.cs
using System;

    public partial class C
    {
        partial int Part(); // CS0766

        // Typically the implementing declaration
        // is contained in a separate file.
        partial int Part() //CS0766
        {
        }

        public static int Main()
        {
            return 1;
        }

    }

请参见

参考

分部类和方法（C# 编程指南）


编译器错误 CS0811

[image]

Visual C# 参考：错误和警告
编译器错误 CS0811
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“name”的完全限定名对于调试信息来说过长。请在不使用“/debug”选项的情况下进行编译。

调试信息中的变量和类型名称具有大小约束。

更正此错误

  • 如果不能修改该名称，则唯一的替代方法是在不使用 /debug 选项的情况下进行编译。

示例

下面的代码生成 CS0811：

复制代码
// cs0811.cs
//Compile with: /debug
using System;
using System.Collections.Generic;

namespace TestNamespace
{
    using Long = List<List<List<List<List<List<List<List<List<List<List<List<List
   <List<List<List<List<List<List<List<List<List<List<List<List<List<List<List<int>>>>>>>>>>>>>>>>>>>>>>>>>>>>; // CS0811

    class Test
    {
        static int Main()
        {
            return 1;
        }
    }
}


编译器错误 CS0815

[image]

Visual C# 参考：错误和警告
编译器错误 CS0815
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法将“expression”赋予隐式类型的局部变量

如果表达式用作隐式类型的变量的初始值设定项，则必须具有类型。由于匿名函数表达式、方法组表达式和 null 文本表达式不具有类型，因此它们不是正确的初始值设定项。对于隐式类型的变量，不能在其声明中用 null 值进行初始化，但可在以后为其赋予 null 值。

更正此错误

  • 为该变量提供显式类型。

示例

下面的代码生成 CS0815：

复制代码
// cs0815.cs
class Test
{
    public static int Main()
    {
        var d = s => -1; // CS0815
        var e = (string s) => 0; // CS0815
        var p = null;//CS0815
        var del = delegate(string a) { return -1; };// CS0815
        return -1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0818

[image]

Visual C# 参考：错误和警告
编译器错误 CS0818
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

必须初始化隐式类型的局部变量

在声明隐式类型的局部变量的同时，必须用值对该变量进行初始化。

更正此错误

  • 为该变量赋值或为其指定显式类型。

示例

下面的代码生成 CS0818：

复制代码
// cs0818.cs
class A
{
    public static int Main()
    {
        var a; // CS0818
        return -1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0819

[image]

Visual C# 参考：错误和警告
编译器错误 CS0819
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

隐式类型的局部变量不能有多个声明符。

显式类型的声明中可以有多个声明符，但隐式类型的变量不能有多个声明符。

更正此错误

  • 在各自单独的行上声明每个隐式类型的局部变量并为其赋值。

示例

下面的代码生成 CS0819：

复制代码
// cs0819.cs
class A
{
    public static int Main()
    {
        var a = 3, b = 2; // CS0819
        return -1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0820

[image]

Visual C# 参考：错误和警告
编译器错误 CS0820
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法将数组初始值设定项赋值给隐式类型的局部变量

隐式类型的数组是由编译器推断其元素类型的数组。该数组必须使用 new[] 修饰符进行初始化，如下面的示例所示。

更正此错误

  • 对数组初始值设定项使用 new[] 修饰符。

  • 不要使用隐式类型的局部变量。

示例

下面的代码生成 CS0820，并演示如何正确初始化隐式类型的数组：

复制代码
//cs0820.cs
class G
{
    public static int Main()
    {

        var a = { 1,2,3}; //CS0820
        // Try using one of the following lines instead.
        // var b = new[] { 1, 2, 3 };
       //int[] b = {1, 2, 3};
        return -1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0821

[image]

Visual C# 参考：错误和警告
编译器错误 CS0821
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

隐式类型的局部变量不能是固定值

fixed 上下文不支持隐式类型的局部变量和匿名类型。

更正此错误

  • 从该变量中移除 fixed 修饰符或为该变量指定显式类型。

示例

下面的代码生成 CS0821：

复制代码
class A
{
    static int x;

    public static int Main()
    {
        unsafe
        {
            fixed (var p = &x) { }
        }
        return -1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0822

[image]

Visual C# 参考：错误和警告
编译器错误 CS0822
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

隐式类型的局部变量不能为常量

仅在存储匿名类型时才需要隐式类型的局部变量。在所有其他情况下使用此类变量只是为了方便起见。如果该变量的值从未更改，则只需为其指定显式类型即可。如果尝试对隐式类型的局部变量使用 readonly 修饰符，则会生成 CS0106。

更正此错误

  • 如果要求该变量为常量或 readonly，请为其指定显式类型。

示例

下面的代码生成 CS0822：

复制代码
// cs0822.cs
class A
{

    public static int Main()
    {
        const var x = 0; // CS0822.cs
        return -1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0825

[image]

Visual C# 参考：错误和警告
编译器错误 CS0825
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

上下文关键字“var”只能出现在局部变量声明内。

通过 var 关键字执行的隐式类型化只能应用于局部方法范围内的变量。

更正此错误

  • 如果变量属于类范围，则为其指定显式类型。 或者，将该变量移到使用它的方法的内部。

示例

下面的代码生成 CS0825，因为类字段上使用了 var：

复制代码
// cs0825.cs
class Test
{
    private var myField; //CS0825

    static int Main()
    {
        var a = 1; // var is OK here
        return -1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0826

[image]

Visual C# 参考：错误和警告
编译器错误 CS0826
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

找不到隐式类型数组的最佳类型。

数组元素都必须为同一类型，或者可根据编译器所使用的类型推理规则隐式转换为同一类型。最佳类型必须为数组表达式中的类型之一。元素不会转换为诸如 object 之类的新类型。对于隐式类型的数组，编译器必须根据为其分配的元素类型推断该数组的类型。

更正此错误

  • 为该数组指定显式类型。

  • 为所有数组元素指定同一类型。

  • 为可能会导致问题的那些元素提供显式强制转换。

示例

下面的代码生成 CS0826，因为数组元素的类型不完全相同，并且编译器的类型推理逻辑未找到一个最佳类型：

复制代码
// cs0826.cs
public class C
{
    public static int Main()
    {
        var x = new[] { 1, "str" }; // CS0826

        char c = 'c';
        short s1 = 0;
        short s2 = -0;
        short s3 = 1;
        short s4 = -1;

        var array1 = new[] { s1, s2, s3, s4, c, '1' }; // CS0826
        return 1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0828

[image]

Visual C# 参考：错误和警告
编译器错误 CS0828
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

不能将“expression”赋予匿名类型属性。

匿名类型不能用 null 值、不安全类型、方法组或匿名函数进行初始化。

更正此错误

  • 将类型声明添加到赋值运算的左侧，或更改右侧的表达式以使其具有可接受的类型。

示例

下面的代码生成 CS0828，因为匿名类型的成员不能用 null 值进行初始化。

复制代码
// cs0828.cs
using System;

public class C
{
    public static int Main()
    {
        var a = 1;
        var c = new { p1 = null }; // CS0828
        return 1;
    }
}

请参见

参考

隐式类型的局部变量（C# 编程指南）


编译器错误 CS0831

[image]

Visual C# 参考：错误和警告
编译器错误 CS0831
示例 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树不能包含基访问。

基访问意味着在基类方法上，以非虚函数调用的方式执行通常是虚函数调用的函数调用。这在表达式目录树本身中是不允许的，但可以在调用基类方法的类中调用帮助器方法。

更正此错误

  • 如果必须用此方式访问基类方法，则创建一个调用该基类的帮助器方法，并使表达式目录树调用该帮助器方法。下面的代码演示了此项技术。

示例

下面的示例生成 CS0831：

复制代码
// cs0831.cs
using System;
using System.Linq;
using System.Linq.Expressions;

public class A
{
    public virtual int BaseMethod() { return 1; }
}
public class C : A
{
    public override int BaseMethod() { return 2; }
    public int Test(C c)
    {
        Expression<Func<int>> e = () => base.BaseMethod(); // CS0831

        // Try the following line instead,
        // along with the BaseAccessHelper method.
        // Expression<Func<int>> e2 = () => BaseAccessHelper();
        return 1;
    }
    // Uncomment to call from e2 expression above.
    // int BaseAccessHelper()
    // {
    //     return base.BaseMethod();
    // }

}


编译器错误 CS0832

[image]

Visual C# 参考：错误和警告
编译器错误 CS0832
示例 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树不能包含赋值运算符。

表达式目录树不保留变量状态，也没有任何存储位置的概念。

更正此错误

  • 从 lambda 或查询表达式中移除赋值运算符。

示例

在示例代码中，就像在所有 lambda 表达式中一样，x 只是通过值传递的输入参数。它的值不能在表达式目录树中更改，可以在委托 lambda 中更改。

复制代码
// cs0843.cs
using System;
using System.Linq;
using System.Linq.Expressions;

public class C
{
    public static int Main()
    {
        Expression<Func<int, int>> e = x => x += 5; // CS0843
        return 1;
    }
}


编译器错误 CS0833

[image]

Visual C# 参考：错误和警告
编译器错误 CS0833
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

匿名类型不能具有名称相同的多个属性。

就像任意类型一样，匿名类型不能具有名称相同的两个属性。

更正此错误

  • 为该类型的每个属性分别指定一个唯一的名称。

示例

下面的示例生成 CS0833：

复制代码
// cs0833.cs
using System;

public class C
{
    public static int Main()
    {
        var c = new { p1 = 1, p1 = 2 }; // CS0833
        return 1;
    }
}

请参见

参考

匿名类型（C# 编程指南）


编译器错误 CS0834

[image]

Visual C# 参考：错误和警告
编译器错误 CS0834
示例 发送反馈意见

更新：2007 年 11 月

错误消息

lambda 表达式必须具有表达式体才能转换为表达式目录树。

要转换为表达式目录树的 lambda 必须为表达式 lambda；语句 lambda 和匿名方法只能转换为委托类型。

更正此错误

  • 从 lambda 表达式中移除语句。

示例

下面的示例生成 CS0834：

复制代码
// cs0834.cs
using System;
using System.Linq;
using System.Linq.Expressions;

public class C
{
    public static int Main()
    {
        Expression<Func<int, int>> e = x => { return x; }; // CS0834
    }
}


编译器错误 CS0835

[image]

Visual C# 参考：错误和警告
编译器错误 CS0835
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将 lambda 转换为其类型参数“type”不是委托类型的表达式目录树。

如果要将某一 lambda 表达式转换为表达式目录树，则该表达式目录树的参数必须具有委托类型。此外，该 lambda 表达式必须可以转换为委托类型。

更正此错误

  • 将类型参数从 int 更改为委托类型，例如 Func<int,int>。

示例

下面的示例生成 CS0835：

复制代码
// cs0835.cs
using System;
using System.Linq;
using System.Linq.Expressions;

public class C
{
    public static int Main()
    {
        Expression<int> e = x => x + 1; // CS0835

        // Try the following line instead.
       // Expression<Func<int,int>> e2 = x => x + 1;

        return 1;
    }
}


编译器错误 CS0836

[image]

Visual C# 参考：错误和警告
编译器错误 CS0836
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能在常量表达式中使用匿名类型。

常量表达式中只允许使用命名常量、文本及包含常量表达式组合的数学表达式。

更正此错误

  • 使匿名类型成为命名类型。

示例

下面的示例演示一种生成 CS0836 的方式：

复制代码
// cs0836.cs
using System;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class A : Attribute
{
    public A(object obj)
    {
    }
}

[A(new { })] // CS0836
public class B
{
}

public class Test
{
    public static int Main()
    {
        return 0;
    }
}


编译器错误 CS0837

[image]

Visual C# 参考：错误和警告
编译器错误 CS0837
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“is”或“as”运算符的第一个操作数不能是 lambda 表达式或匿名方法。

Lambda 表达式和匿名方法不能在 is 或 as 的左侧使用。

更正此错误

  • 如果该错误涉及到 is 运算符，请记住 is 需要一个值和一个类型，并指示是否可通过引用、装箱或取消装箱转换将值设置为该类型。由于 lambda 不是值并且没有引用、装箱或取消装箱转换，因此 lambda 不是 is 的候选项。

  • 如果代码中误用了 as，则可能的更正方法是将其更改为强制转换。

示例

下面的示例生成 CS0837：

复制代码
// cs0837.cs
namespace TestNamespace
{
    public delegate void Del();

    class Test
    {
        static int Main()
        {
            bool b1 = (() => { }) is Del;   // CS0837
            bool b2 = delegate() { } is Del;// CS0837
            Del d1 = () => { } as Del;      // CS0837
            Del d2 = delegate() { } as Del; // CS0837
            return 1;
        }
    }
}


编译器错误 CS0838

[image]

Visual C# 参考：错误和警告
编译器错误 CS0838
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树不能包含多维数组初始值设定项。

表达式目录树中的多维数组不能使用数组初始值设定项进行初始化。

更正此错误

  • 先创建并初始化该数组，然后再创建表达式目录树。

示例

下面的示例生成 CS0838：

复制代码
// cs0838.cs
using System;
using System.Linq;
using System.Linq.Expressions;

namespace TestNamespace
{
    class Test
    {
        static int Main()
        {

            Expression<Func<int[,]>> expr =
                () => new int[2, 2] { { 1, 2 }, { 3, 4 } }; // CS0838

            // try the following 2 lines instead
            int[,] nums = new int[2, 2] { { 1, 2 }, { 3, 4 } };
            Expression<Func<int[,]>> expr2 = () => nums;

            return 1;
        }
    }
}

请参见

概念

表达式目录树


编译器错误 CS0839

[image]

Visual C# 参考：错误和警告
编译器错误 CS0839
示例 发送反馈意见

更新：2007 年 11 月

错误消息

缺少参数。

方法调用中缺少参数。

更正此错误

  • 仔细检查方法的签名，然后找到缺少的参数并提供该参数。

示例

下面的示例生成 CS0839：

复制代码
// cs0839.cs
using System;

namespace TestNamespace
{
    class Test
    {
        static int Add(int i, int j)
        {
            return i + j;
        }

        static int Main()
        {
            int i = Test.Add( , 5); // CS0839
            return 1;

        }
    }
}


编译器错误 CS0840

[image]

Visual C# 参考：错误和警告
编译器错误 CS0840
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“Property name”必须声明主体，因为它未标记为 abstract 或 extern。自动实现的属性必须同时定义 get 和 set 访问器。

除非常规属性被标记为 abstract 或 extern 或者作为 partial 类型的成员，否则必须为其提供主体。自动实现的属性不提供访问器体，但它们必须指定两个访问器。若要创建只读的自动实现属性，请将 set 访问器设为 private。

更正此错误

  • 提供缺少的主体或访问器，或者在该属性和/或其封闭类型上使用 abstract、extern 或 分部（类型）（C# 参考） 修饰符。

示例

下面的示例生成 CS0840：

复制代码
// cs0840.cs
// Compile with /target:library
using System;
class Test
{
    public int myProp { get; } // CS0840

    // to create a read-only property
    // try the following line instead
    public int myProp2 { get; private set; }

}

请参见

参考

自动实现的属性（C# 编程指南）


编译器错误 CS0841

[image]

Visual C# 参考：错误和警告
编译器错误 CS0841
示例 发送反馈意见

更新：2007 年 11 月

错误消息

变量“name”在声明之前无法使用。

变量在使用之前必须声明。

更正此错误

  • 将变量声明移至发生该错误的行之前。

示例

下面的示例生成 CS0841：

复制代码
// cs0841.cs
using System;

public class C
{
    public static int Main()
    {
        j = 5; // CS0841
        int j; // To fix, move this line up.
        return 1;
    }
}


编译器错误 CS0842

[image]

Visual C# 参考：错误和警告
编译器错误 CS0842
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能在用 StructLayout(LayoutKind.Explicit) 标记的类型内部使用自动实现的属性。

自动实现的属性具有由编译器提供的支持字段，此类字段不能由源代码访问。因此，这些属性与 LayoutKind..::.Explicit 不兼容。

更正此错误

  • 使该属性成为提供访问器体的常规属性。

示例

下面的示例生成 CS0842：

复制代码
// cs0842.cs
using System;
using System.Runtime.InteropServices;

namespace TestNamespace
{
    [StructLayout(LayoutKind.Explicit)]
    struct Str
    {
        public int Num // CS0842
        {
            get;
            set;
        }

        static int Main()
        {
            return 1;
        }
    }
}


编译器错误 CS0843

[image]

Visual C# 参考：错误和警告
编译器错误 CS0843
示例 发送反馈意见

更新：2007 年 11 月

错误消息

在控制返回到调用程序之前，自动实现的属性“name”的支持字段必须完全赋值。请考虑从构造函数初始值设定项中调用默认构造函数。

若要从构造函数中为自动实现的属性赋值，必须首先调用默认构造函数以创建对象。

更正此错误

  • 在构造函数初始值设定项中添加对默认构造函数的调用，如以下示例中所示。请注意 : this() 的使用。有关更多信息，请参见 this（C# 参考）。

示例

下面的代码将生成 CS0843：

复制代码
// cs0843.cs
struct S
{
    public int AIProp { get; set; }
    public S(int i){} //CS0843
    // Try the following lines instead.
   // public S(int i) : this()
   // {
   //     AIProp = i;
   // }
}

class Test
{
    static int Main()
    {
        return 1;
    }
}


编译器错误 CS0844

[image]

Visual C# 参考：错误和警告
编译器错误 CS0844
示例 发送反馈意见

更新：2007 年 11 月

错误消息

局部变量“name”在声明之前无法使用。局部变量的声明隐藏字段“name”。

在给定块中标识符只能有一种含义。通过为该标识符引入另一种含义，与类字段同名的局部变量可以隐藏字段。因此，如果在方法中引用类字段，然后使用相同名称声明局部变量，则编译器将生成错误。

更正此错误

  • 使用 this.num 引用类字段。

  • 为局部变量提供一个与类字段名称不同的名称。

示例

下面的代码将生成 CS0844：

复制代码
class Test
    {
        int num;
        public void TestMethod()
        {
            num = 5; // CS0844
            int num = 6;        }
        public static int Main()
        {
            return 1;
        }
    }


编译器错误 CS1001

[image]

Visual C# 参考：错误和警告
编译器错误 CS1001
发送反馈意见

更新：2007 年 11 月

错误消息

应输入标识符

未提供标识符。例如，当声明枚举时，必须指定成员。

下面的示例生成 CS1001：

复制代码
// CS1001.cs
public class clx
{
   enum splitch : int
   {
      'a', 'b' // CS1001, 'a' is not a valid int identifier
   };

   public static void Main()
   {
   }
}

即使编译器不使用参数名（例如，在接口定义中），也需要参数名。需要这些参数，以便使用接口的程序员能够对参数的含义有所了解。

复制代码
// CS1001-2.cs
// compile with: /target:library
interface IMyTest
{
   void TestFunc1(int, int);  // CS1001
}

class CMyTest : IMyTest
{
   void IMyTest.TestFunc1(int a, int b)
   {
   }
}


编译器错误 CS1002

[image]

Visual C# 参考：错误和警告
编译器错误 CS1002
发送反馈意见

更新：2007 年 11 月

错误消息

应输入 ;

编译器检测到缺少分号。

下面的示例生成 CS1002：

复制代码
// CS1002.cs
namespace x
{
   abstract public class clx
   {
      int i   // CS1002, missing semicolon

      public static int Main()
      {
         return 0;
      }
   }
}


编译器错误 CS1003

[image]

Visual C# 参考：错误和警告
编译器错误 CS1003
发送反馈意见

更新：2007 年 11 月

错误消息

语法错误，应输入“char”

编译器将因若干错误条件中的任何一个而生成该错误。请查看您的代码以找到语法错误。

下面的示例生成 CS1003：

复制代码
// CS1003.cs
public class b
{
   public static void Main()
   {
      int[] a;
      a[);   // CS1003
   }
}


编译器错误 CS1004

[image]

Visual C# 参考：错误和警告
编译器错误 CS1004
发送反馈意见

更新：2007 年 11 月

错误消息

重复的“modifier”修饰符

检测到重复的修饰符，如 access 修饰符。

下面的示例生成 CS1004：

复制代码
// CS1004.cs
public class clx
{
   public public static void Main()   // CS1004, two public keywords
   {
   }
}


编译器错误 CS1007

[image]

Visual C# 参考：错误和警告
编译器错误 CS1007
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性访问器已经定义

当声明属性时，还必须声明它的访问器方法。不过，一个属性不能有多个 get 访问器方法或多个 set 访问器方法。

示例

下面的示例生成 CS1007：

复制代码
// CS1007.cs
public class clx
{
    public int MyProperty
    {
        get
        {
            return 0;
        }
        get   // CS1007, this is the second get method
        {
            return 0;
        }
    }

    public static void Main() {}
}


编译器错误 CS1008

[image]

Visual C# 参考：错误和警告
编译器错误 CS1008
发送反馈意见

更新：2007 年 11 月

错误消息

应输入类型 byte、sbyte、short、ushort、int、uint、long 或 ulong

某些数据类型（如 enums）只能声明为保存指定类型的数据。

下面的示例生成 CS1008：

复制代码
// CS1008.cs
abstract public class clx
{
   enum splitch : char   // CS1008, char not valid type for enums
   {
      x, y, z
   }

   public static void Main()
   {
   }
}


编译器错误 CS1009

[image]

Visual C# 参考：错误和警告
编译器错误 CS1009
发送反馈意见

更新：2007 年 11 月

错误消息

无法识别的转义序列

在 string 中反斜杠 (\) 的后面是一个意外的字符。编译器需要一个有效的转义符；有关更多信息，请参见字符转义。

下面的示例生成 CS1009：

复制代码
// CS1009-a.cs
class MyClass
{
   static void Main()
   {
      string a = "\m";   // CS1009
      // try the following line instead
      // string a = "\t";
   }
}

发生该错误的原因通常是在文件名中使用了反斜杠字符，例如：

复制代码
string filename = "c:\myFolder\myFile.txt";

若要纠正该错误，请使用“\\”或前面带有 @ 且用引号括起的字符串，如下面的示例所示：

复制代码
// CS1009-b.cs
class MyClass
{
   static void Main()
   {
      string filename = "c:\myFolder\myFile.txt";   // CS1009
      // try the one of the following lines instead
      // string filename = "c:\\myFolder\\myFile.txt";
      // string filename = @"c:\myFolder\myFile.txt";
   }
}


编译器错误 CS1010

[image]

Visual C# 参考：错误和警告
编译器错误 CS1010
发送反馈意见

更新：2007 年 11 月

错误消息

常数中有换行符

一个字符串没有正确分隔。

下面的示例生成 CS1010：

复制代码
// CS1010.cs
class Sample
{
   static void Main()
   {
      string a = "Hello World;   // CS1010, add end quote
   }
}


编译器错误 CS1011

[image]

Visual C# 参考：错误和警告
编译器错误 CS1011
发送反馈意见

更新：2007 年 11 月

错误消息

空字符

声明了 char，并将其初始化为 null。初始化 char 时必须指定字符。

下面的示例生成 CS1011：

复制代码
// CS1011.cs
class Sample
{
   public char CharField = '';   // CS1011
}


编译器错误 CS1012

[image]

Visual C# 参考：错误和警告
编译器错误 CS1012
发送反馈意见

更新：2007 年 11 月

错误消息

字符文本中字符太多

试图初始化具有多个字符的 char 常数。

执行数据绑定时也会发生 CS1012。例如，下面的代码行将发生错误：

<%# DataBinder.Eval(Container.DataItem, 'doctitle') %>

请尝试改用下面的代码行：

<%# DataBinder.Eval(Container.DataItem, "doctitle") %>

下面的示例生成 CS1012：

复制代码
// CS1012.cs
class Sample
{
   static void Main()
   {
      char a = 'xx';   // CS1012
      char a2 = 'x';   // OK
      System.Console.WriteLine(a2);
   }
}


编译器错误 CS1013

[image]

Visual C# 参考：错误和警告
编译器错误 CS1013
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无效数字

编译器检测到不正确的数字。有关整型的更多信息，请参见整型表（C# 参考）。

示例

下面的示例生成 CS1013：

复制代码
// CS1013.cs
class Sample
{
    static void Main()
    {
        int i = 0x;   // CS1013
    }
}


编译器错误 CS1014

[image]

Visual C# 参考：错误和警告
编译器错误 CS1014
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应为 get 或 set 访问器

在属性声明中发现了方法声明。在属性中只能声明 get 和 set 方法。

有关属性的更多信息，请参见 使用属性（C# 编程指南）。

示例

下面的示例生成 CS1014。

复制代码
// CS1014.cs
// compile with: /target:library
class Sample
{
   public int TestProperty
   {
      get
      {
         return 0;
      }
      int z;   // CS1014  not get or set
   }
}


编译器错误 CS1015

[image]

Visual C# 参考：错误和警告
编译器错误 CS1015
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入对象、字符串或类类型

试图将预定义的数据类型传递到 catch 块。只有从 System..::.Exception 派生的数据类型才能传递到 catch 块中。有关异常的更多信息，请参见 异常处理语句（C# 参考）。

示例

下面的示例生成 CS1015：

复制代码
// CS1015.cs
class Sample
{
    static void Main()
    {
        try
        {
        }
        catch(int)   // CS1015, int is not derived from System.Exception
        {
        }
    }
}


编译器错误 CS1016

[image]

Visual C# 参考：错误和警告
编译器错误 CS1016
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入命名属性参数

未命名的属性参数必须出现在命名参数之前。

示例

下面的示例生成 CS1016：

复制代码
// CS1016.cs
using System;

[AttributeUsage(AttributeTargets.Class)]
public class HelpAttribute : Attribute
{
    public HelpAttribute(string url)   // url is a positional parameter
    {
        m_url = url;
    }

    public string Topic = null;      // Topic is a named parameter
    private string m_url = null;
}

[HelpAttribute(Topic="Samples", "http://intranet/inhouse")]   // CS1016
// try the following line instead
//[HelpAttribute("http://intranet/inhouse", Topic="Samples")]
public class MainClass
{
    public static void Main ()
    {
    }
}


编译器错误 CS1017

[image]

Visual C# 参考：错误和警告
编译器错误 CS1017
示例 发送反馈意见

更新：2007 年 11 月

错误消息

catch 子句不能跟在 try 语句的常规 catch 子句的后面

一个不带任何参数的 catch 块必须是一系列 catch 块的最后一项。有关异常的更多信息，请参见 异常处理语句（C# 参考）。

示例

下面的示例生成 CS1017：

复制代码
// CS1017.cs
using System;

namespace x
{
    public class b : Exception
    {
    }

    public class a
    {
        public static void Main()
        {
            try
            {
            }

            catch   // CS1017, must be last catch
            {
            }

            catch(b)
            {
                throw;
            }
        }
    }
}


编译器错误 CS1018

[image]

Visual C# 参考：错误和警告
编译器错误 CS1018
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入关键字“this”或“base”

编译器遇到了不完整的构造函数声明。

示例

下面的示例生成 CS1018 并建议了几种解决此错误的方法：

复制代码
// CS1018.cs
public class C
{
}

public class a : C
{
    public a(int i)
    {
    }

    public a () :   // CS1018
    // possible resolutions:
    // public a () resolves by removing the colon
    // public a () : base() calls C's default constructor
    // public a () : this(1) calls the assignment constructor of class a
    {
    }

    public static int Main()
    {
        return 1;
    }
}


编译器错误 CS1019

[image]

Visual C# 参考：错误和警告
编译器错误 CS1019
发送反馈意见

更新：2007 年 11 月

错误消息

应输入可重载的一元运算符

您具有返回其他类的值的一元运算符。如果希望进行该转换，则需要进行 implicit 或 explicit 转换。

下面的示例生成 CS1019：

复制代码
// CS1019.cs
public class ii
{
   int i
   {
      get
      {
         return 0;
      }
   }
}

public class a
{
   public static a operator ii(a aa)   // CS1019
   // try the following line instead
   //public static a operator ++(a aa)
   {
      return new a();
   }

   public static void Main()
   {
   }
}


编译器错误 CS1020

[image]

Visual C# 参考：错误和警告
编译器错误 CS1020
发送反馈意见

更新：2007 年 11 月

错误消息

应输入可重载的二元运算符

试图定义运算符重载，但运算符不是二进制运算符（带有两个参数）。

下面的示例生成 CS1020：

复制代码
// CS1020.cs
public class iii
{
   public static int operator ++(iii aa, int bb)   // CS1020, change ++ to +
   {
      return 0;
   }

   public static void Main()
   {
   }
}


编译器错误 CS1021

[image]

Visual C# 参考：错误和警告
编译器错误 CS1021
发送反馈意见

更新：2007 年 11 月

错误消息

整数常数太大

赋为整型的值超出了无符号整数值的最大范围。

下面的示例生成 CS1021：

复制代码
// CS1021.cs
enum F : int
{
   a=(int)2590000000000000000000   // CS1021
}

public class clx
{
   public static void Main()
   {
   }
}


编译器错误 CS1022

[image]

Visual C# 参考：错误和警告
编译器错误 CS1022
发送反馈意见

更新：2007 年 11 月

错误消息

应输入类型、命名空间定义或文件尾

源代码文件中的大括号不成对。

下面的示例生成 CS1022：

复制代码
// CS1022.cs
namespace x
{
}
}   // CS1022


编译器错误 CS1023

[image]

Visual C# 参考：错误和警告
编译器错误 CS1023
发送反馈意见

更新：2007 年 11 月

错误消息

嵌入的语句不能是声明或标记语句

嵌入的语句（如 if 语句后面的语句）既不能包含声明，也不能包含标记语句。

下面的示例生成 CS1023 两次：

复制代码
// CS1023.cs
public class a
{
   public static void Main()
   {
      if (1)
         int i;      // CS1023, declaration is not valid here

      if (1)
         xx : i++;   // CS1023, labeled statement is not valid here
   }
}


编译器错误 CS1024

[image]

Visual C# 参考：错误和警告
编译器错误 CS1024
发送反馈意见

更新：2007 年 11 月

错误消息

应输入预处理器指令

行以 # 符号开头，但后面的字符串不是有效的预处理器指令。

下面的示例生成 CS1024：

复制代码
// CS1024.cs
#import System   // CS1024


编译器错误 CS1025

[image]

Visual C# 参考：错误和警告
编译器错误 CS1025
发送反馈意见

更新：2007 年 11 月

错误消息

应输入单行注释或行尾

带有预处理器指令的行不能有多行注释。

下面的示例生成 CS1025：

复制代码
#if true /* hello
*/   // CS1025
#endif   // this is a good comment

如果尝试使用某些无效的预处理器指令，则也可能发生 CS1025，如下所示：

复制代码
// CS1025.cs
#define a

class Sample
{
   static void Main()
   {
      #if a 1   // CS1025, invalid syntax
         System.Console.WriteLine("Hello, World!");
      #endif
   }
}


编译器错误 CS1026

[image]

Visual C# 参考：错误和警告
编译器错误 CS1026
发送反馈意见

更新：2007 年 11 月

错误消息

应输入 )

找到了不完整的语句。

发生该错误的通常原因是将语句而不是表达式放入 ASP.NET 页中的内联表达式内。例如，以下表达式是错误的：

复制代码
<%=new TimeSpan(DateTime.Now.Ticks - new DateTime(2001, 1, 1).Ticks).Days;%>

正确的表达式应如下：

复制代码
<%=new TimeSpan(DateTime.Now.Ticks - new DateTime(2001, 1, 1).Ticks).Days %>

其解释如下：

复制代码
<% Response.Write(new TimeSpan(DateTime.Now.Ticks - new DateTime(2001, 1, 1).Ticks).Days); %>

下面的示例生成 CS1026：

复制代码
// CS1026.cs
#if (a == b   // CS1026, add closing )
#endif

class x
{
   public static void Main()
   {
   }
}


编译器错误 CS1027

[image]

Visual C# 参考：错误和警告
编译器错误 CS1027
发送反馈意见

更新：2007 年 11 月

错误消息

应输入 #endif 指令

没有为指定的 #if 指令找到匹配的 #endif 预处理器指令。或者，在 #if 块中没有匹配的 #region 指令，而编译器可能找到了 #endregion 指令。

下面的示例生成 CS1027：

复制代码
// CS1027.cs
#if true   // CS1027, uncomment next line to resolve
// #endif

namespace x
{
   public class clx
   {
      public static void Main()
      {
      }
   }
}


编译器错误 CS1028

[image]

Visual C# 参考：错误和警告
编译器错误 CS1028
发送反馈意见

更新：2007 年 11 月

错误消息

意外的预处理器指令

发现了预处理器指令，但它不应出现。

例如，发现了 #endif，但前面没有 #if。

下面的示例生成 CS1028：

复制代码
// CS1028.cs
#endif   // CS1028, no matching #if
namespace x
{
   public class clx
   {
      public static void Main()
      {
      }
   }
}


编译器错误 CS1029

[image]

Visual C# 参考：错误和警告
编译器错误 CS1029
发送反馈意见

更新：2007 年 11 月

错误消息

#error：“text”

显示用 #error 指令定义的错误文本。

下面的示例显示如何创建用户定义的错误：

复制代码
// CS1029.cs
class Sample
{
   static void Main()
   {
      #error Let's give an error here   // CS1029
   }
}


编译器错误 CS1031

[image]

Visual C# 参考：错误和警告
编译器错误 CS1031
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入类型

应输入类型参数。

示例

下面的示例生成 CS1031：

复制代码
// CS1031.cs
namespace x
{
    public class ii
    {
    }

    public class a
    {
        public static operator +(a aa)   // CS1031
        // try the following line instead
        // public static ii operator +(a aa)
        {
            return new ii();
        }

        public static void Main()
        {
            e = new base;   // CS1031, not a type
            e = new this;   // CS1031, not a type
            e = new ();     // CS1031, not a type
        }
    }
}


编译器错误 CS1032

[image]

Visual C# 参考：错误和警告
编译器错误 CS1032
发送反馈意见

更新：2007 年 11 月

错误消息

不能在文件的第一个标记之后，定义或取消定义预处理器符号

#define 和 #undef 预处理器指令必须用在程序的开头，并且在其他任何关键字之前，例如用在命名空间声明中的那些指令之前。

下面的示例生成 CS1032：

复制代码
// CS1032.cs
namespace x
{
   public class clx
   {
      #define a   // CS1032, put before namespace
      public static void Main()
      {
      }
   }
}


编译器错误 CS1033

[image]

Visual C# 参考：错误和警告
编译器错误 CS1033
发送反馈意见

更新：2007 年 11 月

错误消息

源文件已超过在 PDB 中可表示的 16,707,565 行的限制；调试信息将不正确

源代码文件超过了编译器可以处理的最大允许行数。若要解决该错误，请从原始文件创建两个或多个源代码文件。最大行数为 268,435,454 行。如果使用 /debug，则使用超过 16,707,566 行将导致调试信息损坏。


编译器错误 CS1034

[image]

Visual C# 参考：错误和警告
编译器错误 CS1034
发送反馈意见

更新：2007 年 11 月

错误消息

超出编译器限制: 行不能超过“number”个字符

一行中所允许的字符数限制为 16,777,214 个。


编译器错误 CS1035

[image]

Visual C# 参考：错误和警告
编译器错误 CS1035
发送反馈意见

更新：2007 年 11 月

错误消息

发现文件尾，应输入“*/”

没有与注释的开始分隔符相匹配的结束分隔符。

下面的示例生成 CS1035：

复制代码
// CS1035.cs
public class a
{
   public static void Main()
   {
   }
}
/*   // CS1035, needs closing comment


编译器错误 CS1036

[image]

Visual C# 参考：错误和警告
编译器错误 CS1036
发送反馈意见

更新：2007 年 11 月

错误消息

应输入 ( 或 .

doc 注释中的 XML 格式错误。


编译器错误 CS1037

[image]

Visual C# 参考：错误和警告
编译器错误 CS1037
发送反馈意见

更新：2007 年 11 月

错误消息

应输入可重载运算符

当用 /doc 指定注释时，编译器遇到了无效链接。


编译器错误 CS1038

[image]

Visual C# 参考：错误和警告
编译器错误 CS1038
发送反馈意见

更新：2007 年 11 月

错误消息

应输入 #endregion 指令

#region 指令没有相匹配的 #endregion 指令。

下面的示例生成 CS1038：

复制代码
// CS1038.cs
#region testing

public class clx
{
   public static void Main()
   {
   }
}
// CS1038
// uncomment the next line to resolve
// #endregion


编译器错误 CS1039

[image]

Visual C# 参考：错误和警告
编译器错误 CS1039
示例 发送反馈意见

更新：2007 年 11 月

错误消息

字符串未终止

编译器检测到格式错误的 string。

示例

下面的示例生成 CS1039。若要纠正该错误，请添加终止引号。

复制代码
// CS1039.cs
public class MyClass
{
    public static void Main()
    {
        string b = @"hello, world;   // CS1039
    }
}


编译器错误 CS1040

[image]

Visual C# 参考：错误和警告
编译器错误 CS1040
发送反馈意见

更新：2007 年 11 月

错误消息

预处理器指令必须作为一行的第一个非空白字符出现

在行上发现了预处理器指令，并且它不是该行上的第一个标记。指令必须是该行上的第一个标记。

下面的示例生成 CS1040：

复制代码
// CS1040.cs
/* Define a symbol, X */ #define X   // CS1040

// try the following two lines instead
// /* Define a symbol, X */
// #define X

public class MyClass
{
   public static void Main()
   {
   }
}


编译器错误 CS1041

[image]

Visual C# 参考：错误和警告
编译器错误 CS1041
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入标识符，“keyword”是关键字

在应为标识符的地方发现了 C# 语言的保留字。用用户指定的标识符替换关键字。

示例

下面的示例生成 CS1041：

复制代码
// CS1041a.cs
class MyClass
{
    public void f(int long)   // CS1041
    // Try the following instead:
    // public void f(int i)
    {
    }

    public static void Main()
    {
    }
}

当从保留字设置不同的其他编程语言中导入时，可以使用 @ 前缀修改保留标识符，如下面的示例所示。

带 @ 前缀的标识符称作逐字标识符。

复制代码
// CS1041b.cs
class MyClass
{
    public void f(int long)   // CS1041
    // Try the following instead:
    // public void f(int @long)
    {
    }

    public static void Main()
    {
    }
}


编译器错误 CS1043

[image]

Visual C# 参考：错误和警告
编译器错误 CS1043
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入 { 或 ;

属性访问器未正确声明。有关更多信息，请参见使用属性（C# 编程指南）。

示例

下面的示例生成 CS1043。

复制代码
// CS1043.cs
// compile with: /target:library
public class MyClass
{
   public int DoSomething
   {
      get return 1;   // CS1043
      set {}
   }

   // OK
   public int DoSomething2
   {
      get { return 1;}
   }
}


编译器错误 CS1044

[image]

Visual C# 参考：错误和警告
编译器错误 CS1044
发送反馈意见

更新：2007 年 11 月

错误消息

在 for、using、fixed 或声明语句中不能使用多个类型

编译器发现了无效语句。

下面的示例生成 CS1044：

复制代码
// CS1044.cs
using System;

public class MyClass : IDisposable
{
   public void Dispose()
   {
      Console.WriteLine("Res1.Dispose()");
   }

   public static void Main()
   {
      using (MyClass mc1 = new MyClass(),
             MyClass mc2 = new MyClass())   // CS1044, remove an instantiation
      {
      }
   }
}


编译器错误 CS1055

[image]

Visual C# 参考：错误和警告
编译器错误 CS1055
发送反馈意见

更新：2007 年 11 月

错误消息

应为 add 访问器或 remove 访问器

如果未将 event 声明为字段，则它必须同时定义 add 和 remove 访问器函数。

下面的示例生成 CS1055：

复制代码
// CS1055.cs
delegate void del();
class Test
{
   public event del MyEvent
   {
      int i;   // CS1055
      // uncomment accessors and delete previous line to resolve
      // add
      // {
      //    MyEvent += value;
      // }
      // remove
      // {
      //    MyEvent -= value;
      // }
   }

   public static void Main()
   {
   }
}


编译器错误 CS1056

[image]

Visual C# 参考：错误和警告
编译器错误 CS1056
发送反馈意见

更新：2007 年 11 月

错误消息

意外的字符“character”

C# 编译器遇到了意外的字符，无法标识当前处理的标记。例如，如果编译器在处理标识符的过程中遇到了一个欧元字符，它将无法对该标识符进行分类，因为欧元字符只在字符串内有效，而编译器认为它没在处理字符串。


编译器错误 CS1057

[image]

Visual C# 参考：错误和警告
编译器错误 CS1057
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“member”：静态类不能包含受保护成员

在静态类内部声明受保护成员时，会生成此错误。

示例

下面的示例生成 CS1057。

复制代码
// CS1057.cs
using System;

static class Class1
{
    protected static int x;   // CS1057
    public static void Main()
    {
    }
}


编译器错误 CS1059

[image]

Visual C# 参考：错误和警告
编译器错误 CS1059
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

增量或减量运算符的操作数必须为变量、属性或索引器。

在尝试递增或递减常量值时，会引发此错误。如果尝试递增表达式（如 (a+b)++），也会发生此错误。

更正此错误

  • 使变量成为非常量变量。

  • 移除增量或减量运算符。

  • 将表达式存储在一个变量中，然后递增该变量。

示例

下面的示例生成 CS1059，因为 i 是常量而不是变量，并且 E 为 Enum 类型，该类型的元素也是常量值。

复制代码
// CS1059.cs
    class Program
    {
        public enum E : sbyte
        {
            a = 1,
            b = 2
        }

        static void Main(string[] args)
        {
            const int i = 0;
            i++;            // CS1059
            E test = E.a++; // CS1059
        }
    }

请参见

参考

常量（C# 编程指南）


编译器错误 CS1061

[image]

Visual C# 参考：错误和警告
编译器错误 CS1061
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”不包含“member”的定义，并且找不到可接受类型为“type”的第一个参数的扩展方法“name”(是否缺少 using 指令或程序集引用?)。

在尝试调用的方法或访问的类成员不存在时，通常会引发此错误。如果确定该类具有使用该名称的成员或存在适用的扩展方法，则发生此错误的原因可能是缺少 using 指令或程序集引用。

示例

下面的示例生成 CS1061：

复制代码
// cs1061.cs
    public class TestClass
    {
        public void WriteSomething(string s)
        {
            System.Console.WriteLine(s);
        }
    }
    public class Test
    {
        public static void Main()
        {
            TestClass tc = new TestClass();
            tc.WriteLine("Hello");      // CS1061

        }
    }


编译器错误 CS1100

[image]

Visual C# 参考：错误和警告
编译器错误 CS1100
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

方法“name”具有不在第一个参数上的参数修饰符“this”。

this 修饰符只能位于方法的第一个参数上，这向编译器表明该方法是扩展方法。

更正此错误

  • 从该方法的除第一个参数之外的其他所有参数中移除 this 修饰符。

示例

下面的代码生成 CS1100，因为 this 参数修饰的是第二个参数：

复制代码
// cs1100.cs
static class Test
{
    static void ExtMethod(int i, this Test c) // CS1100
    {
    }
}

请参见

参考

扩展方法（C# 编程指南）


编译器错误 CS1101

[image]

Visual C# 参考：错误和警告
编译器错误 CS1101
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

参数修饰符“ref”不能与“this”一起使用。

在 this 关键字修饰静态方法的第一个参数时，它向编译器指示该方法是扩展方法。不需要或不允许对扩展方法的第一个参数使用任何其他修饰符。

示例

下面的示例生成 CS1101：

复制代码
// cs1101.cs
// Compile with: /target:library
public static class Extensions
{
    // No type parameters.
        public static void Test(ref this int i) {} // CS1101

    // Single type parameter.
        public static void Test<T>(ref this T t) {}// CS1101

    // Multiple type parameters.
        public static void Test<T,U,V>(ref this U u) {}// CS1101
}

请参见

参考

扩展方法（C# 编程指南）
this（C# 参考）
ref（C# 参考）


编译器错误 CS1102

[image]

Visual C# 参考：错误和警告
编译器错误 CS1102
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

参数修饰符“out”不能与“this”一起使用。

在 this 关键字修饰静态方法的第一个参数时，它向编译器指示该方法是扩展方法。不需要或不允许对扩展方法的第一个参数使用任何其他修饰符。

更正此错误

  • 从第一个参数移除未经授权的修饰符。

示例

下面的示例生成 CS1102：

复制代码
// cs1102.cs
// Compile with: /target:library.
public static class Extensions
{
    // No type parameters.
        public static void Test(this out int i) {} // CS1102

    //Single type parameter
        public static void Test<T>(this out T t) {}// CS1102

    //Multiple type parameters
        public static void Test<T,U,V>(this out U u) {}// CS1102
}

请参见

参考

扩展方法（C# 编程指南）
this（C# 参考）
out（C# 参考）


编译器错误 CS1103

[image]

Visual C# 参考：错误和警告
编译器错误 CS1103
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

扩展方法的第一个参数的类型不能为“type”。

扩展方法的第一个参数不能为指针类型。

示例

下面的示例生成 CS1103：

复制代码
// cs1103.cs
public static class Extensions
{
    public unsafe static char* Test(this char* charP) { return charP; } // CS1103
}

请参见

参考

扩展方法（C# 编程指南）
unsafe（C# 参考）


编译器错误 CS1104

[image]

Visual C# 参考：错误和警告
编译器错误 CS1104
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

参数数组不能与“this”修饰符一起在扩展方法中使用。

扩展方法的第一个参数不能为 params 数组。

更正此错误

  • 请记住，扩展方法定义的第一个参数指定该方法将“扩展”哪一类型。该参数不是输入参数。因此，在此位置中包含 params 数组没有任何意义。如果必须传入 params 数组，则使其成为第二个参数。

示例

下面的示例生成 CS1104：

复制代码
// cs1104.cs
// Compile with: /target:library
public static class Extensions
{
    public static void Test<T>(this params T[] tArr) {} // CS1104
}

请参见

参考

扩展方法（C# 编程指南）
params（C# 参考）


编译器错误 CS1105

[image]

Visual C# 参考：错误和警告
编译器错误 CS1105
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

扩展方法必须为静态方法。

扩展方法必须在非泛型静态类中声明为静态方法。

示例

下面的示例生成 CS1105，因为 Test 不是静态方法：

复制代码
// cs1105.cs
// Compile with: /target:library
public class Extensions
{

    // Single type parameter.
        public void Test<T>(this System.String s) {} //CS1105

}

请参见

参考

扩展方法（C# 编程指南）
static（C# 参考）


编译器错误 CS1106

[image]

Visual C# 参考：错误和警告
编译器错误 CS1106
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

扩展方法必须在非泛型静态类中定义。

扩展方法必须在非泛型静态类中定义为静态方法。

示例

下面的示例生成 CS1106，因为类 Extensions 未定义为 static：

复制代码
// cs1106.cs
public class Extensions // CS1106
{
    public  static void Test<T>(this System.String s) {}
}

请参见

参考

扩展方法（C# 编程指南）
static（C# 参考）


编译器错误 CS1107

[image]

Visual C# 参考：错误和警告
编译器错误 CS1107
示例 发送反馈意见

更新：2007 年 11 月

错误消息

参数只能有一个“modifier name”修饰符。

参数修饰符（如 this、ref 和 out）在参数定义中多次出现是错误的。

示例

下面的示例生成 CS1107：

复制代码
// cs1107.cs
public static class Test
{
    // Extension methods.
        public static void TestMethod(this this t) {} // CS1107

    // Regular Instance Method
        public void TestMethod(ref ref int i) {} // CS1107

}


编译器错误 CS1108

[image]

Visual C# 参考：错误和警告
编译器错误 CS1108
示例 发送反馈意见

更新：2007 年 11 月

错误消息

参数不能具有所有指定的修饰符；该参数上的修饰符太多。

有些参数修饰符的组合（如 ref 和 out）是不允许的，因为它们对于编译器来说是互相排斥的。

示例

下面的示例生成 CS1108：

复制代码
// cs1108.cs
// Compile with: /target:library
public class Test
{
    // Regular Instance Method.
        public void TestMethod(ref out int i) {} // CS1108

}


编译器错误 CS1109

[image]

Visual C# 参考：错误和警告
编译器错误 CS1109
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

扩展方法必须在顶级静态类上定义，而“name”是嵌套类。

扩展方法不能在嵌套类中定义。

示例

下面的示例生成 CS1109，因为类 Extension 嵌套在类 Out 的内部：

复制代码
// cs1109.cs
public class Test
{
}
static class Out
{
    static class Extension
    {
        static void ExtMethod(this Test c) // CS1109
        {
        }
    }
}

请参见

参考

扩展方法（C# 编程指南）


编译器错误 CS1110

[image]

Visual C# 参考：错误和警告
编译器错误 CS1110
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

在没有对 System.Core.dll 进行引用的情况下，不能在方法声明的第一个参数上使用“this”修饰符。请添加对 System.Core.dll 的引用或从方法声明中移除“this”修饰符。

.NET Framework 3.5 版和更高版本支持扩展方法。扩展方法可生成使用属性来标记方法的元数据。该属性类位于 system.core.dll 中。

更正此错误

  • 按消息中的指示，添加对 System.Core.dll 的引用或从方法声明中移除 this 修饰符。

示例

在没有用对 System.Core.dll 的引用编译文件的情况下，下面的示例会生成 CS1110：

复制代码
// cs1110.cs
// CS1110
// Compile with: /target:library
public static class Extensions
{
    public static bool Test(this bool b) { return b; }
}

请参见

参考

扩展方法（C# 编程指南）


编译器错误 CS1112

[image]

Visual C# 参考：错误和警告
编译器错误 CS1112
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不要使用“System.Runtime.CompilerServices.ExtensionAttribute”。请改用“this”关键字。

当在包含扩展方法的非静态类上使用 ExtensionAttribute 时，会生成此错误。如果在静态类上使用此属性，则可能会发生其他错误，如 CS0708：“不能在静态类中声明实例成员”。

在 C# 中，必须在静态类中定义扩展方法，并用 this 关键字修饰该方法的第一个参数。任何情况下都不要在源代码中使用该属性。有关更多信息，请参见扩展方法（C# 编程指南）。

更正此错误

  • 移除该属性，然后将 this 修饰符应用于该方法的第一个参数。

示例

下面的示例生成 CS1112：

复制代码
// cs1112.cs
[System.Runtime.CompilerServices.ExtensionAttribute] // CS1112
public class Extensions
{
    public bool A(bool b) { return b; }
}

class A { }


编译器错误 CS1113

[image]

Visual C# 参考：错误和警告
编译器错误 CS1113
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

在值类型“name”上定义的扩展方法不能用于创建委托。

为类类型定义的扩展方法可用于创建委托。为值类型定义的扩展方法则不能。

更正此错误

  1. 将扩展方法与类类型相关联。

  2. 使该方法成为该结构上的常规方法。

示例

下面的示例生成 CS1113：

复制代码
// cs1113.cs
using System;
public static class Extensions
{
    public static S ExtMethod(this S s)
    {
        return s;
    }
}

public struct S
{
}

public class Test
{
    static int Main()
    {
        Func<S> f = new S().ExtMethod; // CS1113
        return 1;
    }
}

请参见

参考

扩展方法（C# 编程指南）


编译器错误 CS1501

[image]

Visual C# 参考：错误和警告
编译器错误 CS1501
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“method”方法的重载不带有“number”个参数

对类方法进行了调用，但没有一种形式的方法带有所需数目的参数。

如果正在调用所引用程序集中类上的方法，并且该方法在它的一个或多个参数上有默认值，则会发生 CS1501。C# 不能使您创建参数上带有默认值的方法，但另一种面向运行库的语言可以。如果参数（在所引用程序集的方法中）有默认值，您仍然必须调用方法并显式传递所有参数。

示例

下面的示例生成 CS1501。

复制代码
    // CS1501a.cs
    public class a
    {
        public static int Main()
        {
            a.Main(2);
            return 1;
        }
    }


编译器错误 CS1502

[image]

Visual C# 参考：错误和警告
编译器错误 CS1502
发送反馈意见

更新：2007 年 11 月

错误消息

与“name”最匹配的重载方法有一些无效参数

当正传递到方法的参数类型与该方法的参数类型不匹配时发生该错误。如果重载调用的方法，则重载的任何版本都不具有与正进行传递的参数类型相匹配的签名。

若要解决该问题，请执行以下操作：

  • 仔细检查正进行传递的参数的类型。确保它们与正调用的方法的参数类型相匹配。

  • 如果可以，请使用 System.Convert 类转换任何不匹配的参数。

  • 如果可以，请强制转换所有不匹配的参数使它们与该方法需要的类型相匹配。

  • 如果可以，请定义该方法的其他重载版本以便与正要发送的参数类型相匹配。

下面的示例生成 CS1502：

复制代码
// CS1502.cs
namespace x
{
   public class a
   {
      public a(char i)
      // try the following constructor instead
      // public a(int i)
      {
      }

      public static void Main()
      {
         a aa = new a(2222);   // CS1502
      }
   }
}

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 修订了错误消息文本。 | 内容 Bug 修复


编译器错误 CS1503

[image]

Visual C# 参考：错误和警告
编译器错误 CS1503
发送反馈意见

更新：2007 年 11 月

错误消息

参数“number”无法从 TypeA 转换为 TypeB [Argument 'number' cannot convert from TypeA to TypeB]

方法中某个参数的类型与实例化该类时所传递的类型不匹配。此错误通常和 CS1502 一起出现。有关如何解决此错误的讨论，请参见 CS1502。

修订记录

日期 | 修订 | 原因
2008 年 7 月 | 修订了错误消息文本。 | 内容 Bug 修复


编译器错误 CS1504

[image]

Visual C# 参考：错误和警告
编译器错误 CS1504
发送反馈意见

更新：2007 年 11 月

错误消息

未能打开源文件“file”(“reason”)

源文件未能由编译器打开或读取。该文件可能由其他应用程序锁定，或可能存在其他某种操作系统问题。此消息包含有关编译器无法打开或读取该文件的操作系统方面的原因。


编译器错误 CS1507

[image]

Visual C# 参考：错误和警告
编译器错误 CS1507
发送反馈意见

更新：2007 年 11 月

错误消息

生成模块时，无法链接资源文件“file”

在同一编译中 /linkresource 与 /target:module 一起使用，是不允许的。例如，下面的选项将生成 CS1507：

复制代码
csc /linkresource:rf.resource /target:module in.cs

不过允许嵌入资源 (/resource)。


编译器错误 CS1508

[image]

Visual C# 参考：错误和警告
编译器错误 CS1508
发送反馈意见

更新：2007 年 11 月

错误消息

此程序集中已使用了资源标识符“identifier”

在编译中，相同的标识符 (identifier) 被传递给了两个或多个 /resource 或 /linkresource 编译器选项。

例如，下面的选项将生成 CS1508：

复制代码
/resource:anyfile.bmp,DuplicatIdent /linkresource:a.bmp,DuplicatIdent


编译器错误 CS1509

[image]

Visual C# 参考：错误和警告
编译器错误 CS1509
发送反馈意见

更新：2007 年 11 月

错误消息

引用的文件“file”不是程序集；请使用“/addmodule”选项代替

在使用 /target:module 的编译中（没有程序集清单）产生的输出文件（输出文件 1）被指定为了 /reference。因此，不是程序集追加到当前程序的程序集，而是输出文件 1 中的元数据信息将添加到当前程序的程序集。


编译器错误 CS1510

[image]

Visual C# 参考：错误和警告
编译器错误 CS1510
示例 发送反馈意见

更新：2007 年 11 月

错误消息

ref 或 out 参数必须是可以赋值的变量

在方法调用中只有一个变量可以作为 ref 参数传递。ref 值与传递指针类似。

示例

下面的示例生成 CS1510：

复制代码
// CS1510.cs
public class C
{
   public static int j = 0;

   public static void M(ref int j)
   {
      j++;
   }

   public static void Main ()
   {
      M (ref 2);   // CS1510, can't pass a number as a ref parameter
      // try the following to resolve the error
      // M (ref j);
   }
}


编译器错误 CS1511

[image]

Visual C# 参考：错误和警告
编译器错误 CS1511
示例 发送反馈意见

更新：2007 年 11 月

错误消息

关键字“base”在静态方法中不可用

在静态方法中使用了 base 关键字。base 只能在实例构造函数、实例方法或实例访问器中调用。

示例

下面的示例生成 CS1511。

复制代码
// CS1511.cs
// compile with: /target:library
public class A
{
   public int j = 0;
}

class C : A
{
   public void Method()
   {
      base.j = 3;   // base allowed here
   }

   public static int StaticMethod()
   {
      base.j = 3;   // CS1511
      return 1;
   }
}


编译器错误 CS1512

[image]

Visual C# 参考：错误和警告
编译器错误 CS1512
发送反馈意见

更新：2007 年 11 月

错误消息

关键字“base”在当前上下文中不可用

在方法、属性或构造函数之外使用了 base 关键字。

下面的示例生成 CS1512：

复制代码
// CS1512.cs
using System;

class Base {}

class CMyClass : Base
{
    private String xx = base.ToString();   // CS1512
    // Try putting this initialization in the constructor instead:
    // public CMyClass()
    // {
    //    xx = base.ToString();
    // }

    public static void Main()
    {
        CMyClass z = new CMyClass();
    }
}


编译器错误 CS1513

[image]

Visual C# 参考：错误和警告
编译器错误 CS1513
发送反馈意见

更新：2007 年 11 月

错误消息

应输入 }

编译器要求输入缺失的右大括号 (})。

下面的示例生成 CS1513：

复制代码
// CS1513
namespace y   // CS1513, no close curly brace
{
   class x
   {
      public static void Main()
      {
      }
   }


编译器错误 CS1514

[image]

Visual C# 参考：错误和警告
编译器错误 CS1514
发送反馈意见

更新：2007 年 11 月

错误消息

应输入 {

编译器要求输入缺失的左大括号 ({)。

下面的示例生成 CS1514：

复制代码
// CS1514.cs
namespace x
// CS1514, no open curly brace


编译器错误 CS1515

[image]

Visual C# 参考：错误和警告
编译器错误 CS1515
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入“in”

在 foreach, in 语句中，缺少“in”部分。

示例

下面的示例生成 CS1515：

复制代码
using System;

class Driver
{
   static void Main()
   {
      int[] arr = new int[] {1, 2, 3};

      // try the following line instead
      // foreach (int x in arr)
      foreach (int x arr)      // CS1515, "in" is missing
      {
         Console.WriteLine(x);
      }
   }
}


编译器错误 CS1517

[image]

Visual C# 参考：错误和警告
编译器错误 CS1517
发送反馈意见

更新：2007 年 11 月

错误消息

无效的预处理器表达式

编译器遇到了无效的预处理器表达式。

有关更多信息，请参见预处理器指令。

下面的示例显示了一些有效和无效的预处理器表达式：

复制代码
// CS1517.cs
#if symbol      // OK
#endif
#if !symbol     // OK
#endif
#if (symbol)    // OK
#endif
#if true        // OK
#endif
#if false       // OK
#endif
#if 1           // CS1517
#endif
#if ~symbol     // CS1517
#endif
#if *           // CS1517
#endif

class x
{
   public static void Main()
   {
   }
}


编译器错误 CS1518

[image]

Visual C# 参考：错误和警告
编译器错误 CS1518
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入 class、delegate、enum、interface 或 struct

发现了在 namespace 中所不支持的声明。在命名空间中，编译器只接受类、结构、枚举、接口、命名空间和委托。

示例

下面的示例生成 CS1518：

复制代码
// CS1518.cs
namespace x
{
   sealed class c1 {};      // OK
   namespace f2 {};         // OK
   sealed f3 {};            // CS1518
   }


编译器错误 CS1519

[image]

Visual C# 参考：错误和警告
编译器错误 CS1519
发送反馈意见

更新：2007 年 11 月

错误消息

类、结构或接口成员声明中的标记“token”无效

任何在类型的前面包含无效修饰符的 class、结构或接口的成员声明都将生成该错误。若要修复此错误，请移除无效的修饰符。

下面的示例生成 CS1519：

复制代码
// CS1519.cs
public class IMyInterface
{
   checked void f4();   // CS1519
   // Remove "checked" from the line above.
   lock void f5();      // CS1519
   // Remove "lock" from the line above
   namespace;           // CS1519
   // The line above should be removed entirely.
   int i;               // OK
}


编译器错误 CS1520

[image]

Visual C# 参考：错误和警告
编译器错误 CS1520
发送反馈意见

更新：2007 年 11 月

错误消息

方法必须有返回类型

在类、结构或接口中声明的方法必须具有显式返回类型。

下面的示例生成 CS1520：

复制代码
// CS1520a.cs
public class x
{
   f7()   // CS1520, needs return type
   // try the following definition
   // void f7()
   {
   }

   public static void Main()
   {
   }
}

另外，当构造函数名称的大小写与类或结构声明的不同时，也可能会遇到该错误，如以下示例所示：

复制代码
// CS1520b.cs
public class Class1
{
   public class1()   // CS1520, incorrect case
   {
   }
   static void Main()
   {
   }
}


编译器错误 CS1521

[image]

Visual C# 参考：错误和警告
编译器错误 CS1521
发送反馈意见

更新：2007 年 11 月

错误消息

无效的基类型

base 类规范的格式不正确。

下面的示例生成 CS1521：

复制代码
// CS1521.cs
class CMyClass
{
   public static void Main()
   {
   }
}

class CMyClass1 : CMyClass     // OK
{
}

class CMyClass2 : CMyClass[]   // CS1521
{
}

class CMyClass3 : CMyClass*    // CS1521
{
}


编译器错误 CS1524

[image]

Visual C# 参考：错误和警告
编译器错误 CS1524
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入 catch 或 finally

try 块后面必须跟 catch 或 finally 块。

有关异常的更多信息，请参见 异常处理语句（C# 参考）。

示例

下面的示例生成 CS1524：

复制代码
// CS1524.cs
class x
{
    public static void Main()
    {
        try
        {
            // Code here
        }
        catch
        {
        }
        try
        {
            // Code here
        }
        finally
        {
        }
        try
        {
            // Code here
        }
    }     // CS1524, missing catch or finally
}


编译器错误 CS1525

[image]

Visual C# 参考：错误和警告
编译器错误 CS1525
发送反馈意见

更新：2007 年 11 月

错误消息

无效的表达式项“character”

编译器在表达式中检测到了无效字符。

下面的示例生成 CS1525：

复制代码
// CS1525.cs
class x
{
   public static void Main()
   {
      int i = 0;
      i = i +   // OK - identifier
      'c' +     // OK - character
      (5) +     // OK - parenthesis
      [ +       // CS1525, operator not a valid expression element
      throw +   // CS1525, keyword not allowed in expression
      void;     // CS1525, void not allowed in expression
   }
}

空白标签也可能会生成 CS1525，如以下示例所示：

复制代码
// CS1525b.cs
using System;
public class MyClass
{
   public static void Main()
   {
      goto FoundIt;
      FoundIt:      // CS1525
      // Uncomment the following line to resolve:
      // System.Console.Write("Hello");
   }
}


编译器错误 CS1526

[image]

Visual C# 参考：错误和警告
编译器错误 CS1526
示例 发送反馈意见

更新：2007 年 11 月

错误消息

new 表达式要求在类型后有 ()、[] 或 {}

未正确指定用于为对象动态分配内存的 new 运算符。

示例

下面的示例显示如何使用 new 为数组和对象分配空间。

复制代码
// CS1526.cs
public class y
{
   public static int i = 0;
   public int myi = 0;
}

public class z
{
   public static void Main()
   {
      y py = new y;   // CS1526
      y[] aoys = new y[10];   // Array of Ys

      for (int i = 0; i < aoys.Length; i++)
         aoys[i] = new y();   // an object of type y
   }
}


编译器错误 CS1527

[image]

Visual C# 参考：错误和警告
编译器错误 CS1527
发送反馈意见

更新：2007 年 11 月

错误消息

命名空间中定义的元素无法显式声明为 private、protected 或 protected internal

命名空间中的类型声明可以具有 public 或 internal 访问。如果没有指定可访问性，则默认是 internal。

下面的示例生成 CS1527：

复制代码
// CS1527.cs
namespace Sample
{
   private class C1 {};             // CS1527
   protected class C2 {};           // CS1527
   protected internal class C3 {};  // CS1527
}


编译器错误 CS1528

[image]

Visual C# 参考：错误和警告
编译器错误 CS1528
发送反馈意见

更新：2007 年 11 月

错误消息

应输入“;”或“=”(无法在声明中指定构造函数参数)

类引用的构造就像正在创建类的对象一样。例如，尝试将变量传递给构造函数。使用 new 运算符创建类的对象。

下面的示例生成 CS1528：

复制代码
// CS1528.cs
using System;

public class B
{
   public B(int i)
   {
      _i = i;
   }

   public void PrintB()
   {
      Console.WriteLine(_i);
   }

   private int _i;
}

public class mine
{
   public static void Main()
   {
      B b(3);   // CS1528, reference is not an object
      // try one of the following
      // B b;
      // or
      // B bb = new B(3);
      // bb.PrintB();
   }
}


编译器错误 CS1529

[image]

Visual C# 参考：错误和警告
编译器错误 CS1529
示例 发送反馈意见

更新：2007 年 11 月

错误消息

using 子句必须位于命名空间中除外部别名声明外所定义的所有其他元素之前

using 子句在命名空间中必须最先出现。

示例

下面的示例生成 CS1529：

复制代码
// CS1529.cs
namespace X
{
    namespace Subspace
    {
        using Microsoft;

        class SomeClass
        {
        };

        using Microsoft;      // CS1529, place before class definition
    }

    using System.Reflection;  // CS1529, place before namespace 'Subspace'
}

using System;                 // CS1529, place at the beginning of the file


编译器错误 CS1530

[image]

Visual C# 参考：错误和警告
编译器错误 CS1530
发送反馈意见

更新：2007 年 11 月

错误消息

不允许在命名空间中定义的元素上使用关键字“new”

没有必要在 namespace 中的任何构造上均指定 new 关键字。

下面的示例生成 CS1530：

复制代码
// CS1530.cs
namespace a
{
   new class i   // CS1530
   {
   }

   // try the following instead
   class ii
   {
      public static void Main()
      {
      }
   }
}


编译器错误 CS1534

[image]

Visual C# 参考：错误和警告
编译器错误 CS1534
发送反馈意见

更新：2007 年 11 月

错误消息

重载的二元运算符“operator”接受两个参数

二进制可重载的运算符的定义必须带两个参数。

下面的示例生成 CS1534：

复制代码
// CS1534.cs
class MyClass
{
   public static MyClass operator - (MyClass MC1, MyClass MC2, MyClass MC3)   // CS1534
   // try the following line instead
   // public static MyClass operator - (MyClass MC1, MyClass MC2)
   {
      return new MyClass();
   }

   public static int Main()
   {
      return 1;
   }
}


编译器错误 CS1535

[image]

Visual C# 参考：错误和警告
编译器错误 CS1535
示例 发送反馈意见

更新：2007 年 11 月

错误消息

重载的一元运算符“operator”接受一个参数

一元可重载的运算符的定义必须带一个参数。

示例

下面的示例生成 CS1535：

复制代码
// CS1535.cs
class MyClass
{
    // uncomment the method parameter to resolve CS1535
    public static MyClass operator ++ (/*MyClass MC1*/)   // CS1535
    {
        return new MyClass();
    }

    public static int Main()
    {
        return 1;
    }
}


编译器错误 CS1536

[image]

Visual C# 参考：错误和警告
编译器错误 CS1536
发送反馈意见

更新：2007 年 11 月

错误消息

参数类型 void 无效

指定 void 指针以外的 void 参数没有必要或者是无效的。

下面的示例生成 CS1536：

复制代码
// CS1536.cs
class a
{
   public static int x( void )   // CS1536
   // try the following line instead
   // public static int x()
   {
      return 0;
   }

   public static void Main()
   {
   }
}


编译器错误 CS1537

[image]

Visual C# 参考：错误和警告
编译器错误 CS1537
发送反馈意见

更新：2007 年 11 月

错误消息

using 别名“alias”以前在此命名空间中出现过

您两次将某个符号定义为了命名空间的别名。一个符号只能定义一次。

下面的示例生成 CS1537：

复制代码
// CS1537.cs
namespace x
{
   using System;
   using Object = System.Object;
   using Object = System.Object;   // CS1537, delete this line to resolve
   using System = System;
}


编译器错误 CS1540

[image]

Visual C# 参考：错误和警告
编译器错误 CS1540
发送反馈意见

更新：2007 年 11 月

错误消息

无法通过类型“type1”的限定符访问保护成员“member”；限定符必须是类型“type2”(或者从该类型派生的)

虽然派生的 class 可以访问其基类的受保护成员，但它无法通过基类的实例这样做。

下面的示例生成 CS1540：

复制代码
// CS1540.cs
public class Base
{
   protected void func()
   {
   }
}

public class Derived : Base
{
   public static void test(Base anotherInstance)
   // the method declaration could be changed as follows
   // public static void test(Derived anotherInstance)
   {
      anotherInstance.func();   // CS1540
   }
}

public class Tester : Derived
{
   public static void Main()
   {
      Base pBase = new Base();
      // the allocation could be changed as follows
      // Derived pBase = new Derived();
      test(pBase);
   }
}


编译器错误 CS1541

[image]

Visual C# 参考：错误和警告
编译器错误 CS1541
发送反馈意见

更新：2007 年 11 月

错误消息

无效的引用选项：“symbol”— 无法引用目录

编译器检测到指定目录而不是特定文件的尝试。例如，当使用 /reference 编译器选项时，必须指定文件；指定目录是不可能的。

例如，向编译器传递 /reference:c:\ 将生成 CS1541。


编译器错误 CS1542

[image]

Visual C# 参考：错误和警告
编译器错误 CS1542
发送反馈意见

更新：2007 年 11 月

错误消息

“dll”无法添加到此程序集中，因为它已经是程序集；请使用“/R”选项

用 /addmodule 编译器选项引用的文件不是用 /target:module 生成的；请在该编译中使用 /reference 引用文件。


编译器错误 CS1545

[image]

Visual C# 参考：错误和警告
编译器错误 CS1545
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性、索引器或事件“property”不受该语言支持；请尝试直接调用访问器方法“set accessor”或“get accessor”

代码正在使用具有非默认索引器的对象并试图使用索引语法。若要解决此错误，请调用属性的 get 或 set 访问器方法。

示例

复制代码
// CPP1545.cpp
// compile with: /clr /LD
// a Visual C++ program
using namespace System;
public ref struct Employee {
   Employee( String^ s, int d ) {}

   property String^ name {
      String^ get() {
         return nullptr;
      }
   }
};

public ref struct Manager {
   property Employee^ Report [String^] {
      Employee^ get(String^ s) {
         return nullptr;
      }

      void set(String^ s, Employee^ e) {}
   }
};

下面的示例生成 CS1545。

复制代码
// CS1545.cs
// compile with: /r:CPP1545.dll

class x {
   public static void Main() {
      Manager Ed = new Manager();
      Employee Bob = new Employee("Bob Smith", 12);
      Ed.Report[ Bob.name ] = Bob;   // CS1545
      Ed.set_Report( Bob.name, Bob);   // OK
   }
}


编译器错误 CS1546

[image]

Visual C# 参考：错误和警告
编译器错误 CS1546
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性、索引器或事件“property”不受该语言支持；请尝试直接调用访问器方法“accessor”

代码正在使用具有默认索引属性的对象，并试图使用索引语法。若要解决该错误，请调用属性的访问器方法。有关索引器和属性的更多信息，请参见索引器（C# 编程指南）。

下面的示例生成 CS1546。

示例

此代码示例由一个编译为 .dll 的 .cpp 文件和一个使用该 .dll 的 .cs 文件组成。下面是 .dll 文件的代码，它定义了一个将由 .cs 文件中的代码访问的属性。

复制代码
// CPP1546.cpp
// compile with: /clr /LD
using namespace System;
public ref class MCPP
{
public:
    property int Prop [int,int]
    {
        int get( int i, int b )
        {
            return i;
        }
    }
};

这是 C# 文件。

复制代码
// CS1546.cs
// compile with: /r:CPP1546.dll
using System;
public class Test
{
    public static void Main()
    {
        int i = 0;
        MCPP mcpp = new MCPP();
        i = mcpp.Prop(1,1); // CS1546
        // Try the following line instead:
        // i = mcpp.get_Prop(1,1);
    }
}


编译器错误 CS1547

[image]

Visual C# 参考：错误和警告
编译器错误 CS1547
发送反馈意见

更新：2007 年 11 月

错误消息

关键字“void”不能用于此上下文中

编译器检测到无效的 void 关键字使用。

下面的示例生成 CS1547：

复制代码
// CS1547.cs
public class MyClass
{
   void BadMethod()
   {
      void i;   // CS1547, cannot have variables of type void
   }

   public static void Main()
   {
   }
}


编译器错误 CS1548

[image]

Visual C# 参考：错误和警告
编译器错误 CS1548
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

对程序集“assembly”签名时加密失败 —“reason”

程序集签名失败时将发生 CS1548。这通常是由于无效的密钥文件名称、无效的密钥文件路径或已损坏的密钥文件而造成的。

若要对程序集进行完全签名，必须提供一个包含有关公钥和私钥信息的有效密钥文件。若要延迟为程序集签名，必须选中“仅延迟签名”复选框并提供一个包含有关公钥信息的有效密钥文件。为程序集延迟签名时不需要提供私钥。

有关更多信息，请参见 如何：对程序集进行签名 (Visual Studio)、/keyfile（指定强名称密钥文件）（C# 编译器选项） 和 /delaysign（延迟为程序集签名）（C# 编译器选项）。

创建程序集时，C# 编译器会调入一个称为“al.exe”的实用工具。如果在创建程序集时出现失败，al.exe 会报告失败的原因。请参见 Al.exe 工具错误和警告 并在该主题中搜索编译器在“原因”中报告的文本。

请参见

任务

如何：对程序集进行签名 (Visual Studio)


编译器错误 CS1549

[image]

Visual C# 参考：错误和警告
编译器错误 CS1549
发送反馈意见

更新：2007 年 11 月

错误消息

未找到合适的加密服务

未安装 RSA 兼容的加密服务提供程序。


编译器错误 CS1551

[image]

Visual C# 参考：错误和警告
编译器错误 CS1551
发送反馈意见

更新：2007 年 11 月

错误消息

索引器必须至少有一个参数

声明的索引器不带参数。

下面的示例生成 CS1551：

复制代码
// CS1551.cs
public class MyClass
{
   int intI;

   int this[]   // CS1551
   // try the following line instead
   // int this[int i]
   {
      get
      {
         return intI;
      }
      set
      {
         intI = value;
      }
   }

   public static void Main()
   {
   }
}


编译器错误 CS1552

[image]

Visual C# 参考：错误和警告
编译器错误 CS1552
示例 发送反馈意见

更新：2007 年 11 月

错误消息

数组类型说明符 [] 必须出现在参数名之前

数组类型说明符在数组声明中的位置是在变量名之后。

示例

下面的示例生成 CS1552：

复制代码
// CS1552.cs
public class C
{
    public static void Main(string args[])   // CS1552
    // try the following line instead
    // public static void Main(string [] args)
    {
    }
}


编译器错误 CS1553

[image]

Visual C# 参考：错误和警告
编译器错误 CS1553
发送反馈意见

更新：2007 年 11 月

错误消息

声明无效；请改用“modifier operator <dest-type> (...”

operator 的返回类型必须紧挨在参数列表之前，并且 modifier 为 implicit 或 explicit。

下面的示例生成 CS1553：

复制代码
// CS1553.cs
class MyClass
{
   public static int implicit operator (MyClass f)   // CS1553
   // try the following line instead
   // public static implicit operator int (MyClass f)
   {
      return 6;
   }

   public static void Main()
   {
   }
}


编译器错误 CS1554

[image]

Visual C# 参考：错误和警告
编译器错误 CS1554
发送反馈意见

更新：2007 年 11 月

错误消息

声明无效；请改用“<type> operator op (...”

用户定义的 operator 的返回类型必须出现在关键字运算符之前。

下面的示例生成 CS1554：

复制代码
// CS1554.cs
class MyClass
{
   public static operator ++ MyClass (MyClass f)    // CS1554
   // try the following line instead
   // public static MyClass operator ++ (MyClass f)
   {
      return new MyClass ();
   }

   public static void Main()
   {
   }
}


编译器错误 CS1555

[image]

Visual C# 参考：错误和警告
编译器错误 CS1555
发送反馈意见

更新：2007 年 11 月

错误消息

未能找到为 Main 方法指定的“class”

给 /main 编译器选项指定了类，但在源代码中没有找到该类的名称。


编译器错误 CS1556

[image]

Visual C# 参考：错误和警告
编译器错误 CS1556
发送反馈意见

更新：2007 年 11 月

错误消息

为 Main 方法指定的“construct”必须是有效的类或结构

给 /main 编译器选项传递的标识符不是类名。


编译器错误 CS1557

[image]

Visual C# 参考：错误和警告
编译器错误 CS1557
发送反馈意见

更新：2007 年 11 月

错误消息

无法对 Main 方法使用“class”，因为它在不同的输出文件中

为多输出文件编译中的一个输出文件指定了 /main 编译器选项。但是，在 /main 编译的源代码中没有找到该类，却在编译中的其他某个输出文件的源代码文件中找到了它。


编译器错误 CS1558

[image]

Visual C# 参考：错误和警告
编译器错误 CS1558
发送反馈意见

更新：2007 年 11 月

错误消息

“class”没有合适的静态 Main 方法

/main 编译器选项指定了一个在其中查找 Main 方法的类。但是，Main 方法未正确定义。

由于返回类型无效，下面的示例生成 CS1558。

复制代码
// CS1558.cs
// compile with: /main:MyNamespace.MyClass

namespace MyNamespace
{
   public class MyClass
   {
      public static float Main()
      {
         return 0.0; // CS1558 because the return type is a float.
      }
   }
}


编译器错误 CS1559

[image]

Visual C# 参考：错误和警告
编译器错误 CS1559
发送反馈意见

更新：2007 年 11 月

错误消息

无法对 Main 方法使用“object”，因为它是被导入的

给 /main 编译器选项指定的类无效，该类不能用作 Main 方法的位置。


编译器错误 CS1560

[image]

Visual C# 参考：错误和警告
编译器错误 CS1560
示例 发送反馈意见

更新：2007 年 11 月

错误消息

为预处理器指令指定的文件名无效。文件名太长，或者不是有效的文件名

用 #line（C# 参考） 指定的文件名超过了 _MAX_PATH（256 个字符），或者 #line 所在的行超过了 2000 个字符。

示例

下面的示例生成 CS1560。

复制代码
// cs1560.cs
using System;
class MyClass
{
   public static void Main()
   {
      Console.WriteLine("Normal line #1.");
      #line 21 "MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890.txt"   // CS1560
    }
}


编译器错误 CS1561

[image]

Visual C# 参考：错误和警告
编译器错误 CS1561
发送反馈意见

更新：2007 年 11 月

错误消息

输出的文件名太长或无效

输出文件名的长度不能超过 256 个字符，并且不得包含无效的字符，例如欧元符号、问号，或反斜杠及其他。


编译器错误 CS1562

[image]

Visual C# 参考：错误和警告
编译器错误 CS1562
发送反馈意见

更新：2007 年 11 月

错误消息

必须为没有源的输出指定 /out 选项

编译可以创建输出文件，但没有作为输入的源代码文件（输出文件的名称可以暗含在其中）。例如，您可能正在试图编译纯元数据文件或纯资源文件。

使用 /out 编译器选项指定输出文件的名称。


编译器错误 CS1563

[image]

Visual C# 参考：错误和警告
编译器错误 CS1563
发送反馈意见

更新：2007 年 11 月

错误消息

输出“output file”没有任何源文件

指定了 /out 编译器选项，但其后没有源代码文件。您既不应使用 /out，也不应在 /out 的后面指定源代码文件。


编译器错误 CS1564

[image]

Visual C# 参考：错误和警告
编译器错误 CS1564
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

指定的选项冲突: Win32 资源文件；Win32 清单。

如果使用 /Win32res 编译器选项，则必须在资源文件中包含自定义 Win32 清单（如果需要）。自定义 Win32 清单必须与 Win32 资源文件一起提供。只有在未指定 win32 资源文件时，才使用 /win32manifest 选项。

更正此错误

  • 将 win32 清单添加到 win32 资源文件中，并移除 /win32manifest 编译器选项。

示例

下面的示例在用 /Win32res 选项编译并且资源文件中未包含清单时会生成 CS1564。

复制代码
// cs1564.cs
// Compile with: /Win32res
public class Test
{
    static int Main(string[] args)
    {
        return 1;
    }
}

C# 3.0 编译器为所有二进制文件添加了默认的 win32Manifest。

请参见

参考

/win32manifest（导入自定义 Win32 清单文件）（C# 编译器选项）
/win32res（导入 Win32 资源文件）（C# 编译器选项）


编译器错误 CS1565

[image]

Visual C# 参考：错误和警告
编译器错误 CS1565
发送反馈意见

更新：2007 年 11 月

错误消息

指定的选项冲突：Win32 资源文件；Win32 图标

在同一编译中同时指定 /win32res 编译器选项和 /win32ico 编译器选项无效。


编译器错误 CS1566

[image]

Visual C# 参考：错误和警告
编译器错误 CS1566
发送反馈意见

更新：2007 年 11 月

错误消息

读取资源文件“file”时出错 —“reason”

编译器在将文件名传递给 /resource 编译器选项时遇到麻烦。


编译器错误 CS1567

[image]

Visual C# 参考：错误和警告
编译器错误 CS1567
发送反馈意见

更新：2007 年 11 月

错误消息

生成 Win32 资源时出错：“file”

您的编译或者使用了 /win32icon 编译器选项，或者未使用 /win32res 编译器选项，这导致编译器生成包含资源信息的文件，但编译器因磁盘空间不足或其他某些错误而无法创建文件。

如果您无法解决此文件生成问题，可以使用 /win32res，该编译器选项不生成包含资源信息的文件。


编译器错误 CS1569

[image]

Visual C# 参考：错误和警告
编译器错误 CS1569
示例 发送反馈意见

更新：2007 年 11 月

错误消息

生成 XML 文档文件“Filename”时出错(“reason”)

当试图将 XML 文档写入消息中提到的文件时，由于指定的原因，会出现错误。这可能是由于类似“未找到网络驱动器”或“访问被拒绝”等原因导致的。通常情况下，错误原因中会给出为更正此错误需采取哪些操作的建议。例如，如果错误中指出“访问被拒绝”，您应当验证对文件是否有写权限。

示例

复制代码
// 1569a.cs
// compile with: /doc:CS1569.xml
// post-build command: attrib +r CS1569.xml
class Test
{
   /// <summary>Test.</summary>
   public static void Main() {}
}

上面的示例生成一个当时设置为只读的 .xml 文件。此示例试图写入同一文件。下面的示例生成 CS1569。

复制代码
// CS1569.cs
// compile with: /doc:CS1569.xml
// CS1569 expected
class Test
{
   /// <summary>Test.</summary>
   public static void Main() {}
}


编译器错误 CS1575

[image]

Visual C# 参考：错误和警告
编译器错误 CS1575
发送反馈意见

更新：2007 年 11 月

错误消息

stackalloc 表达式在类型后要求有 []

使用 stackalloc 请求的分配大小必须在方括号中指定。

下面的示例生成 CS1575：

复制代码
// CS1575.cs
// compile with: /unsafe
public class MyClass
{
   unsafe public static void Main()
   {
      int *p = stackalloc int (30);   // CS1575
      // try the following line instead
      // int *p = stackalloc int [30];
   }
}


编译器错误 CS1576

[image]

Visual C# 参考：错误和警告
编译器错误 CS1576
发送反馈意见

更新：2007 年 11 月

错误消息

为 #line 指令指定的行号缺少或无效

编译器检测到传递给 #line 指令的值有错误。

下面的示例生成 CS1576：

复制代码
// CS1576.cs
public class MyClass
{
   static void Main()
   {
      #line "abc.sc"         // CS1576
      // try the following line instead
      //#line  101 "abc.sc"
      intt i;  // error will be reported on line 101
   }
}


编译器错误 CS1577

[image]

Visual C# 参考：错误和警告
编译器错误 CS1577
发送反馈意见

更新：2007 年 11 月

错误消息

程序集生成失败 - 原因

编译的程序集生成部分失败。有关更多信息，请参见 alink 实用工具的错误文档（Al.exe 工具错误和警告）。


编译器错误 CS1578

[image]

Visual C# 参考：错误和警告
编译器错误 CS1578
发送反馈意见

更新：2007 年 11 月

错误消息

应输入文件名、单行注释或行尾

在 #line 指令后只能有一个文件名（在双引号中）或单行注释。

下面的示例生成 CS1578：

复制代码
// CS1578.cs
class MyClass
{
   static void Main()
   {
      #line 101 abc.cs   // CS1578
      // try the following line instead
      //#line 101 "abc.cs"
      intt i;          // error will be reported on line 101
   }
}


编译器错误 CS1579

[image]

Visual C# 参考：错误和警告
编译器错误 CS1579
示例 发送反馈意见

更新：2007 年 11 月

错误消息

foreach 语句不能对“type1”类型的变量进行操作，因为“type2”不包含“identifier”的公共定义

若要使用 foreach 语句循环访问某个集合，该集合必须满足以下要求：

  • 它必须是一个接口、类或结构。

  • 它必须包含一个返回类型的公共 GetEnumerator 方法。

  • 返回类型必须包含一个名为 Current 的公共属性和一个名为 MoveNext 的公共方法。

  • 有关更多信息，请参见 如何：使用 foreach 访问集合类（C# 编程指南）。

示例

在本示例中，foreach 无法循环访问集合，因为 MyCollection 集合中没有 publicGetEnumerator 方法。

下面的示例生成 CS1579。

复制代码
// CS1579.cs
using System;
public class MyCollection
{
   int[] items;
   public MyCollection()
   {
      items = new int[5] {12, 44, 33, 2, 50};
   }

   // Delete the following line to resolve.
   MyEnumerator GetEnumerator()

   // Uncomment the following line to resolve:
   // public MyEnumerator GetEnumerator()
   {
      return new MyEnumerator(this);
   }

   // Declare the enumerator class:
   public class MyEnumerator
   {
      int nIndex;
      MyCollection collection;
      public MyEnumerator(MyCollection coll)
      {
         collection = coll;
         nIndex = -1;
      }

      public bool MoveNext()
      {
         nIndex++;
         return(nIndex < collection.items.GetLength(0));
      }

      public int Current
      {
         get
         {
            return(collection.items[nIndex]);
         }
      }
   }

   public static void Main()
   {
      MyCollection col = new MyCollection();
      Console.WriteLine("Values in the collection are:");
      foreach (int i in col)   // CS1579
      {
         Console.WriteLine(i);
      }
   }
}


编译器错误 CS1583

[image]

Visual C# 参考：错误和警告
编译器错误 CS1583
发送反馈意见

更新：2007 年 11 月

错误消息

“file”不是有效的 Win32 资源文件

传递给 /win32res 编译器选项的文件不是有效的资源文件。


编译器错误 CS1585

[image]

Visual C# 参考：错误和警告
编译器错误 CS1585
发送反馈意见

更新：2007 年 11 月

错误消息

成员修饰符“keyword”必须位于成员类型和名称之前

方法签名中的访问说明符没有出现在正确的位置。

下面的示例生成 CS1585：

复制代码
// CS1585.cs
public class Class1
{
   public void static Main(string[] args)   // CS1585
   // try the following line instead
   // public static void Main(string[] args)
   {
   }
}


编译器错误 CS1586

[image]

Visual C# 参考：错误和警告
编译器错误 CS1586
发送反馈意见

更新：2007 年 11 月

错误消息

数组创建必须有数组大小或数组初始值设定项

数组未正确声明。

下面的示例生成 CS1586：

复制代码
// CS1586.cs
using System;
class MyClass
{
   public static void Main()
   {
      int[] a = new int[];   // CS1586
      // try the following line instead
      int[] b = new int[5];
   }
}


编译器错误 CS1588

[image]

Visual C# 参考：错误和警告
编译器错误 CS1588
发送反馈意见

更新：2007 年 11 月

错误消息

无法确定公共语言运行库目录 --“reason”

编译器无法检索到 .NET Framework 公共语言运行库的安装目录。该错误指示公共语言运行库未正确安装。


编译器错误 CS1593

[image]

Visual C# 参考：错误和警告
编译器错误 CS1593
发送反馈意见

更新：2007 年 11 月

错误消息

委托“del”不接受“number”参数

传递给 delegate 调用的参数数目与委托声明中的参数数目不一致。

下面的示例生成 CS1593：

复制代码
// CS1593.cs
using System;
delegate string func(int i);   // declare delegate

class a
{
   public static void Main()
   {
      func dt = new func(z);
      x(dt);
   }

   public static string z(int j)
   {
      Console.WriteLine(j);
      return j.ToString();
   }

   public static void x(func hello)
   {
      hello(8, 9);   // CS1593
      // try the following line instead
      // hello(8);
   }
}


编译器错误 CS1594

[image]

Visual C# 参考：错误和警告
编译器错误 CS1594
发送反馈意见

更新：2007 年 11 月

错误消息

委托“delegate”有一些无效参数

传递给 delegate 调用的参数类型与委托声明中的参数类型不一致。

下面的示例生成 CS1594：

复制代码
// CS1594.cs
using System;
delegate string func(int i);   // declare delegate

class a
{
   public static void Main()
   {
      func dt = new func(z);
      x(dt);
   }

   public static string z(int j)
   {
      Console.WriteLine(j);
      return j.ToString();
   }

   public static void x(func hello)
   {
      hello("8");   // CS1594
      // try the following line instead
      // hello(8);
   }
}


编译器错误 CS1597

[image]

Visual C# 参考：错误和警告
编译器错误 CS1597
发送反馈意见

更新：2007 年 11 月

错误消息

方法或访问器块后面的分号无效

不需要（或不允许）使用分号结束方法或访问器块。

下面的示例生成 CS1597：

复制代码
// CS1597.cs
class TestClass
{
   public static void Main()
   {
   };   // CS1597, remove semicolon
}


编译器错误 CS1599

[image]

Visual C# 参考：错误和警告
编译器错误 CS1599
发送反馈意见

更新：2007 年 11 月

错误消息

方法或委托不能返回“type”类型

.NET Framework 类库中的某些类型（例如 TypedReference、RuntimeArgumentHandle 和 ArgIterator）不能用作返回类型，因为它们有可能会被用来执行不安全的操作。

下面的示例生成 CS1599：

复制代码
// CS1599.cs
using System;

class MyClass
{
   public static void Main()
   {
   }

   public TypedReference Test1()   // CS1599
   {
      return null;
   }

   public ArgIterator Test2()   // CS1599
   {
      return null;
   }
}


编译器错误 CS1600

[image]

Visual C# 参考：错误和警告
编译器错误 CS1600
发送反馈意见

更新：2007 年 11 月

错误消息

编译被用户取消

当使用 Visual Studio IDE 时，取消了使用 C# 编译器的编译。


编译器错误 CS1601

[image]

Visual C# 参考：错误和警告
编译器错误 CS1601
发送反馈意见

更新：2007 年 11 月

错误消息

方法或委托参数不能是“type”类型

.NET Framework 类库中的某些类型（例如 TypedReference、RuntimeArgumentHandle 和 ArgIterator）不能用作 ref 或 out 参数，因为它们有可能会被用来执行不安全的操作。

下面的示例生成 CS1601：

复制代码
// CS1601.cs
using System;

class MyClass
{
   public void Test1 (ref TypedReference t)   // CS1601
   {
   }

   public void Test2 (out ArgIterator t)   // CS1601
   {
   }
}


编译器错误 CS1604

[image]

Visual C# 参考：错误和警告
编译器错误 CS1604
发送反馈意见

更新：2007 年 11 月

错误消息

无法给“variable”赋值，因为它是只读的

对只读变量进行了赋值。若要避免此错误，不要在此上下文中给此变量赋值或试图修改此变量。


编译器错误 CS1605

[image]

Visual C# 参考：错误和警告
编译器错误 CS1605
发送反馈意见

更新：2007 年 11 月

错误消息

无法将“var”作为 ref 或 out 参数传递，因为它是只读的

作为 ref 或 out 参数传递的变量应在调用的方法中修改。因此，不可能将只读参数作为 ref 或 out 传递。


编译器错误 CS1606

[image]

Visual C# 参考：错误和警告
编译器错误 CS1606
发送反馈意见

更新：2007 年 11 月

错误消息

程序集签名失败；不能对输出签名 — reason

产生了程序集，但当编译器试图完成对它的签名时出现失败。


编译器错误 CS1608

[image]

Visual C# 参考：错误和警告
编译器错误 CS1608
发送反馈意见

更新：2007 年 11 月

错误消息

C# 类型上不允许有 Required 属性

在 C# 代码中定义的类型上不允许 RequiredAttributeAttribute。


编译器错误 CS1609

[image]

Visual C# 参考：错误和警告
编译器错误 CS1609
示例 发送反馈意见

更新：2007 年 11 月

错误消息

修饰符不能放置在事件访问器声明中

修饰符只能放置在事件声明中，不能放置在事件访问器声明中。有关更多信息，请参见使用属性（C# 编程指南）。

示例

下面的示例生成 CS1609。

复制代码
// CS1609.cs
// compile with: /target:library
delegate int Del();
class A
{
   public event Del MyEvent
   {
      private add {}   // CS1609
      // try the following line instead
      // add {}
      remove {}
   }
}


编译器错误 CS1611

[image]

Visual C# 参考：错误和警告
编译器错误 CS1611
发送反馈意见

更新：2007 年 11 月

错误消息

params 参数不能声明为 ref 或 out

关键字 ref 或 out 不能与 params 关键字一起使用。

下面的示例生成 CS1611：

复制代码
// CS1611.cs
public class MyClass
{
   public static void Test(params ref int[] a)   // CS1611, remove ref
   {
   }

   public static void Main()
   {
      Test(1);
   }
}


编译器错误 CS1612

[image]

Visual C# 参考：错误和警告
编译器错误 CS1612
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法修改“expression”的返回值，因为它不是变量

试图修改的值类型是中间表达式的结果。该值是不持久的，因此它将保持不变。

若要解决该错误，请将表达式的结果存储在中间值中，或对中间表达式使用引用类型。

示例

下面的代码生成错误 CS1612。

复制代码
// CS1612.cs
public struct MyStruct
{
    public int Width;
}

public class ListView
{
    public MyStruct Size
    {
        get { return new MyStruct(); }
    }
}

public class MyClass
{
    public MyClass()
    {
        ListView lvi;
        lvi = new ListView();
        lvi.Size.Width = 33;  // CS1612
        // Use this instead:
        // MyStruct temp = lvi.Size;
        // temp.Width = 33;
    }

    public static void Main() {}
}


编译器错误 CS1613

[image]

Visual C# 参考：错误和警告
编译器错误 CS1613
发送反馈意见

更新：2007 年 11 月

错误消息

无法找到接口“interface”的托管 coclass 包装类“class”（是否缺少程序集引用？）

试图从接口实例化 COM 对象。该接口具有 ComImport 和 CoClass 属性，但编译器无法找到为 CoClass 属性提供的类型。

若要解决此错误，可以尝试使用下列方法之一：

  • 向具有该 coclass 的程序集添加引用（多数情况下，该接口和该 coclass 应该在同一程序集中）。有关信息，请参见 /reference 或“添加引用”对话框。

  • 修复该接口上的 CoClass 属性。

下面的示例演示了 CoClassAttribute 的正确用法：

复制代码
// CS1613.cs
using System;
using System.Runtime.InteropServices;

[Guid("1FFD7840-E82D-4268-875C-80A160C23296")]
[ComImport()]
[CoClass(typeof(A))]
public interface IA{}
public class A : IA {}

public class AA
{
   public static void Main()
   {
      IA i;
      i = new IA(); // This is equivalent to new A().
                    // because of the CoClass attribute on IA
   }
}


编译器错误 CS1614

[image]

Visual C# 参考：错误和警告
编译器错误 CS1614
发送反馈意见

更新：2007 年 11 月

错误消息

“name”在“attribute1”和“attribute2”之间不明确。请使用“@attribute”或“attributeAttribute”

编译器遇到了意义不明确的属性指定。

为简便起见，C# 编译器允许将 ExampleAttribute 指定为 [Example]。不过，如果名为 Example 的属性类与 ExampleAttribute 同时存在，则会引起多义性，因为编译器无法判断 [Example] 是指 Example 属性还是指 ExampleAttribute 属性。要解决此问题，请对 Example 属性使用 [@Example]，而对 ExampleAttribute 使用 [ExampleAttribute]。

下面的示例生成 CS1614：

复制代码
// CS1614.cs
using System;

// Both of the following classes are valid attributes with valid
// names (MySpecial and MySpecialAttribute). However, because the lookup
// rules for attributes involves auto-appending the 'Attribute' suffix
// to the identifier, these two attributes become ambiguous; that is,
// if you specify MySpecial, the compiler can't tell if you want
// MySpecial or MySpecialAttribute.

public class MySpecial : Attribute {
   public MySpecial() {}
}

public class MySpecialAttribute : Attribute {
   public MySpecialAttribute() {}
}

class MakeAWarning {
   [MySpecial()] // CS1614
                 // Ambiguous: MySpecial or MySpecialAttribute?
   public static void Main() {
   }

   [@MySpecial()] // This isn't ambiguous, it binds to the first attribute above.
   public static void NoWarning() {
   }

   [MySpecialAttribute()] // This isn't ambiguous, it binds to the second attribute above.
   public static void NoWarning2() {
   }

   [@MySpecialAttribute()] // This is also legal.
   public static void NoWarning3() {
   }
}


编译器错误 CS1615

[image]

Visual C# 参考：错误和警告
编译器错误 CS1615
发送反馈意见

更新：2007 年 11 月

错误消息

参数“number”不应与关键字“keyword”一起传递

当函数没有接受 ref 或 out 参数作为该参数时，使用了关键字 ref 或 out 中的一个。若要解决此错误，请移除不正确的关键字，并使用与函数声明匹配的正确的关键字（如果有的话）。

下面的示例生成 CS1615：

复制代码
// CS1615.cs
class C
{
   public void f(int i) {}
   public static void Main()
   {
      int i = 1;
      f(ref i);  // CS1615
   }
}


编译器错误 CS1617

[image]

Visual C# 参考：错误和警告
编译器错误 CS1617
发送反馈意见

更新：2007 年 11 月

错误消息

选项“option”对 /langversion 无效；必须是 ISO-1、ISO-2 或 Default

如果您使用了 /langversion 命令行开关或项目设置，但没有指定一个有效的语言选项，则会出现此错误。要解决此错误，请检查命令行语法或项目设置，将其更改为其中一个列出的选项。

例如，使用 csc /langversion:ISO 编译将生成错误 CS1617。


编译器错误 CS1618

[image]

Visual C# 参考：错误和警告
编译器错误 CS1618
发送反馈意见

更新：2007 年 11 月

错误消息

无法用“method”创建委托，因为它具有 Conditional 属性

无法用条件方法创建委托，因为在某些版本中可能不存在该方法。

下面的示例生成 CS1618：

复制代码
// CS1618.cs
using System;
using System.Diagnostics;

delegate void del();

class MakeAnError {
   public static void Main() {
      del d = new del(ConditionalMethod);   // CS1618
      // Invalid because on builds where DEBUG is not set,
      // there will be no "ConditionalMethod".
   }
   // To fix the error, remove the next line:
   [Conditional("DEBUG")]
   public static void ConditionalMethod()
   {
      Console.WriteLine("Do something only in debug");
   }
}


编译器错误 CS1619

[image]

Visual C# 参考：错误和警告
编译器错误 CS1619
发送反馈意见

更新：2007 年 11 月

错误消息

无法创建临时文件“filename”－ reason

由于特定的原因（例如，磁盘已满），编译器无法创建临时文件。


编译器错误 CS1620

[image]

Visual C# 参考：错误和警告
编译器错误 CS1620
发送反馈意见

更新：2007 年 11 月

错误消息

参数“number”必须与关键字“keyword”一起传递

如果您要将参数传递给带有 ref 或 out 参数的函数，但您在调用时没有包括 ref 或 out 关键字，或者包括了错误的关键字，就会发生此错误。错误文本中指出了应使用的正确关键字以及导致失败的参数。

下面的示例生成 CS1620：

复制代码
// CS1620.cs
class C
{
    void f(ref int i) {}
    public static void Main()
    {
        int x = 1;
        f(out x);  // CS1620 – f takes a ref parameter, not an out parameter
        // Try this line instead:
        // f(ref x);
    }
}


编译器错误 CS1621

[image]

Visual C# 参考：错误和警告
编译器错误 CS1621
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能在匿名方法或 lambda 表达式内使用 yield 语句

yield 语句不能存在于迭代器的匿名方法块中。

示例

下面的示例生成 CS1621：

复制代码
// CS1621.cs

using System.Collections;

delegate object MyDelegate();

class C : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        MyDelegate d = delegate
        {
            yield return this; // CS1621
            return this;
        };
        d();
        // Try this instead:
        // MyDelegate d = delegate { return this; };
        // yield return d();
    }

    public static void Main()
    {
    }
}


编译器错误 CS1622

[image]

Visual C# 参考：错误和警告
编译器错误 CS1622
发送反馈意见

更新：2007 年 11 月

错误消息

无法从迭代器返回值。请使用 yield return 语句返回值，或使用 yield break 语句结束迭代。

迭代器是一个特殊的函数，它通过 yield 语句而不是 return 语句返回值。有关更多信息，请参见迭代器。

下面的示例生成 CS1622：

复制代码
// CS1622.cs
// compile with: /target:library
using System.Collections;

class C : IEnumerable
{
   public IEnumerator GetEnumerator()
   {
      return (IEnumerator) this;  // CS1622
      yield return this;   // OK
   }
}


编译器错误 CS1623

[image]

Visual C# 参考：错误和警告
编译器错误 CS1623
示例 发送反馈意见

更新：2007 年 11 月

错误消息

迭代器不能有 ref 或 out 参数

如果迭代器方法带有 ref 或 out 参数，则会发生此错误。若要避免此错误，请从方法签名中移除 ref 或 out 关键字。

示例

下面的示例生成 CS1623：

复制代码
// CS1623.cs
using System.Collections;

class C : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        yield return 0;
    }

    // To resolve the error, remove ref
    public IEnumerator GetEnumerator(ref int i)  // CS1623
    {
        yield return i;
    }

    // To resolve the error, remove out
    public IEnumerator GetEnumerator(out float f)  // CS1623
    {
        f = 0.0F;
        yield return f;
    }
}


编译器错误 CS1624

[image]

Visual C# 参考：错误和警告
编译器错误 CS1624
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“accessor”体不能是迭代器块，因为“type”不是迭代器接口类型

当使用了迭代器访问器，但返回类型不是以下迭代器接口类型之一时会发生此错误：IEnumerable、IEnumerable<(Of <(T>)>)、IEnumerator 和 IEnumerator<(Of <(T>)>)。若要避免此错误，请将其中一个迭代器接口类型用作返回类型。

示例

下面的示例生成 CS1624：

复制代码
// CS1624.cs
using System;
using System.Collections;

class C
{
    public int Iterator
    // Try this instead:
    // public IEnumerable Iterator
    {
        get  // CS1624
        {
            yield return 1;
        }
    }
}


编译器错误 CS1625

[image]

Visual C# 参考：错误和警告
编译器错误 CS1625
发送反馈意见

更新：2007 年 11 月

错误消息

无法在 finally 子句体内生成

在 finally 子句体内不允许出现 yield 语句。若要避免此错误，请将 yield 语句移到 finally 子句的外部。

下面的示例生成 CS1625：

复制代码
// CS1625.cs
using System.Collections;

class C : IEnumerable
{
   public IEnumerator GetEnumerator()
   {
      try
      {
      }
      finally
      {
        yield return this;  // CS1625
      }
   }
}

public class CMain
{
   public static void Main() { }
}


编译器错误 CS1626

[image]

Visual C# 参考：错误和警告
编译器错误 CS1626
发送反馈意见

更新：2007 年 11 月

错误消息

无法在包含 catch 子句的 try 块体内生成值

如果有一个 catch 子句与 try 块关联，该 try 块内不允许有 yield 语句。若要避免此错误，请将 yield 语句移到 catch 子句的外部。

下面的示例生成 CS1626：

复制代码
// CS1626.cs
using System.Collections;

class C : IEnumerable
{
   public IEnumerator GetEnumerator()
   {
      try
      {
         yield return this;  // CS1626
      }
      catch
      {

      }
   }
}

public class CMain
{
   public static void Main() { }
}


编译器错误 CS1627

[image]

Visual C# 参考：错误和警告
编译器错误 CS1627
发送反馈意见

更新：2007 年 11 月

错误消息

yield return 后应为表达式

如果使用 yield 时未包含表达式，则会发生此错误。若要避免此错误，请在语句中插入适当的表达式。

下面的示例生成 CS1627：

复制代码
// CS1627.cs
using System.Collections;

class C : IEnumerable
{
   public IEnumerator GetEnumerator()
   {
      yield return;   // CS1627
      // To resolve, add the following line:
      // yield return 0;
   }
}

public class CMain
{
   public static void Main() { }
}


编译器错误 CS1628

[image]

Visual C# 参考：错误和警告
编译器错误 CS1628
发送反馈意见

更新：2007 年 11 月

错误消息

不能在匿名方法、lambda 表达式或查询表达式内使用 ref 或 out 参数“parameter”

如果您在匿名方法块内使用 ref 或 out 参数，则会出现此错误。若要避免此错误，请使用局部变量或某个其他的构造。

下面的示例生成 CS1628：

复制代码
// CS1628.cs

delegate int MyDelegate();

class C
{
  public static void F(ref int i)
  {
      MyDelegate d = delegate { return i; };  // CS1628
      // Try this instead:
      // int tmp = i;
      // MyDelegate d = delegate { return tmp; };
  }

  public static void Main()
  {

  }
}


编译器错误 CS1629

[image]

Visual C# 参考：错误和警告
编译器错误 CS1629
发送反馈意见

更新：2007 年 11 月

错误消息

迭代器中不能出现不安全的代码

C# 语言规范不允许迭代器中出现不安全的代码。

下面的示例生成 CS1629：

复制代码
// CS1629.cs
// compile with: /unsafe
using System.Collections.Generic;
class C
{
   IEnumerator<int> IteratorMeth() {
      int i;
      unsafe  // CS1629
      {
         int *p = &i;
         yield return *p;
      }
   }
}


编译器错误 CS1630

[image]

Visual C# 参考：错误和警告
编译器错误 CS1630
发送反馈意见

更新：2007 年 11 月

错误消息

/errorreport 的选项“option”无效；必须是 prompt、send、queue 或 none

命令行选项 /errorreport 后面必须跟随 prompt、send、queue 或 none，用于指定当发生内部编译器错误时要采取的操作。


编译器错误 CS1631

[image]

Visual C# 参考：错误和警告
编译器错误 CS1631
发送反馈意见

更新：2007 年 11 月

错误消息

无法在 catch 子句体内生成值

不允许从 catch 子句体内使用 yield 语句。若要避免此错误，请将 yield 语句移到 catch 子句体的外面。

下面的示例生成 CS1631：

复制代码
// CS1631.cs
using System;
using System.Collections;

public class C : IEnumerable
{
   public IEnumerator GetEnumerator()
   {
      try
      {
      }
      catch(Exception e)
      {
        yield return this;  // CS1631
      }
   }

   public static void Main()
   {
   }
}


编译器错误 CS1632

[image]

Visual C# 参考：错误和警告
编译器错误 CS1632
发送反馈意见

更新：2007 年 11 月

错误消息

控制不能离开匿名方法体或 lambda 表达式体

如果跳转语句（break、goto、continue 等）试图将控制移到匿名方法块以外，则会发生此错误。匿名方法块是一个函数体，只能通过返回语句或到达块的末尾退出。

下面的示例生成 CS1632：

复制代码
// CS1632.cs
// compile with: /target:library
delegate void MyDelegate();
class MyClass
{
   public void Test()
   {
      for (int i = 0 ; i < 5 ; i++)
      {
         MyDelegate d = delegate {
            break;   // CS1632
          };
      }
   }
}


编译器错误 CS1637

[image]

Visual C# 参考：错误和警告
编译器错误 CS1637
示例 发送反馈意见

更新：2007 年 11 月

错误消息

迭代器不能有不安全的参数或 yield 类型

检查迭代器的参数列表和任何 yield 语句的类型以验证您没有使用任何不安全的类型。

示例

下面的示例生成 CS1637：

复制代码
// CS1637.cs
// compile with: /unsafe
using System.Collections;

public unsafe class C
{
    public IEnumerator Iterator1(int* p)  // CS1637
    {
        yield return null;
    }
}


编译器错误 CS1638

[image]

Visual C# 参考：错误和警告
编译器错误 CS1638
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“identifier”是一个保留标识符，不能在使用 ISO 语言版本模式时使用

当 ISO 语言兼容性选项由 /langversion 编译器开关指定时，任何含有双下划线的标识符都会产生此错误。若要避免此错误，请消除任何含有双下划线的标识符，或者不使用 ISO-1 语言版本选项。

示例

下面的示例生成 CS1638：

复制代码
// CS1638.cs
// compile with: /langversion:ISO-1
class bad__identifer // CS1638 (double underscores are not ISO compliant)
{
}

// Try this instead:
//class GoodIdentifier
//{
//}

class CMain
{
    public static void Main() { }
}

请参见

参考

/langversion（符合规范的语法）（C# 编译器选项）


编译器错误 CS1639

[image]

Visual C# 参考：错误和警告
编译器错误 CS1639
发送反馈意见

更新：2007 年 11 月

错误消息

接口“interface”的托管 coclass 包装类签名“signature”不是有效的类名签名。

当接口的元数据无效时会发生此错误。如果此接口是使用 C# 或受支持的 .NET 语言生成的，则永远不会发生此错误。请与接口的供应商核实或验证接口程序集是否已成功生成。


编译器错误 CS1640

[image]

Visual C# 参考：错误和警告
编译器错误 CS1640
示例 发送反馈意见

更新：2007 年 11 月

错误消息

foreach 语句不能对“type”类型的变量进行操作，因为它实现“interface”的多个实例化，请尝试强制转换为特定的接口实例化

此类型从 IEnumerator<T> 的两个或更多的实例继承，这意味着此类型不存在 foreach 可以使用的唯一枚举。指定 IEnumerator<T> 的类型，或使用其他循环构造。

示例

下面的示例生成 CS1640：

复制代码
// CS1640.cs

using System;
using System.Collections;
using System.Collections.Generic;

public class C : IEnumerable, IEnumerable<int>, IEnumerable<string>
{
    IEnumerator<int> IEnumerable<int>.GetEnumerator()
    {
        yield break;
    }

    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
        yield break;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return (IEnumerator)((IEnumerable<string>)this).GetEnumerator();
    }
}

public class Test
{
    public static int Main()
    {
        foreach (int i in new C()){}    // CS1640

        // Try specifing the type of IEnumerable<T>
        // foreach (int i in (IEnumerable<int>)new C()){}
        return 1;
    }
}


编译器错误 CS1641

[image]

Visual C# 参考：错误和警告
编译器错误 CS1641
发送反馈意见

更新：2007 年 11 月

错误消息

固定大小缓冲区字段的字段名称后面必须有数组大小说明符

与规则数组不同，固定大小缓冲区要求在声明点指定固定大小。若要解决此错误，请添加一个正整数字符或正整数常数，并在标识符后面加上方括号。

下面的示例生成 CS1641：

复制代码
// CS1641.cs
// compile with: /unsafe /target:library
unsafe struct S {
   fixed int [] a;  // CS1641

   // OK
   fixed int b [10];
   const int c = 10;
   fixed int d [c];
}


编译器错误 CS1642

[image]

Visual C# 参考：错误和警告
编译器错误 CS1642
示例 发送反馈意见

更新：2007 年 11 月

错误消息

固定大小的缓冲区字段只能是结构的成员。

如果您在 class 而不是 struct 中使用固定大小的缓冲区，将会出现此错误。若要解决此错误，请将 class 更改为 struct 或者将字段声明为普通数组。

示例

下面的示例生成 CS1642。

复制代码
// CS1642.cs
// compile with: /unsafe /target:library
unsafe class C
{
   fixed int a[10];   // CS1642
}

unsafe struct D
{
    fixed int a[10];
}

unsafe class E
{
   public int[] a = null;
}


编译器错误 CS1643

[image]

Visual C# 参考：错误和警告
编译器错误 CS1643
示例 发送反馈意见

更新：2007 年 11 月

错误消息

在“type!”类型的方法中，并非所有代码路径都会返回一个值

如果一个委托体没有返回语句，或者它的返回语句无法由编译器访问，则会出现此错误。在下面的示例中，编译器不尝试预测分支条件的结果以验证匿名方法块是否始终返回一个值。

示例

下面的示例生成 CS1643：

复制代码
// CS1643.cs
delegate int MyDelegate();

class C
{
    static void Main()
    {
        MyDelegate d = delegate
        {                 // CS1643
            int i = 0;
            if (i == 0)
                return 1;
        };
    }
}


编译器错误 CS1644

[image]

Visual C# 参考：错误和警告
编译器错误 CS1644
发送反馈意见

更新：2007 年 11 月

错误消息

功能“feature”不是标准化 ISO C# 语言规范的一部分，有些编译器可能不接受它

如果您指定了 /langversion 选项 ISO-1，而您编译的代码所使用的功能不是 ISO 1.0 标准的组成部分，则会发生此错误。若要解决此错误，请不要对任何 C# 2.0 编译器功能（如泛型或匿名方法）使用 ISO-1 兼容性选项。

下面的示例生成 CS1644：

复制代码
// CS1644.cs
// compile with: /langversion:ISO-1 /target:library
class C<T> {}   // CS1644


编译器错误 CS1646

[image]

Visual C# 参考：错误和警告
编译器错误 CS1646
发送反馈意见

更新：2007 年 11 月

错误消息

关键字、标识符或字符串应位于逐字说明符 @ 之后

请参见逐字说明符“@”用法的字符串文本。逐字说明符只允许在字符串、关键字或标识符之前使用。若要解决此错误，请从任何不适当的位置移除 @ 符，或者添加所需要的字符串、关键字或标识符。

下面的示例生成 CS1646：

复制代码
// CS1646
class C
{
   int i = @5;  // CS1646
   // Try this line instead:
   // int i = 5;
}


编译器错误 CS1647

[image]

Visual C# 参考：错误和警告
编译器错误 CS1647
发送反馈意见

更新：2007 年 11 月

错误消息

表达式太长或太复杂，无法在“code”附近编译

编译器处理您的代码时出现堆栈溢出。要解决此错误，请简化您的代码。如果代码是有效的，请与产品支持联系。


编译器错误 CS1648

[image]

Visual C# 参考：错误和警告
编译器错误 CS1648
发送反馈意见

更新：2007 年 11 月

错误消息

无法修改只读字段“identifier”的成员（在构造函数中或在变量初始值设定项中除外）

当您试图修改一个只读字段的成员时，由于不允许修改只读字段的成员，将出现此错误。若要解决此错误，请将只读字段的赋值限定为构造函数或变量初始值设定项，或者从字段的声明中移除 readonly 关键字。

下面的示例生成 CS1648：

复制代码
// CS1648.cs
public struct Inner
  {
    public int i;
  }

class Outer
{
  public readonly Inner inner = new Inner();
}

class D
{
   static void Main()
   {
      Outer outer = new Outer();
      outer.inner.i = 1;  // CS1648
   }
}


编译器错误 CS1649

[image]

Visual C# 参考：错误和警告
编译器错误 CS1649
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法向只读字段“identifier”的成员传递 ref 或 out 参数（构造函数中除外）

如果将变量作为 ref 或 out 参数传递给一个属于 readonly 字段的成员的函数，则会发生此错误。由于该函数可能修改 ref 和 out 参数，所以这是不允许的。若要解决此错误，请移除字段上的 readonly 关键字，或者不要将 readonly 字段的成员传递给该函数。例如，您可能尝试创建一个可以被修改的临时变量，并将该临时变量作为 ref 参数传递，如下面的示例所示。

示例

下面的示例生成 CS1649：

复制代码
// CS1649.cs
public struct Inner
    {
        public int i;
    }

class Outer
{
    public readonly Inner inner = new Inner();
}

class D
{
    static void f(ref int iref)
    {
    }

    static void Main()
    {
        Outer outer = new Outer();
        f(ref outer.inner.i);  // CS1649
        // Try this code instead:
        // int tmp = outer.inner.i;
        // f(ref tmp);
    }
}


编译器错误 CS1650

[image]

Visual C# 参考：错误和警告
编译器错误 CS1650
发送反馈意见

更新：2007 年 11 月

错误消息

无法对静态只读字段“identifier”的字段赋值(在静态构造函数或变量初始值设定项中除外)

当您试图修改只读静态字段的成员时，由于不允许修改这类成员，将出现此错误。若要解决此错误，请将只读字段的赋值限定为构造函数或变量初始值设定项，或者从字段的声明中移除 readonly 关键字。

复制代码
// CS1650.cs
public struct Inner
{
    public int i;
}

class Outer
{
    public static readonly Inner inner = new Inner();
}

class D
{
    static void Main()
    {
        Outer.inner.i = 1;  // CS1650
    }
}


编译器错误 CS1651

[image]

Visual C# 参考：错误和警告
编译器错误 CS1651
发送反馈意见

更新：2007 年 11 月

错误消息

无法向静态只读字段“identifier”的字段传递 ref 或 out（静态构造函数中除外）

如果将变量作为 ref 参数传递给一个属于静态只读字段成员的函数，则会发生此错误。由于该函数可能修改 ref 参数，所以这是不允许的。若要解决此错误，请移除字段上的 readonly 关键字，或者不要将 readonly 字段的成员传递给该函数。例如，您可能尝试创建一个可以被修改的临时变量，并将该临时变量作为 ref 参数传递，如下面的示例所示。

下面的示例生成 CS1651：

复制代码
// CS1651.cs
public struct Inner
  {
    public int i;
  }

class Outer
{
  public static readonly Inner inner = new Inner();
}

class D
{
   static void f(ref int iref)
   {
   }

   static void Main()
   {
      f(ref Outer.inner.i);  // CS1651
      // Try this instead:
      // int tmp = Outer.inner.i;
      // f(ref tmp);
   }
}


编译器错误 CS1654

[image]

Visual C# 参考：错误和警告
编译器错误 CS1654
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法修改“variable”的成员，因为它是一个“read-only variable type”

当您试图修改一个只读变量的成员时发生此错误，因为它位于特殊的结构中。

此错误发生的一个常见区域位于 foreach 循环内。这是修改集合元素的值时发生的编译时错误。因此，您无法对属于值类型（包括结构）的元素进行任何修改。在其元素属于引用类型的集合中，可以修改每一个元素的可访问成员，但任何试图添加、移除或替换所有元素的操作都将生成编译器错误 CS1656。

示例

下面的示例生成错误 CS1654，因为 Book 为 struct。若要修复此错误，请将 struct 更改为 class。

复制代码
using System.Collections.Generic;
using System.Text;

namespace CS1654
{

    struct Book
    {
        public string Title;
        public string Author;
        public double Price;
        public Book(string t, string a, double p)
        {
            Title=t;
            Author=a;
            Price=p;

        }
    }

    class Program
    {
        List<Book> list;
        static void Main(string[] args)
        {
             //Use a collection initializer to initialize the list
            Program prog = new Program();
            prog.list = new List<Book>();
            prog.list.Add(new Book ("The C# Programming Language",
                                    "Hejlsberg, Wiltamuth, Golde",
                                     29.95));
            prog.list.Add(new Book ("The C++ Programming Language",
                                    "Stroustrup",
                                     29.95));
            prog.list.Add(new Book ("The C Programming Language",
                                    "Kernighan, Ritchie",
                                    29.95));
            foreach(Book b in prog.list)
            {
                //Compile error if Book is a struct
                //Make Book a class to modify its members
                b.Price +=9.95; // CS1654
            }

        }
    }
}


编译器错误 CS1655

[image]

Visual C# 参考：错误和警告
编译器错误 CS1655
发送反馈意见

更新：2007 年 11 月

错误消息

无法将“variable”的字段作为 ref 或 out 参数传递，因为它是“readonly variable type”

如果您尝试将 foreach 变量、using 变量或 fixed 变量的成员作为 ref 或 out 参数传递给函数，则会发生此错误。因为这些变量在这些上下文中被视为只读变量，而这是不允许的。

下面的示例生成 CS1655：

复制代码
// CS1655.cs
struct S
{
   public int i;
}

class CMain
{
  static void f(ref int iref)
  {
  }

  public static void Main()
  {
     S[] sa = new S[10];
     foreach(S s in sa)
     {
        CMain.f(ref s.i);  // CS1655
     }
  }
}


编译器错误 CS1656

[image]

Visual C# 参考：错误和警告
编译器错误 CS1656
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法给“variable”赋值，因为它是“read-only variable type”

当变量赋值发生在只读上下文中时，将出现此错误。只读上下文包括 foreach 迭代变量、using 变量和 fixed 变量。若要解决此错误，请避免在 using 块、foreach 语句和 fixed 语句中给变量赋值。

示例

以下示例生成错误 CS1656，因为它试图替换 foreach 循环内的集合中的所有元素。解决此错误的一种方法是将 foreach 循环更改为 for 循环。另一种方法（此处未显示）是修改现有元素的成员；这可能对类起作用，但对结构不起作用。

复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace CS1654_2
{

    class Book
    {
        public string Title;
        public string Author;
        public double Price;
        public Book(string t, string a, double p)
        {
            Title=t;
            Author=a;
            Price=p;

        }
    }

    class Program
    {
        private List<Book> list;
        static void Main(string[] args)
        {
            Program prog = new Program();
            prog.list = new List<Book>();
            prog.list.Add(new Book ("The C# Programming Language",
                                    "Hejlsberg, Wiltamuth, Golde",
                                     29.95));
            prog.list.Add(new Book ("The C++ Programming Language",
                                    "Stroustrup",
                                     29.95));
            prog.list.Add(new Book ("The C Programming Language",
                                    "Kernighan, Ritchie",
                                    29.95));
            foreach(Book b in prog.list)
            {
                // Cannot modify an entire element in a foreach loop
                // even with reference types.
                // Use a for or while loop instead
                if(b.Title == "The C Programming Language")
                    b = new Book("Programming Windows, 5th Ed.", "Petzold", 29.95); //CS1654
            }

            //With a for loop you can modify elements
            //for(int x = 0; x < prog.list.Count; x++)
            //{
            //    if(prog.list[x].Title== "The C Programming Language")
            //        prog.list[x] = new Book("Programming Windows, 5th Ed.", "Petzold", 29.95);
            //}
            //foreach(Book b in prog.list)
            //    Console.WriteLine(b.Title);

        }
    }
}

下面的示例演示如何在除 foreach 循环之外的其他上下文中生成 CS1656：

复制代码
// CS1656.cs
// compile with: /unsafe
using System;

class C : IDisposable
{
    public void Dispose() { }
}

class CMain
{
    unsafe public static void Main()
    {
        using (C c = new C())
        {
            c = new C(); // CS1656
        }

        int[] ary = new int[] { 1, 2, 3, 4 };
        fixed (int* p = ary)
        {
            p = null; // CS1656
        }
    }
}


编译器错误 CS1657

[image]

Visual C# 参考：错误和警告
编译器错误 CS1657
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将“parameter”作为 ref 或 out 参数传递，因为“reason”

当一个变量在该变量为只读的上下文中作为 ref 或 out 参数传递时，将出现此错误。只读上下文包括 foreach 迭代变量、using 变量和 fixed 变量。若要解决此错误，请不要调用在 using 块、foreach 语句和 fixed 语句中接受 foreach、using 或 fixed 变量作为 ref 或 out 参数的函数。

示例

下面的示例生成 CS1657：

复制代码
// CS1657.cs
using System;
class C : IDisposable
{
    public int i;
    public void Dispose() {}
}

class CMain
{
    static void f(ref C c)
    {
    }
    static void Main()
    {
        using (C c = new C())
        {
            f(ref c);  // CS1657
        }
    }
}

下面的代码说明 fixed 语句中的同样的问题：

复制代码
// CS1657b.cs
// compile with: /unsafe
unsafe class C
{
    static void F(ref int* p)
    {
    }

    static void Main()
    {
        int[] a = new int[5];
        fixed(int* p = a) F(ref p); // CS1657
    }
}


编译器错误 CS1660

[image]

Visual C# 参考：错误和警告
编译器错误 CS1660
发送反馈意见

更新：2007 年 11 月

错误消息

无法将匿名方法块转换为类型“type”，因为它不是一个委托类型

如果您尝试将匿名方法块分配给或以其他方式转换为不是委托类型的类型，就会发生此错误。

下面的示例生成 CS1660：

复制代码
// CS1660.cs
delegate int MyDelegate();
class C {
   static void Main()
   {
     int i = delegate { return 1; };  // CS1660
     // Try this instead:
     // MyDelegate myDelegate = delegate { return 1; };
     // int i = myDelegate();
   }
}


编译器错误 CS1661

[image]

Visual C# 参考：错误和警告
编译器错误 CS1661
发送反馈意见

更新：2007 年 11 月

错误消息

无法将匿名方法块转换为委托类型“delegate type”，因为指定块的参数类型和委托参数类型不匹配

如果在匿名方法定义中，匿名方法的参数类型和委托参数类型不匹配，则将出现此错误。请检查参数数目、参数类型和所有 ref 或 out 参数，验证是否完全匹配。

下面的示例生成 CS1661：

复制代码
// CS1661.cs

delegate void MyDelegate(int i);

class C
{
    public static void Main()
    {
        MyDelegate d = delegate(string s) { };  // CS1661
    }
}


编译器错误 CS1662

[image]

Visual C# 参考：错误和警告
编译器错误 CS1662
发送反馈意见

更新：2007 年 11 月

错误消息

无法将匿名方法块转换为委托类型“delegate type”，原因是块中的某些返回类型无法隐式转换为委托返回类型

如果匿名方法块的返回语句有一个无法隐式转换为委托的返回类型的类型，则会发生此错误。

下面的示例生成 CS1662：

复制代码
// CS1662.cs

delegate int MyDelegate(int i);

class C
{

  public static void Main()
  {
     MyDelegate d = delegate(int i) { return 1.0; };  // CS1662
     // Try this instead:
     // MyDelegate d = dekegate(int i) { return (int)1.0; };
  }
}


编译器错误 CS1663

[image]

Visual C# 参考：错误和警告
编译器错误 CS1663
示例 发送反馈意见

更新：2007 年 11 月

错误消息

固定大小的缓冲区类型必须为下列类型之一: bool、byte、short、int、long、char、sbyte、ushort、uint、ulong、float 或 double

固定大小的缓冲区不能是上面列出的类型以外的任何其他类型。若要避免此错误，请使用其他类型或者不使用固定数组。

示例

下面的示例生成 CS1663。

复制代码
// CS1663.cs
// compile with: /unsafe /target:library

unsafe struct C
{
   fixed string ab[10];   // CS1663
}


编译器错误 CS1664

[image]

Visual C# 参考：错误和警告
编译器错误 CS1664
发送反馈意见

更新：2007 年 11 月

错误消息

长度为“length”且类型为“type”的固定大小缓冲区太大

固定大小缓冲区的最大大小（由长度乘以元素大小决定）为 2^31 = 268435455。


编译器错误 CS1665

[image]

Visual C# 参考：错误和警告
编译器错误 CS1665
示例 发送反馈意见

更新：2007 年 11 月

错误消息

固定大小的缓冲区的长度必须大于零

如果固定大小的缓冲区的大小被声明为零或负值，则发生此错误。固定大小的缓冲区的长度必须是正整数。

示例

下面的示例生成 CS1665。

复制代码
// CS1665.cs
// compile with: /unsafe /target:library
struct S
{
   public unsafe fixed int A[0];   // CS1665
}


编译器错误 CS1666

[image]

Visual C# 参考：错误和警告
编译器错误 CS1666
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能使用非固定表达式中包含的固定大小缓冲区。请尝试使用 fixed 语句。

如果您在一个涉及本身不固定的类的表达式中使用固定大小缓冲区，则会出现此错误。运行库可以自由地将未固定的类到处移动以优化内存访问，这在使用固定大小缓冲区时可能会导致错误。为避免此错误，请在语句中使用 fixed 关键字。

示例

下面的示例生成 CS1666。

复制代码
// CS1666.cs
// compile with: /unsafe /target:library
unsafe struct S
{
   public fixed int buffer[1];
}

unsafe class Test
{
   S field = new S();

   private bool example1()
   {
      return (field.buffer[0] == 0);   // CS1666 error
   }

   private bool example2()
   {
      // OK
      fixed (S* p = &field)
      {
         return (p->buffer[0] == 0);
      }
   }

   private bool example3()
   {
      S local = new S();
      return (local.buffer[0] == 0);
   }
}


编译器错误 CS1667

[image]

Visual C# 参考：错误和警告
编译器错误 CS1667
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性 (Attribute)“attribute”在属性 (Property) 或事件访问器上无效。它仅在“declaration type”声明上有效。

如果您在属性 (Property) 或事件访问器上使用属性 (Attribute)，而属性 (Attribute) 应在属性 (Property) 或事件本身上时，会发生此错误。属性 (Attribute) CLSCompliantAttribute、ConditionalAttribute 和 ObsoleteAttribute 会发生此错误。

示例

下面的示例生成 CS1670：

复制代码
// CS1667.cs
using System;

public class C
{
    private int i;

    //Try this instead:
    //[Obsolete]
    public int ObsoleteProperty
    {
        [Obsolete]  // CS1667
        get { return i; }
        set { i = value; }
    }

    public static void Main()
    {
    }
}


编译器错误 CS1670

[image]

Visual C# 参考：错误和警告
编译器错误 CS1670
示例 发送反馈意见

更新：2007 年 11 月

错误消息

params 在此上下文中无效

一些 C# 功能与变量参数列表不兼容，并且不允许使用 params 关键字，包括：

  • 匿名方法的参数列表

  • 重载运算符

示例

下面的示例生成 CS1670：

复制代码
// CS1670.cs
public class C
{
    public bool operator +(params int[] paramsList)  // CS1670
    {
        return false;
    }

    static void Main()
    {
    }
}


编译器错误 CS1671

[image]

Visual C# 参考：错误和警告
编译器错误 CS1671
发送反馈意见

更新：2007 年 11 月

错误消息

命名空间声明不能有修饰符或属性

修饰符在应用于命名空间时没有意义，因此不允许使用修饰符。

下面的示例生成 CS1671：

复制代码
// CS1671.cs
public namespace NS // CS1671
{

}


编译器错误 CS1672

[image]

Visual C# 参考：错误和警告
编译器错误 CS1672
发送反馈意见

更新：2007 年 11 月

错误消息

选项“option”对 /platform 无效；必须是 anycpu、x86、Itanium 或 x64

这些选项指定处理器类型；使用其中一个列出的形式。


编译器错误 CS1673

[image]

Visual C# 参考：错误和警告
编译器错误 CS1673
发送反馈意见

更新：2007 年 11 月

错误消息

结构内部的匿名方法、lambda 表达式和查询表达式无法访问“this”的实例成员。请考虑将“this”复制到匿名方法、lambda 表达式或查询表达式外部的某个局部变量并改用该局部变量。

下面的示例生成 CS1673：

复制代码
// CS1673.cs
delegate int MyDelegate();

public struct S
{
   int member;

   public int F(int i)
   {
       member = i;
       // Try assigning to a local variable
       // S s = this;
       MyDelegate d = delegate()
       {
          i = this.member;  // CS1673
          // And use the local variable instead of "this"
          // i =  s.member;
          return i;

       };
       return d();
   }
}

class CMain
{
   public static void Main()
   {
   }
}


编译器错误 CS1674

[image]

Visual C# 参考：错误和警告
编译器错误 CS1674
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“T”：using 语句中使用的类型必须可以隐式转换为“System.IDisposable”

using 语句 旨在用于确保在 using 块的最后处置对象，因此只有可处置的类型才能在这类语句中使用。例如，值类型不是可处置的，并且没有约束为类的类型参数不能假定为可处置。

示例

下面的示例生成 CS1674。

复制代码
// CS1674.cs
class C
{
   public static void Main()
   {
      int a = 0;
      a++;

      using (a) {}   // CS1674
   }
}

下面的示例生成 CS1674。

复制代码
// CS1674_b.cs
using System;
class C {
   public void Test() {
      using (C c = new C()) {}   // CS1674
   }
}

// OK
class D : IDisposable {
   void IDisposable.Dispose() {}
   public void Dispose() {}

   public static void Main() {
      using (D d = new D()) {}
   }
}

下面的示例说明了需要使用一个类类型约束来确保未知的类型参数是可释放的。下面的示例生成 CS1674。

复制代码
// CS1674_c.cs
// compile with: /target:library
using System;
public class C<T>
// Add a class type constraint that specifies a disposable class.
// Uncomment the following line to resolve.
// public class C<T> where T : IDisposable
{
   public void F(T t)
   {
      using (t) {}   // CS1674
   }
}


编译器错误 CS1675

[image]

Visual C# 参考：错误和警告
编译器错误 CS1675
示例 发送反馈意见

更新：2007 年 11 月

错误消息

枚举不能有类型参数

若要解决此错误，请从 enum 声明中移除类型参数。

示例

下面的示例生成 CS1675：

复制代码
// CS1675.cs
enum E<T>  // CS1675
{
}

class CMain
{
    public static void Main()
    {
    }
}


编译器错误 CS1676

[image]

Visual C# 参考：错误和警告
编译器错误 CS1676
发送反馈意见

更新：2007 年 11 月

错误消息

参数“number”必须用“keyword”关键字声明

当匿名方法中的参数类型修饰符与您要将该方法强制转换为的委托的声明中使用的修饰符不同时，会出现此错误。

下面的示例生成 CS1676：

复制代码
// CS1676.cs
delegate void E(ref int i);
class Errors
{
   static void Main()
   {
      E e = delegate(out int i) { };   // CS1676
      // To resolve, use the following line instead:
      // E e = delegate(ref int i) { };
   }
}


编译器错误 CS1677

[image]

Visual C# 参考：错误和警告
编译器错误 CS1677
示例 发送反馈意见

更新：2007 年 11 月

错误消息

参数“number”不应用“keyword”关键字声明

当匿名方法中的参数类型修饰符与您要将该方法强制转换为的委托的声明中使用的修饰符不匹配时，则会发生此错误。

示例

下面的示例生成 CS1677：

复制代码
// CS1677.cs
delegate void D(int i);
class Errors
{
    static void Main()
    {
        D d = delegate(out int i) { };   // CS1677
        // To resolve, use the following line instead:
        // D d = delegate(int i) { };

        D d = delegate(ref int j){}; // CS1677
        // To resolve, use the following line instead:
        // D d = delegate(int j){};
    }
}


编译器错误 CS1678

[image]

Visual C# 参考：错误和警告
编译器错误 CS1678
发送反馈意见

更新：2007 年 11 月

错误消息

参数“number”被声明为类型“type1”，但应为“type2”

当匿名方法中的参数类型与您要将方法强制转换为的委托的声明不同时，将出现此错误。

下面的示例生成 CS1678：

复制代码
// CS1678
delegate void D(int i);
class Errors
{
   static void Main()
   {
      D d = delegate(string s) { };   // CS1678
      // To resolve, use the following line instead:
      // D d = delegate(int s) { };
   }
}


编译器错误 CS1679

[image]

Visual C# 参考：错误和警告
编译器错误 CS1679
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“/reference”的外部别名无效；“identifier”不是有效的标识符

当使用 /reference 选项的外部程序集别名功能时，根据 C# 语言规范，在 /reference: 之后以及在“=”之前的文本必须是一个有效的 C# 标识符或关键字。

要更正此错误，请将“=”之前的文本更改为有效的 C# 标识符或关键字。

示例

下面的示例生成 CS1679。

复制代码
// CS1679.cs
// compile with: /reference:123$BadIdentifier%=System.dll
class TestClass {
    static void Main()
    {
    }
}


编译器错误 CS1680

[image]

Visual C# 参考：错误和警告
编译器错误 CS1680
发送反馈意见

更新：2007 年 11 月

错误消息

无效的引用别名选项：“alias=”-- 缺少文件名。

如果您在使用 /reference 编译器选项的 alias 功能时没有指定有效的文件名，则会发生此错误。

下面的示例生成 CS1680。

复制代码
// CS1680.cs
// compile with: /reference:alias=
// CS1680 expected
// To resolve, specify the name of a file with an assembly manifest
class MyClass {}


编译器错误 CS1681

[image]

Visual C# 参考：错误和警告
编译器错误 CS1681
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能重新定义全局外部别名

全局别名已被定义为包括所有的非别名引用，因此不能重新定义。

示例

下面的示例生成 CS1681。

复制代码
// CS1681.cs
// compile with: /reference:global=System.dll
// CS1681 expected

// try this instead: /reference:System.dll
class A
{
   static void Main() {}
}


编译器错误 CS1686

[image]

Visual C# 参考：错误和警告
编译器错误 CS1686
示例 发送反馈意见

更新：2007 年 11 月

错误消息

在匿名方法块或 lambda 表达式的内部不能获取局部变量“variable”或其成员的地址，也不能使用局部变量“variable”或其成员

当您使用某个变量并尝试获取其地址时，只要这些操作有一项是在匿名方法内部执行的，就会生成此错误。

示例

下面的示例生成 CS1686。

复制代码
// CS1686.cs
// compile with: /unsafe /target:library
class MyClass
{
   public unsafe delegate int * MyDelegate();

   public unsafe int * Test()
   {
      int j = 0;
      MyDelegate d = delegate { return &j; };   // CS1686
      return &j;   // OK
   }
}


编译器错误 CS1688

[image]

Visual C# 参考：错误和警告
编译器错误 CS1688
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将没有参数列表的匿名方法块转换为委托类型“delegate”，因为它有一个或多个 out 参数。

编译器允许在大多数情况下从匿名方法块中省略参数。当匿名方法块没有参数列表，而委托具有 out 参数时，会发生此错误。编译器不允许这种情况，因为它需要忽略 out 参数的存在，而这不可能是正确的行为。

示例

下面的代码生成错误 CS1688。

复制代码
// CS1688.cs
using System;
delegate void OutParam(out int i);
class ErrorCS1676
{
    static void Main()
    {
        OutParam o;
        o = delegate  // CS1688
        // Try this instead:
        // o = delegate(out int i)
        {
            Console.WriteLine("");
        };
    }
}


编译器错误 CS1689

[image]

Visual C# 参考：错误和警告
编译器错误 CS1689
示例 发送反馈意见

更新：2007 年 11 月

错误消息

属性“Attribute Name”仅对方法或属性类有效

只有 ConditionalAttribute 属性会出现此错误。正如错误信息中所指出的，此属性只能在方法或属性类上使用。例如，试图将此属性应用于类会产生此错误。

示例

下面的示例生成 CS1689。

复制代码
// CS1689.cs
// compile with: /target:library
[System.Diagnostics.Conditional("A")]   // CS1689
class MyClass {}


编译器错误 CS1703

[image]

Visual C# 参考：错误和警告
编译器错误 CS1703
示例 发送反馈意见

更新：2007 年 11 月

错误消息

已导入了具有相同的简单名称“name”的程序集。请尝试移除其中一个引用，或者给引用加上签名以启用并列模式。

编译器移除有相同的路径和文件名称的引用，但有可能相同的文件在两个位置都存在，或者您忘了更改版本号。此错误指出两个引用具有相同的程序集标识，因此编译器无法在元数据中区分它们。或者移除其中的一个冗余引用，或者用某种办法使引用一致，例如通过提高程序集版本号。

以下代码生成错误 CS1703。

示例

此代码在目录 .\bin1 中创建程序集 A。

将此示例保存在名为 CS1703a1.cs 的文件中，并用以下标志编译该文件：/t:library /out:.\bin1\cs1703.dll /keyfile:key.snk

复制代码
using System;
public class A { }

此代码在目录 .\bin2 中创建程序集 A 的副本。

将此示例保存在名为 CS1703a2.cs 的文件中，并用以下标志编译该文件：/t:library /out:.\bin2\cs1703.dll /keyfile:key.snk

复制代码
using System;
public class A { }

此代码引用前面两个模块中的程序集 A。

将此示例保存在名为 CS1703ref.cs 的文件中，并用以下标志编译该文件：/t:library /r:A2=.\bin2\cs1703.dll /r:A1=.\bin1\cs1703.dll

复制代码
extern alias A1;
extern alias A2;


编译器错误 CS1704

[image]

Visual C# 参考：错误和警告
编译器错误 CS1704
示例 发送反馈意见

更新：2007 年 11 月

错误消息

已导入具有相同简单名称“Assembly Name”的程序集。请尝试移除其中一个引用，或者给引用加上签名以启用并列模式。

此错误指出有两个引用具有相同的程序集标识，这是因为相关程序集缺少强名称而且没有签名，因而编译器无法在元数据中区分它们。因此，运行时会忽略版本和区域性程序集名称属性。用户应移除多余的引用，重命名其中一个引用，或者为它们提供一个强名称。

示例

此示例创建一个程序集并将它保存到根目录下。

复制代码
// CS1704_a.cs
// compile with: /target:library /out:c:\\cs1704.dll
public class A {}

此示例创建一个与上面的示例同名的程序集，但将它保存到不同的位置。

复制代码
// CS1704_b.cs
// compile with: /target:library /out:cs1704.dll
public class A {}

此示例试图引用这两个程序集。下面的示例生成 CS1704。

复制代码
// CS1704_c.cs
// compile with: /target:library /r:A2=cs1704.dll /r:A1=c:\\cs1704.dll
// CS1704 expected
extern alias A1;
extern alias A2;


编译器错误 CS1705

[image]

Visual C# 参考：错误和警告
编译器错误 CS1705
示例 发送反馈意见

更新：2007 年 11 月

错误消息

程序集“AssemblyName1”所使用的“TypeName”的版本高于所引用的程序集“AssemblyName2”的版本

您要引用一个版本号高于所引用的程序集的版本号的类型。

例如，您有两个程序集：A 和 B。A 引用的类 myClass 被添加到 2.0 版的程序集 B 中。但是，对程序集 B 的引用指定的是 1.0 版。编译器有一致性绑定引用规则，对 2.0 版的引用无法由 1.0 版满足。

示例

本示例由 4 个代码模块组成：

  • 两个 DLL，它们除了版本属性外，其他方面都是一样的。

  • 一个引用它们的 DLL。

  • 客户端。

下面是两个相同的 DLL 中的第一个。

复制代码
// CS1705_a.cs
// compile with: /target:library /out:c:\\cs1705.dll /keyfile:mykey.snk
[assembly:System.Reflection.AssemblyVersion("1.0")]
public class A
{
   public void M1() {}
   public class N1 {}
   public void M2() {}
   public class N2 {}
}

public class C1 {}
public class C2 {}

下面是程序集 2.0 版，由属性 AssemblyVersionAttribute 指定。

复制代码
// CS1705_b.cs
// compile with: /target:library /out:cs1705.dll /keyfile:mykey.snk
using System.Reflection;
[assembly:AssemblyVersion("2.0")]
public class A
{
   public void M2() {}
   public class N2 {}
   public void M1() {}
   public class N1 {}
}

public class C2 {}
public class C1 {}

将此示例保存在一个名为 CS1705ref.cs 的文件中，并使用以下标志编译该文件：/t:library /r:A2=.\bin2\CS1705a.dll /r:A1=.\bin1\CS1705a.dll

复制代码
// CS1705_c.cs
// compile with: /target:library /r:A2=c:\\CS1705.dll /r:A1=CS1705.dll
extern alias A1;
extern alias A2;
using a1 = A1::A;
using a2 = A2::A;
using n1 = A1::A.N1;
using n2 = A2::A.N2;
public class Ref
{
   public static a1 A1() { return new a1(); }
   public static a2 A2() { return new a2(); }
   public static A1::C1 M1() { return new A1::C1(); }
   public static A2::C2 M2() { return new A2::C2(); }
   public static n1 N1() { return new a1.N1(); }
   public static n2 N2() { return new a2.N2(); }
}

下面的示例引用 1.0 版的 CS1705.dll 程序集。但语句 Ref.A2().M2() 引用 CS1705_c.dll 的类中的 A2 方法，它将返回 a2（别名为 A2::A），而 A2 通过 extern 语句引用 2.0 版，因此导致版本不匹配。

下面的示例生成 CS1705。

复制代码
// CS1705_d.cs
// compile with: /reference:c:\\CS1705.dll /reference:CS1705_c.dll
// CS1705 expected
class Tester
{
   static void Main()
   {
      Ref.A1().M1();
      Ref.A2().M2();
   }
}


编译器错误 CS1706

[image]

Visual C# 参考：错误和警告
编译器错误 CS1706
示例 发送反馈意见

更新：2007 年 11 月

错误消息

表达式不能包含匿名方法或 lambda 表达式

不能在表达式内插入匿名方法。

更正此错误

  • 在表达式中使用规则 delegate。

示例

下面的示例生成 CS1706。

复制代码
// CS1706.cs
using System;

delegate void MyDelegate();
class MyAttribute : Attribute
{
    public MyAttribute(MyDelegate d) { }
}

// Anonymous Method in Attribute declaration is not allowed.
[MyAttribute(delegate{/* anonymous Method in Attribute declaration */})]  // CS1706
class Program
{
}


编译器错误 CS1708

[image]

Visual C# 参考：错误和警告
编译器错误 CS1708
示例 发送反馈意见

更新：2007 年 11 月

错误消息

固定大小的缓冲区只能通过局部变量或字段访问

C# 2.0 的一个新功能是能够在 struct 的内部定义内嵌数组。这类数组只能通过局部变量或字段访问，不能作为表达式左侧的中间值被引用。此外，这些数组无法通过 static 或 readonly 的字段访问。

要解决此错误，请定义一个数组变量，然后将内嵌数组分配给该变量。或者，从表示内嵌数组的字段中移除 static 或 readonly 修饰符。

示例

下面的示例生成 CS1708。

复制代码
// CS1708.cs
// compile with: /unsafe
using System;

unsafe public struct Foo
{
    public fixed char name[10];
}

public unsafe class C
{
    public Foo UnsafeMethod()
    {
        Foo myFoo = new Foo();
        return myFoo;
    }

    static void Main()
    {
        C myC = new C();
        myC.UnsafeMethod().name[3] = 'a';  // CS1708
        // Uncomment the following 2 lines to resolve:
        // Foo myFoo = myC.UnsafeMethod();
        // myFoo.name[3] = 'a';

        // The field cannot be static.
        C._foo1.name[3] = 'a';  // CS1708

        // The field cannot be readonly.
        myC._foo2.name[3] = 'a';  // CS1708
    }

    static readonly Foo _foo1;
    public readonly Foo _foo2;
}


编译器错误 CS1713

[image]

Visual C# 参考：错误和警告
编译器错误 CS1713
发送反馈意见

更新：2007 年 11 月

错误消息

生成类型 Typename1—'Reason' 的元数据名称时发生意外错误。

此错误通常是由内部编译器错误导致的。对您的代码稍稍进行更改，如缩短名称的长度，然后重新编译，便可能解决此问题。


编译器错误 CS1714

[image]

Visual C# 参考：错误和警告
编译器错误 CS1714
发送反馈意见

更新：2007 年 11 月

错误消息

TypeName1 的基类或接口未能解析或无效

您要从某个类或接口实现 TypeName1，而该类或接口无法被解析（例如，编译器无法找到它）或者无效。解决方法是确定它是这两种情况中的哪一种，然后要么更准确地指定类型的位置，要么修复基类中的任何编译器错误。


编译器错误 CS1715

[image]

Visual C# 参考：错误和警告
编译器错误 CS1715
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“Type1”：类型必须为“Type2”才能与重写成员“MemberName”匹配

此错误与 编译器错误 CS0508 相同，只不过 CS0508 现在仅适用于具有返回类型的方法，而 CS1715 适用于仅具有“types”而不是“return types”的属性和索引器。

示例

下面的代码生成 CS1715。

复制代码
// CS1715.cs
abstract public class Base
{
    abstract public int myProperty
    {
        get;
        set;
    }
}

public class Derived : Base
{
    int myField;
    public override double myProperty  // CS1715
    // try the following line instead
    // public override int myProperty
    {
        get { return myField; }
        set { myField;= value; }
    }

    public static void Main()
    {
        Derived d = new Derived();
        d.myProperty = 5;
    }
}


编译器错误 CS1716

[image]

Visual C# 参考：错误和警告
编译器错误 CS1716
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不要使用“System.Runtime.CompilerServices.FixedBuffer”属性。请改用“fixed”字段修饰符。

在包含与字段声明相似的固定大小的数组声明的不安全代码段中，会发生此错误。不要使用此属性。请改用关键字 fixed。

示例

下面的示例生成 CS1716。

复制代码
// CS1716.cs
// compile with: /unsafe
using System;
using System.Runtime.CompilerServices;

public struct UnsafeStruct
{
    [FixedBuffer(typeof(int), 4)]  // CS1716
    unsafe public int aField;
    // Use this single line instead of the above two lines.
    // unsafe public fixed int aField[4];
}

public class TestUnsafe
{
    static int Main()
    {
        UnsafeStruct us = new UnsafeStruct();
        unsafe
        {
            if (us.aField[0] == 0)
                return us.aField[1];
            else
                return us.aField[2];
        }
    }
}


编译器错误 CS1719

[image]

Visual C# 参考：错误和警告
编译器错误 CS1719
发送反馈意见

更新：2007 年 11 月

错误消息

读取 Win32 资源文件“File Name”时出错 --“reason”

读取 Win32 资源文件的尝试失败，原因已在错误中给出，通常的原因是“未找到文件”或“访问被拒绝”之类。按照原因的描述更正问题可解决此错误。


编译器错误 CS1721

[image]

Visual C# 参考：错误和警告
编译器错误 CS1721
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类“class”不能具有多个基类: “class_1”和“class_2”

导致此错误的最常见的原因是试图使用多重继承。 C# 中的类只能从一个类继承：类声明中位于类名称后面的其余的类都必须是接口。

示例

下面的示例生成 CS1721。

复制代码
// CS1721.cs
public class A {}
public class B {}
public class MyClass : A, B {}   // CS1721


编译器错误 CS1722

[image]

Visual C# 参考：错误和警告
编译器错误 CS1722
示例 发送反馈意见

更新：2007 年 11 月

错误消息

基类“class”必须在任何接口之前

指定从中继承的类和要实现的接口时，必须首先指定类名。

示例

下面的示例生成 CS1722。

复制代码
// CS1722.cs
// compile with: /target:library
public class A {}
interface I {}

public class MyClass : I, A {}   // CS1722
public class MyClass2 : A, I {}   // OK


编译器错误 CS1724

[image]

Visual C# 参考：错误和警告
编译器错误 CS1724
示例 发送反馈意见

更新：2007 年 11 月

错误消息

为“System.Runtime.InteropServices.DefaultCharSetAttribute”的参数指定的值无效

此错误由 DefaultCharSetAttribute 类的无效参数生成。

示例

下面的示例生成 CS1724。

复制代码
// CS1724.cs
using System.Runtime.InteropServices;
// To resolve, replace 42 with a valid CharSet value.
 [module:DefaultCharSetAttribute((CharSet)42)]   // CS1724
class C {

[DllImport("F.Dll")]
extern static void FW1Named();

static void Main() {}
}


编译器错误 CS1725

[image]

Visual C# 参考：错误和警告
编译器错误 CS1725
示例 发送反馈意见

更新：2007 年 11 月

错误消息

友元程序集引用“reference”无效。不能在 InternalsVisibleTo 声明中指定版本、区域性、公钥标记或处理器体系结构。

不能在友元程序集引用中添加版本区域性。分部类应对友元程序集可见。

示例

下面的示例生成 CS1725。

复制代码
// CS1725.cs
// compile with: /target:library
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("partial01,version=1.1.0.0")]   // CS1725
// try the following line instead
// [assembly:InternalsVisibleTo("partial01")]

partial class TestClass
{
   public static string strBar = "my string";
}


编译器错误 CS1726

[image]

Visual C# 参考：错误和警告
编译器错误 CS1726
示例 发送反馈意见

更新：2007 年 11 月

错误消息

友元程序集引用“reference”无效。强名称签名的程序集必须在其 InternalsVisibleTo 声明中指定一个公钥。

强名称签名的程序集只能将使用 InternalsVisibleToAttribute 创建的友元程序集访问权限授予其他强签名的程序集。

若要解决 CS1726，请对要授予其友元访问权限的程序集进行签名（赋予其强名称），或不授予友元访问权限。

有关更多信息，请参见友元程序集（C# 编程指南）。

示例

下面的示例生成 CS1726。

复制代码
// CS1726.cs
// compile with: /keyfile:CS1726.key /target:library
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("UnsignedAssembly")]   // CS1726
// try the following line instead
// [assembly:InternalsVisibleTo("SignedAssembly, PublicKey=0024000004800000940000000602000000240000525341310004000001000100031d7b6f3abc16c7de526fd67ec2926fe68ed2f9901afbc5f1b6b428bf6cd9086021a0b38b76bc340dc6ab27b65e4a593fa0e60689ac98dd71a12248ca025751d135df7b98c5f9d09172f7b62dabdd302b2a1ae688731ff3fc7a6ab9e8cf39fb73c60667e1b071ef7da5838dc009ae0119a9cbff2c581fc0f2d966b77114b2c4")]

class A {}


编译器错误 CS1727

[image]

Visual C# 参考：错误和警告
编译器错误 CS1727
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

未经授权无法自动发送错误报告。请访问“”以授权发送错误报告。

此错误文本中列出的网站介绍如何为 Visual Studio 2005 命令行工具启用自动错误报告。

示例

下面的示例生成 CS1727。

复制代码
// CS1727.cs
// compile with: /errorreport:send
// CS1727 expected
class Test
{
    static void Main(){}
}

请参见

参考

/errorreport（设置错误报告行为）（C# 编译器选项）


编译器错误 CS1728

[image]

Visual C# 参考：错误和警告
编译器错误 CS1728
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将委托绑定到作为“type”成员的“member”

无法将委托绑定到 Nullable 类型的成员。

示例

下面的示例生成 CS1728：

复制代码
// CS1728.cs
class Test
{
   delegate T GetT<T>();
   delegate T GetT1<T>(T t);

   delegate bool E(object o);
   delegate int I();
   delegate string S();

   static void Main()
   {
      int? x = null;
      int? y = 5;

      GetT<int> d1 = x.GetValueOrDefault;   // CS1728
      GetT<int> d2 = y.GetValueOrDefault;   // CS1728
      GetT1<int> d3 = x.GetValueOrDefault;   // CS1728
      GetT1<int> d4 = y.GetValueOrDefault;   // CS1728
   }
}


编译器错误 CS1729

[image]

Visual C# 参考：错误和警告
编译器错误 CS1729
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”不包含采用“number”参数的构造函数。

在直接或间接调用类的构造函数时，如果编译器找不到具有相同数量参数的任意构造函数，则会发生此错误。在下面的示例中，test 类没有采用任意参数的构造函数。因此，该类只有不含参数的默认构造函数。由于在生成该错误的第二行上，派生类没有声明其自己的构造函数，因此编译器将提供默认构造函数。该构造函数在基类中调用无参数构造函数。由于基类没有此类构造函数，因此会生成 CS1729。

更正此错误

  1. 调整构造函数调用中的参数数量。

  2. 修改相应的类，提供包含必须调用的参数的构造函数。

  3. 在基类中提供无参数构造函数。

示例

下面的示例生成 CS1729：

复制代码
// cs1729.cs
class Test
{
    static int Main()
    {
        Test test = new Test(2); //CS1729
        return 1;
    }
}
public class Parent
{
    public Parent(int i) { }
}
public class Child : Parent { } // CS1729

可选注释


编译器错误 CS1730

[image]

Visual C# 参考：错误和警告
编译器错误 CS1730
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

程序集和模块属性必须位于文件中定义的所有其他元素之前(using 子句和外部别名声明除外)。

在程序集级提供的属性不能出现在任何类型定义的后面。

更正此错误

  • 将该属性移至文件顶部，且位于 using 指令和 extern 别名声明的下方。

示例

下面的代码生成 CS1730：

复制代码
// cs1730.cs
class Test
{
}
[assembly: System.Attribute] // CS1730

请参见

参考

属性（C# 编程指南）


编译器错误 CS1731

[image]

Visual C# 参考：错误和警告
编译器错误 CS1731
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将“expression”转换为委托，原因是块中的某些返回类型不能隐式转换为委托返回类型。

在 lambda 表达式或匿名方法具有与委托的返回类型不兼容的返回类型时会生成此错误。

更正此错误

  • 更改委托的返回类型或表达式的返回类型。

示例

下面的代码将生成 CS1731：

复制代码
class CS1731
{
    delegate double D();
    D d = () => { return "Who knows the real sword of Gryffindor?"; };
}


编译器错误 CS1732

[image]

Visual C# 参考：错误和警告
编译器错误 CS1732
示例 发送反馈意见

更新：2007 年 11 月

错误消息

需要参数。

如果 lambda 表达式在输入参数后面包含一个逗号，但在后面没有指定参数，则会生成此错误。

更正此错误

  • 移除逗号，或在逗号后面添加编译器要查找的输入参数。

示例

下面的示例生成 CS1732：

复制代码
// cs1732.cs
// compile with: /target:library
class Test
    {
        delegate void D(int x, int y);
        static void Main()
        {
        D d = (x,) => { }; // CS1732
      }
    }


编译器错误 CS1733

[image]

Visual C# 参考：错误和警告
编译器错误 CS1733
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应为表达式。

每当编译器在出错的行上需要一个表达式时，就会发生此错误。在下面的示例中，初始值设定项中的尾随逗号向编译器指示后面将跟随另一个表达式。

更正此错误

  • 提供缺少的表达式。

  • 移除导致编译器需要一个表达式的标记。

示例

下面的示例生成 CS1733，因为存在尾随逗号：

复制代码
// cs1733.cs
using System.Collections.Generic;
public class Test
{
    public static void Main()
    {
        List<int> list = new List<int>() {{ 1, 2, }};// CS1733
    }
}


编译器错误 CS1900

[image]

Visual C# 参考：错误和警告
编译器错误 CS1900
发送反馈意见

更新：2007 年 11 月

错误消息

警告等级必须在 0-4 的范围内

/warn 编译器选项只能带五个可能的值（0、1、2、3 或 4）之一。传递给 /warn 的任何其他值均将导致 CS1900。

下面的示例生成 CS1900：

复制代码
// CS1900.cs
// compile with: /W:5
// CS1900 expected
class x
{
   public static void Main()
   {
   }
}


编译器错误 CS1902

[image]

Visual C# 参考：错误和警告
编译器错误 CS1902
发送反馈意见

更新：2007 年 11 月

错误消息

用于 /debug 的选项“option”无效；选项必须是 full 或 pdbonly

给 /debug 编译器选项传递的选项无效。

下面的示例生成 CS1902：

复制代码
// CS1902.cs
// compile with: /debug:x
// CS1902 expected
class x
{
   public static void Main()
   {
   }
}


编译器错误 CS1906

[image]

Visual C# 参考：错误和警告
编译器错误 CS1906
发送反馈意见

更新：2007 年 11 月

错误消息

选项“option”无效；资源可见性必须是“public”或“private”

此错误指示一个无效的 /resource（将资源文件嵌入到输出中） 或 /linkresource（链接到 .NET Framework 资源） 命令行选项。请检查 /resource 或 /linkresource 命令行选项的语法，确保所使用的可访问性修饰符是 public 或 private。


编译器错误 CS1908

[image]

Visual C# 参考：错误和警告
编译器错误 CS1908
示例 发送反馈意见

更新：2007 年 11 月

错误消息

DefaultValue 属性的变量类型必须与参数类型匹配

对 DefaultValueAttribute 属性值使用错误的参数时生成此错误。请使用与参数类型匹配的值。

示例

下面的示例生成 CS1908。

复制代码
// CS1908.cs
// compile with: /target:library
using System.Runtime.InteropServices;

public interface ISomeInterface
{
   void Bad([Optional] [DefaultParameterValue("true")] bool b);   // CS1908

   void Good([Optional] [DefaultParameterValue(true)] bool b);   // OK
}


编译器错误 CS1909

[image]

Visual C# 参考：错误和警告
编译器错误 CS1909
示例 发送反馈意见

更新：2007 年 11 月

错误消息

DefaultValue 属性不适用于“type”类型的参数

使用不适用于此参数类型的 DefaultValue 属性时，会生成 CS1909。

示例

下面的示例生成 CS1909。

复制代码
// CS1909.cs
// compile with: /target:library
using System.Runtime.InteropServices;

public interface ISomeInterface
{
   void Test1([DefaultParameterValue(new int[] {1, 2})] int[] arr1);   // CS1909

   void Test2([DefaultParameterValue("Test String")] string s);   // OK
}


编译器错误 CS1910

[image]

Visual C# 参考：错误和警告
编译器错误 CS1910
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”类型的参数不适用于 DefaultValue 属性

对于类型为对象的参数，DefaultParameterValueAttribute 的变量必须是 null、整型、浮点型、bool、string、enum 或 char。该变量不能是 Type 类型或任何数组类型。

示例

下面的示例生成 CS1910。

复制代码
// CS1910.cs
// compile with: /target:library
using System.Runtime.InteropServices;

public interface MyI
{
   void Test([DefaultParameterValue(typeof(object))] object o);   // CS1910
}


编译器错误 CS1912

[image]

Visual C# 参考：错误和警告
编译器错误 CS1912
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

成员“name”的初始化重复。

一个对象初始值设定项一次只能初始化一个成员。

更正此错误

  • 在对象初始值设定项中移除成员的第二次初始化。

示例

下面的代码生成 CS1912，因为 memberA 初始化了两次：

复制代码
// cs1912.cs
using System.Linq;

public class TestClass
{
    public int memberA { get; set; }
    public int memberB { get; set; }
}

public class Test
{
    static void Main()
    {
        TestClass tc = new TestClass() { memberA = 5, memberA = 6, memberB = 2}; // CS1912
    }
}

请参见

参考

对象和集合初始值设定项（C# 编程指南）


编译器错误 CS1913

[image]

Visual C# 参考：错误和警告
编译器错误 CS1913
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法初始化成员“name”。它不是字段或属性。

对象初始值设定项只能用于初始化可访问的字段或属性。

更正此错误

  • 用常规构造函数或其他初始化方法初始化类成员。

示例

下面的示例生成 CS1913：

复制代码
// cs1912.cs
class A
{
    public delegate void D();
    public event D myEvent;
}

public class Test
{
    static void Main()
    {

        A a = new A() {myEvent = M}; // CS1913
    }

    public void M(){}
}

请参见

参考

类和结构（C# 编程指南）


编译器错误 CS1914

[image]

Visual C# 参考：错误和警告
编译器错误 CS1914
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法在对象初始值设定项中为静态字段“name”赋值

根据定义，对象初始值设定项用于对类的对象或实例进行初始化。它们不能用于对类型的 static 字段进行初始化。不管为一个类创建了多少个实例，该类的 static 字段都只有一个副本。

更正此错误

  • 将字段更改为该类型的实例字段，或者不要尝试通过对象初始值设定项对该字段进行初始化。

示例

下面的代码生成 CS1914，因为该初始值设定项尝试对 TestClass.Number 字段进行初始化，而该字段为 static：

复制代码
// cs1914.cs
using System.Linq;
public class TestClass
{
    public string Message { get; set; }
    public static int Number { get; set; }
}
class Test
{
    static void Main()
    {
        TestClass b = new TestClass() { Message = "Hello", Number = "555-1212" }; // CS1914

    }
}


编译器错误 CS1917

[image]

Visual C# 参考：错误和警告
编译器错误 CS1917
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法使用对象初始值设定项为类型“struct name”的只读字段“name”的成员赋值，因为它是值类型。

只能在构造函数中分配那些为值类型的只读字段。

更正此错误

  • 将结构更改为类类型。

  • 使用构造函数初始化结构。

示例

下面的代码将生成 CS1917：

复制代码
// cs1917.cs
class CS1917
{
    public struct TestStruct
    {
        public int i;
    }
    public class C
    {
        public readonly TestStruct str = new TestStruct();
        public static int Main()
        {
            C c = new C { str = { i = 1 } }; // CS1917
            return 0;
        }
    }
}


编译器错误 CS1918

[image]

Visual C# 参考：错误和警告
编译器错误 CS1918
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法使用对象初始值设定项为类型为“type”的属性“name”的成员赋值，因为它是值类型。

在尝试使用对象初始值设定项对结构类型的属性进行初始化时，如果该结构类型本身是正在初始化的类的属性，则会发生此错误。

更正此错误

  • 如果必须用对象初始值设定项对属性的字段进行完全初始化，请将该结构更改为类类型。另一种方法是，在使用对象初始值设定项创建对象后，在单独的方法调用中对该结构的成员进行初始化。

示例

下面的示例生成 CS1918：

复制代码
// cs1918.cs
public struct MyStruct
{
    public int i;

}
public class Test
{
    private MyStruct str = new MyStruct();
    public MyStruct Str
    {
        get
        {
            return str;
        }
    }
    public static int Main()
    {
        Test t = new Test { Str = { i = 1 } }; // CS1918
        return 0;
    }
}

请参见

参考

对象和集合初始值设定项（C# 编程指南）


编译器错误 CS1919

[image]

Visual C# 参考：错误和警告
编译器错误 CS1919
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

创建对象时不能使用不安全的类型“type name”。

new 运算符仅在托管堆上创建对象。但可以通过使用语言的互操作功能调用返回指针的本机方法，间接地在非托管内存中创建对象。

更正此错误

  1. 在新的对象创建表达式中使用安全类型。例如，使用 char 或 int，而不是使用 char* 或 int*。

  2. 如果必须在非托管内存中创建对象，请使用 Win32 或 COM 方法，或者用 C 或 C++ 编写自己的函数并从 C# 调用该函数。

示例

下面的示例生成 CS1919，因为指针类型是不安全的：

复制代码
// cs1919.cs
// Compile with: /unsafe
unsafe public class C
{
    public static int Main()
    {
        var col1 = new int* { }; // CS1919
        var col2 = new char* { }; // CS1919
        return 1;
    }
}

请参见

参考

互操作性（C# 编程指南）


编译器错误 CS1920

[image]

Visual C# 参考：错误和警告
编译器错误 CS1920
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

元素初始值设定项不能为空。

集合初始值设定项由一系列元素初始值设定项组成。除非元素初始值设定项包含赋值表达式，否则它们不需要用大括号括起。但是，如果确实提供了大括号，则大括号不能为空。如果元素初始值设定项为对象初始值设定项，则只要该初始值设定项包含新的对象创建表达式，大括号就可为空。

更正此错误

  • 在大括号中间添加缺少的表达式。

  • 如果表达式要用作对象初始值设定项，则在大括号的前面添加新的对象创建表达式。

示例

下面的示例生成 CS1920：

复制代码
  // cs1920.cs
using System.Collections.Generic;
public class Test
{
    public static int Main()
    {
        // Error. Empty initializer
        // for inner list.
        List<List<int>> collection =
            new List<List<int>>() { { } }; // CS1920

        // OK. No initializer for inner list.
        List<List<int>> collection2 =
            new List<List<int>>() {  };

        // OK. Inner list is initialized
        // to one List<int> with zero elements.
        List<List<int>> collection3 =
            new List<List<int>>() { new List<int> { } };
        return 0;
    }
}

请参见

参考

对象和集合初始值设定项（C# 编程指南）


编译器错误 CS1921

[image]

Visual C# 参考：错误和警告
编译器错误 CS1921
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

与“method”最匹配的重载方法具有对于初始值设定项元素而言错误的签名。可初始化的 Add 必须是可访问的实例方法。

在尝试对没有公共非静态 Add 方法的类使用集合初始值设定项时，会生成此错误。如果 Add 方法因其保护级别（private、protected 和 internal）而不可访问，则会收到 CS0122，因此该错误可能意味着该方法被定义为 static。

示例

下面的示例生成 CS1921：

复制代码
// cs1921.cs
using System.Collections;
public class C : CollectionBase
{
    public static void Add(int i)
    {
    }
}
public class Test
{
    public static void Main()
    {
        var collection = new C { 1, 2, 3 }; // CS1921
    }
}

请参见

参考

对象和集合初始值设定项（C# 编程指南）


编译器错误 CS1922

[image]

Visual C# 参考：错误和警告
编译器错误 CS1922
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

集合初始值设定项要求其类型“type”实现 System.Collections.IEnumerable。

若要对某一类型使用集合初始值设定项，该类型必须实现 IEnumerable。如果在打算使用对象初始值设定项时无意中使用了集合初始值设定项语法，则会发生此错误。

更正此错误

  • 如果该类型不表示集合，请使用对象初始值设定项语法，而不要使用集合初始值设定项语法。

  • 如果该类型确实表示集合，则先对其进行修改以使其实现 IEnumerable，然后再使用集合初始值设定项对该类型的对象进行初始化。

  • 如果该类型表示集合，但您无法访问源代码，则只需通过其类构造函数或其他初始化方法对其元素进行初始化即可。

示例

下面的代码生成 CS1922：

复制代码
// cs1922.cs
public class Test
{
    public static void Main()
    {
        // Collection initializer.
        var tc = new TestClass  {1,"hello"} ; // CS1922

        // Object initalizer.
        var tc2 = new TestClass { memberA = 1, memberB = "hello" }; // OK
    }
}

public class TestClass
{
    public int memberA { get; set; }
    public string memberB { get; set; }
}

请参见

参考

对象和集合初始值设定项（C# 编程指南）


编译器错误 CS1925

[image]

Visual C# 参考：错误和警告
编译器错误 CS1925
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法使用集合初始值设定项来初始化类型为“type”的对象。

只允许对满足某些条件的集合类使用集合初始值设定项。有关更多信息，请参见对象和集合初始值设定项（C# 编程指南）。如果尝试使用嵌套在集合初始值设定项中的数组初始值设定项的缩写形式，也会生成此错误。

更正此错误

  • 通过调用其构造函数和方法来初始化对象。

示例

下面的代码生成 CS1925：

复制代码
// cs1925.cs
public class Student
{
    public int[] Scores;
}

class Test
{
    static void Main(string[] args)
    {
        Student student = new Student { Scores = { 1, 2, 3 } }; // CS1925
    }
}


编译器错误 CS1926

[image]

Visual C# 参考：错误和警告
编译器错误 CS1926
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

读取 Win32 清单文件“filename”时出错 --“error”。

如果满足以下条件，则会生成此错误：

  1. 在命名行上或通过以下方法指定了 /win32manifest 选项：右击“解决方案资源管理器”中的“项目”图标，指向“添加”，单击“新建项”，然后单击“应用程序清单文件”。

  2. 该文件损坏或缺失。

更正此错误

  1. 移除该选项。

  2. 替换、修复或重新生成该文件。

示例

在使用损坏的 win32 清单文件或缺少该文件的情况下编译时，下面的示例会生成 CS1926：

复制代码
// cs1926.cs
// Compile with: /win32manifest: ../../app.manifest
// CS1926
class Test
{
    public static int Main()
    {
        return 1;
    }
}

请参见

参考

/win32manifest（导入自定义 Win32 清单文件）（C# 编译器选项）


编译器错误 CS1928

[image]

Visual C# 参考：错误和警告
编译器错误 CS1928
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“Type”不包含“method”的定义，并且最佳扩展方法重载“method”的某些参数无效。

如果编译器找不到具有已调用方法的名称的类成员，则会生成此错误。虽然编译器可以找到具有该名称的扩展方法，但该扩展方法没有与通过方法调用传入的类型相匹配的签名。

更正此错误

  • 传入与现有扩展方法或类方法相匹配的类型。

示例

下面的代码生成 CS1928：

复制代码
// cs1928.cs
class Test
{
    static void Main()
    {
        Test t = new Test();
        t.M("hi"); // CS1928
    }
}
static class Ext
{
    public static void M(this Test t, int i)
    {
    }
}

此错误通常与 CS1503 错误（参数“n”：无法从“typeA”转换为“typeB”）一起发生。

请参见

参考

扩展方法（C# 编程指南）


编译器错误 CS1929

[image]

Visual C# 参考：错误和警告
编译器错误 CS1929
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

实例参数: 无法从“typeA”转换为“typeB”。

在尝试从没有扩展某个扩展方法的类中调用该方法时，会生成此错误。在此处显示的示例中，为派生类 A 定义了扩展方法，但没有为基类 B 定义该方法。

更正此错误

  • 为必须要调用扩展方法的类型创建一个新的扩展方法，或将该调用移到现有方法所扩展的类型的对象中。

示例

下面的代码生成 CS1928 和 CS1929：

复制代码
// cs1929.cs
using System.Linq;
    using System.Collections;

    static class Ext
    {
        public static void ExtMethod(this A a)
        {
        }
    }

    class A : B
    {
    }

    class B
    {
        static void Main()
        {
            B b = new B();
            b.ExtMethod(); // CS1929
        }
    }

请参见

参考

扩展方法（C# 编程指南）


编译器错误 CS1930

[image]

Visual C# 参考：错误和警告
编译器错误 CS1930
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

已声明范围变量“name”

查询表达式中的范围变量会一直位于范围内，直到该查询表达式终止。因此，该变量必须具有唯一标识符。

更正此错误

  • 为在查询表达式中引入的每个范围变量指定一个唯一名称。

示例

下面的示例生成 CS1930，因为同时对 from 子句中的范围变量和 let 子句所引入的范围变量使用了标识符 num。

复制代码
// cs1930.cs
using System.Linq;
class Program
{
    static void Main()
    {
        int[] nums = { 0, 1, 2, 3, 4, 5 };
        var query = from num in nums
                    let num = 3 // CS1930
                    select num;
    }
}

请参见

概念

LINQ 查询表达式（C# 编程指南）


编译器错误 CS1931

[image]

Visual C# 参考：错误和警告
编译器错误 CS1931
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

范围变量“variable”与“variable”以前的声明冲突。

就像所有其他声明一样，范围变量的声明必须具有一个标识符，该标识符在变量的声明空间内是唯一的。

更正此错误

  • 为范围变量指定一个唯一名称。

示例

下面的代码生成 CS1931，因为标识符 x 同时被用作 Main 中的局部变量和查询表达式中的范围变量：

复制代码
// cs1931.cs
class Test
    {
        static void Main()
        {
            int x = 1;
            var y = from x in Enumerable.Range(1, 100) // CS1931
                    select x;
        }
    }

请参见

概念

LINQ 查询表达式（C# 编程指南）


编译器错误 CS1932

[image]

Visual C# 参考：错误和警告
编译器错误 CS1932
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法将“expression”赋给范围变量。

无论范围变量是在 from 子句还是 let 子句中引入，编译器都必须能够推断该变量的类型。该变量不能为 null（因为 null 不是类型），并且不能为其分配类型不安全的表达式。

更正此错误

  • 移除无效的赋值运算。

  • 将表达式显式转换为允许的类型。

示例

下面的代码生成 CS1932，因为不能推断范围变量的类型。将值强制转换为预期类型可以修复该错误，如下面的示例所示。

复制代码
// CS1932.cs
using System.Linq;
class Test
{
    static void Main()
    {

        var x = from i in Enumerable.Range(1, 100)
                let k = null // CS1932
                // Try the following line instead.
                let k = (string) null
                select i;
    }
}

请参见

概念

LINQ 查询表达式（C# 编程指南）


编译器错误 CS1933

[image]

Visual C# 参考：错误和警告
编译器错误 CS1933
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

表达式不能包含查询表达式

有些变量不能用查询表达式进行初始化。常量不能用查询表达式进行初始化，因为常量只能用文本、命名常量和数学运算符的某一组合进行初始化。

更正此错误

  • 从查询变量中移除该修饰符。

示例

下面的示例生成 CS1933：

复制代码
// cs1933.cs
using System.Linq;
using System.Collections;

class P
{
    const IEnumerable e = from x in new[] { 1, 2, 3 } select x; // CS1933
    static int Main()
    {
        return 1;
    }
}

请参见

概念

LINQ 查询表达式（C# 编程指南）


编译器错误 CS1934

[image]

Visual C# 参考：错误和警告
编译器错误 CS1934
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

找不到源类型“type”的查询模式的实现。未找到“method”。请考虑显式指定范围变量“name”的类型。

如果查询表达式指定了没有为其实现任何标准查询运算符的数据源，则会生成此错误。生成此错误的一种方式是在没有为范围变量指定显式类型的情况下指定 ArrayList。

更正此错误

  • 在下面的示例中，只需指定范围变量的类型即可解决该问题：

    复制代码
    var q = from int x in list

示例

下面的示例演示一种生成 CS1934 的方式：

复制代码
// cs1934.cs
using System.Linq;
using System.Collections;
static class Test
{
    public static void Main()
    {
        var list = new ArrayList { 0, 1, 2, 3, 4, 5 };
        var q = from x in list // CS1934
                select x + 1;
    }
}

请参见

任务

如何：使用 LINQ 查询 ArrayList


编译器错误 CS1935

[image]

Visual C# 参考：错误和警告
编译器错误 CS1935
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

找不到源类型“type”的查询模式的实现。未找到“method”。是否缺少对“System.Core.dll”的引用或“System.Linq”的 using 指令?

查询中的源类型必须是 IEnumerable、IEnumerable<T>、派生类型或您或他人已为其实现标准查询运算符的类型。如果源类型是 IEnumerable 或 IEnumerable<T>，则必须添加对 system.core.dll 的引用和 System.Linq 命名空间的 using 指令，以便将标准查询运算符扩展方法放入范围中。标准查询运算符的自定义实现也必须以相同的方式，通过 using 指令及对程序集的引用（如果需要）放入范围中。

更正此错误

  • 添加所需的 using 指令以及对该项目的引用。

示例

下面的代码生成 CS1935，因为 System.Linq 的 using 指令已被注释掉：

复制代码
// cs1935.cs
// CS1935
using System;
using System.Collections.Generic;
// using System.Linq;

class Test
{
    static int Main()
    {
        int[] nums = {0,1,2,3,4,5};
        IEnumerable<int> e = from n in nums
                        where n > 3
                        select n;
        return 0;
    }
}

请参见

概念

标准查询运算符概述


编译器错误 CS1936

[image]

Visual C# 参考：错误和警告
编译器错误 CS1936
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

找不到源类型“type”的查询模式的实现。 未找到“method”。

为了查询某一源类型，该类型必须实现要在查询中调用的标准查询运算符方法。该实现可以采用通过适当的 using 指令放入范围中的类成员或扩展方法的形式。

更正此错误

  • 请确保查询的是对象集合，而不是单个对象。

  • 请确保指定了必需的 using 指令。

示例

下面的示例生成 CS1936：

复制代码
// cs1936.cs
using System.Collections;
using System.Linq;
class Test
{
    static int Main()
    {
        object obj;
        IEnumerable e = from x in obj // CS1936
                        select x;
        return 0;
    }
}

如果无意中尝试查询某一类型的单个对象而不是此类对象的集合，则通常会发生此错误。

请参见

概念

标准查询运算符概述


编译器错误 CS1937

[image]

Visual C# 参考：错误和警告
编译器错误 CS1937
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

名称“name”不在“equals”左侧的范围中。请考虑交换“equals”两侧的表达式。

equals 关键字是一种在 join 子句中使用的特殊运算符，用于确定两个表达式是否相等。左侧源序列的范围变量位于 equals 左侧的范围中，而右侧源的范围变量仅位于 equals 左侧的范围中。可以通过试验 IntelliSense 验证这一点，如下面的代码示例所示。

更正此错误

  • 交换两个范围变量的位置，如下面的示例中的注释行所示：

示例

下面的示例生成 CS1937。

复制代码
// cs1937.cs
using System.Linq;
class Test
{
    static void Main()
    {
        int[] sourceA = { 1, 2, 3, 4, 5 };
        int[] sourceB = { 3, 4, 5, 6, 7 };

        var query = from a in sourceA
                    join b in sourceB on b equals a // CS1937
                    // Try the following line instead.
                    //join b in sourceB on a equals b
                    select new { a, b };
    }
}

左侧通常称为“外侧”，而右侧通常称为“内侧”。

请参见

参考

join 子句（C# 参考）


编译器错误 CS1938

[image]

Visual C# 参考：错误和警告
编译器错误 CS1938
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

名称“name”不在“equals”右侧的范围中。请考虑交换“equals”两侧的表达式。

equals 关键字是一种在 join 子句中使用的特殊运算符，用于确定两个表达式是否相等。左侧源序列的范围变量位于 equals 左侧的范围中，而右侧源的范围变量仅位于 equals 左侧的范围中。可以通过试验 IntelliSense 验证这一点，如下面的代码示例所示。

更正此错误

  • 交换两个范围变量的位置，如下面的示例中的注释行所示：

示例

下面的代码生成 CS1938：

复制代码
// cs1938.cs
using System.Linq;
class Test
{
    static void Main()
    {
        int[] sourceA = { 1, 2, 3, 4, 5 };
        int[] sourceB = { 3, 4, 5, 6, 7 };

        var query = from a in sourceA
                    join b in sourceB on b equals a // CS1938
                    // Try the following line instead.
                    // join b in sourceB on a equals b
                    select new { a, b };
    }
}

请参见

参考

join 子句（C# 参考）


编译器错误 CS1939

[image]

Visual C# 参考：错误和警告
编译器错误 CS1939
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无法将范围变量“name”作为 out 或 ref 参数传递。

范围变量是在查询表达式中引入的只读变量，它用作源序列中每个连续元素的标识符。因为该变量不能以任何方式进行更改，所以通过 ref 或 out 传递它没有任何意义。因此，这两个操作都是无效的。

更正此错误

  • 通过值传递范围变量。

示例

下面的示例生成 CS1939：

复制代码
// cs1939.cs
using System.Linq;
class Test
{
    public static void F(ref int i)
    {
    }
    public static void Main()
    {
        var list = new int[] { 0, 1, 2, 3, 4, 5 };
        var q = from x in list
                let k = x
                select Test.F(ref x); // CS1939
    }
}


编译器错误 CS1940

[image]

Visual C# 参考：错误和警告
编译器错误 CS1940
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

找到了源类型“type”的查询模式的多个实现。对“method”的调用不明确。

如果定义了查询方法的多个实现，而编译器无法确定哪个实现最适合该查询，则会生成此错误。在下面的示例中，Select 的两个版本具有相同签名，因为它们都接受一个 int 作为输入参数，并将 int 用作返回值。

更正此错误

  • 为每个方法仅提供一个实现。

示例

下面的代码生成 CS1940：

复制代码
// cs1940.cs
using System; //must include explicitly for types defined in 3.5
class Test
{
    public delegate int Dele(int x);
    int num = 0;
    public int Select(Func<int, int> d)
    {
        return d(this.num);
    }
    public int Select(Dele d) // CS1940
    {
        return d(this.num) + 1;
    }
    public static void Main()
    {
        var q = from x in new Test()
        select x;
    }
}

请参见

概念

标准查询运算符概述


编译器错误 CS1941

[image]

Visual C# 参考：错误和警告
编译器错误 CS1941
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“clause”子句中一个表达式的类型不正确。类型推理在对“method”的调用中失败。

查询表达式中的类型推理源于数据源中的元素类型。

更正此错误

  • 如果发生此错误的原因不十分明显，请仔细检查查询，并跟踪从数据源到错误发生点的每个子句的结果的类型。

示例

下面的代码生成 CS1941，因为 equals 运算符被要求对 int 和 string 进行比较。

复制代码
// cs1941.cs
using System.Collections;
using System.Linq;
class Test
{
    static int Main()
    {
        var nums = new[] { 1, 2, 3, 4, 5, 6 };
        var words = new string[] { "lake", "mountain", "sky" };
        IEnumerable e = from n in nums
                        join w in words on n equals w // CS1941
                        select w;
        return 0;
    }
}

发生类型推理失败的方法是查询子句在编译时转换为的方法。

请参见

概念

LINQ 查询表达式（C# 编程指南）
查询操作中的类型关系 (LINQ)


编译器错误 CS1942

[image]

Visual C# 参考：错误和警告
编译器错误 CS1942
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“clause”子句中表达式的类型不正确。类型推理在对“method”的调用中失败。

如果为范围变量指定了不正确的显式类型，通常会生成此错误。

更正此错误

  • 如果范围变量是显式类型的，请确保该类型与循环访问的集合中的元素类型相同或可从该类型隐式转换。如果范围变量的前面带有 var 关键字，请移除 var。

示例

下面的代码生成 CS1942：

复制代码
// cs1942.cs
class Program
    {
        static void Main(string[] args)
        {
            var x = from var i in Enumerable.Range(1, 100) // CS1949
                    select i; //CS1942
        }
    }

CS1942 与 CS1949 相关，因为对范围变量使用 var 会导致因 var 不是类型而使基础 Cast<T> 运算失败。

请参见

概念

LINQ 查询表达式（C# 编程指南）

参考

var（C# 参考）


编译器错误 CS1943

[image]

Visual C# 参考：错误和警告
编译器错误 CS1943
示例 发送反馈意见

更新：2007 年 11 月

错误消息

在具有源类型“type”的查询表达式中后面的 from 子句中不允许使用类型为“type”的表达式。类型推理在对“method”的调用中失败。

所有范围变量都必须表示可查询的类型。

更正此错误

  1. 请确保该类型是实现 IEnumerable、IEnumerable<T> 或派生接口的可查询类型，或者是定义了查询模式的任何其他类型。

  2. 如果该类型是非泛型 IEnumerable，请在范围变量上提供显式类型。

示例

下面的代码生成 CS1943：

复制代码
// cs1943.cs
using System.Linq;
class Test
{
    class TestClass
    { }
    static void Main()
    {
        int[] nums = { 0, 1, 2, 3, 4, 5 };
        TestClass tc = new TestClass();

        var x = from n in nums
                from s in tc // CS1943
                select n + s;
    }
}


编译器错误 CS1944

[image]

Visual C# 参考：错误和警告
编译器错误 CS1944
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树不能包含不安全的指针操作

表达式目录树不支持指针类型，因为 Expression<(Of <(TDelegate>)>)..::.Compile 方法只能生成可验证代码。请参见注释。

更正此错误

  • 请不要在尝试创建表达式目录树时使用指针类型。

示例

下面的示例生成 CS1944：

复制代码
// cs1944.cs
// Compile with: /unsafe
using System.Linq.Expressions;
unsafe class Test
{
    public delegate int* D(int i);
    static void Main()
    {
        Expression<D> tree = x => &x; // CS1944
    }
}

using System.Linq.Expressions;
unsafe class Test
{
    public delegate int* D(int i);
    static void Main()
    {
        Expression<D> tree = x => &x; // CS1944
    }
}

在有些情况下，可以在表达式目录树中包含指针。例如，考虑以下代码：

Expression<Func<int*[], int*[]>) e = (int*[] i)=>i;

此代码是有效的表达式目录树，因为类型参数都不是指针类型。它们是指针数组，而数组不是指针类型。此外，表达式目录树体对使用任何指针都不会存在危险。

请参见

参考

unsafe（C# 参考）


编译器错误 CS1945

[image]

Visual C# 参考：错误和警告
编译器错误 CS1945
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树不能包含匿名方法表达式。

表达式目录树只能包含表达式。匿名方法只能表示语句。

更正此错误

  • 不要尝试用语句创建表达式目录树。

示例

下面的代码生成 CS1945：

复制代码
// cs1945.cs
using System;
using System.Linq.Expressions;

public delegate void D();
class Test
{
    static void Main()
    {
        Expression<Func<int, Func<int, bool>>> tree = (x => delegate(int i) { return true; }); // CS1945
    }
}

请参见

概念

表达式目录树

参考

语句、表达式和运算符（C# 编程指南）


编译器错误 CS1946

[image]

Visual C# 参考：错误和警告
编译器错误 CS1946
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法将匿名方法表达式转换为表达式目录树。

匿名方法表示一组语句，而表达式目录树不能包含任何语句。因此，匿名方法不能由表达式目录树表示。

更正此错误

  • 将匿名方法更改为 lambda 表达式。

示例

下面的示例生成 CS1946：

复制代码
// cs1946.cs
using System;
    using System.Linq.Expressions;

    public delegate void D();

    class Test
    {
        static void Main()
        {
            Expression<D> tree = delegate() { }; //CS1946
            // Try using a lambda expression instead.
            // Expression<D> tree = (x) => x + 1;
        }
    }

请参见

概念

表达式目录树

参考

匿名方法（C# 编程指南）


编译器错误 CS1947

[image]

Visual C# 参考：错误和警告
编译器错误 CS1947
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法对范围变量“variable name”赋值 -- 它是只读的。

范围变量类似于 foreach 语句中的迭代变量。不能在查询表达式中为该变量赋值。

更正此错误

  1. 移除对范围变量的赋值。

  2. 如果需要，通过使用 let 子句引入一个新的范围变量，并使用该变量存储值。

示例

下面的代码生成 CS1947：

复制代码
// cs1947.cs
using System.Linq;
class Test
{
    static void Main()
    {
        int[] array = new int[] { 1, 2, 3, 4, 5 };
        var x = from i in array
                let k = i
                select i = 5; // CS1947
        x.ToList();
    }
}

请参见

概念

LINQ 查询表达式（C# 编程指南）


编译器错误 CS1948

[image]

Visual C# 参考：错误和警告
编译器错误 CS1948
示例 发送反馈意见

更新：2007 年 11 月

错误消息

范围变量“name”不能与方法类型参数同名

同一声明空间不能包含同一标识符的两个声明。

更正此错误

  • 更改范围变量或类型参数的名称。

示例

下面的示例生成 CS1948，因为同时对方法 TestMethod 的范围变量和类型参数使用了标识符 T：

复制代码
// cs1948.cs
using System.Linq;
class Test
{
    public void TestMethod<T>(T t)
    {
        var x = from T in Enumerable.Range(1, 100) // CS1948
                select T;
    }
}


编译器错误 CS1949

[image]

Visual C# 参考：错误和警告
编译器错误 CS1949
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

不能在范围变量声明中使用上下文关键字“var”。

编译器会对范围变量进行隐式类型化。无需对范围变量使用 var。

更正此错误

  • 从范围变量前面移除 var 关键字。

示例

下面的示例生成 CS1949：

复制代码
// cs1949.cs
using System;
using System.Linq;
class Test
{
    static void Main()
    {
        var x = from var i in Enumerable.Range(1, 100) // CS1949
        select i;
    }
}

请参见

概念

LINQ 查询表达式（C# 编程指南）
LINQ 查询介绍


编译器错误 CS1950

[image]

Visual C# 参考：错误和警告
编译器错误 CS1950
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

集合初始值设定项的最佳重载 Add 方法“name”具有一些无效参数。

若要支持集合初始值设定项，则类必须实现 IEnumerable 并具有公共 Add 方法。若要通过使用集合初始值设定项初始化类型，则 Add 方法的输入参数必须与您尝试添加的对象类型兼容。

更正此错误

  • 在集合初始值设定项中使用兼容类型。

  • 修改集合类型中 Add 方法的输入参数和/或辅助功能。

  • 添加一个新 Add 方法，该方法具有与您正在传入的类型相匹配的输入参数。

  • 使集合类成为泛型集合类，以使其可以具有一个接受任何传入类型的 Add 方法。

示例

下面的示例将生成 CS1950：

复制代码
// cs1950.cs
using System.Collections;
class TestClass : CollectionBase
{
    public void Add(int c)
    {
    }
}

class Test
{
    static void Main()
    {
        TestClass t = new TestClass { "hi" }; // CS1950
    }
}

请参见

参考

对象和集合初始值设定项（C# 编程指南）


编译器错误 CS1951

[image]

Visual C# 参考：错误和警告
编译器错误 CS1951
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树 lambda 不能包含 out 或 ref 参数。

表达式目录树只将表达式表示为数据结构。无法表示特定的内存位置，但如果通过引用传递参数，则必须这样做。

更正此错误

  • 唯一的选择是移除委托定义中的 ref 修饰符并按值传入参数。

示例

下面的示例将生成 CS1951：

复制代码
// cs1951.cs
using System.Linq;
public delegate int TestDelegate(ref int i);
class Test
{
    static void Main()
    {
        System.Linq.Expressions.Expression<TestDelegate> tree1 = (ref int x) => x; // CS1951
    }
}

请参见

概念

表达式目录树


编译器错误 CS1952

[image]

Visual C# 参考：错误和警告
编译器错误 CS1952
示例 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树 lambda 不能包含具有变量参数的方法

在编译为表达式目录树的 lambda 表达式中不允许有不受支持的关键字 __arglist。

更正此错误

  • 不要使用 __arglist，就当从未听说过此关键字。

示例

下面的代码将产生 CS1952：

复制代码
// cs1952.cs
using System;
using System.Linq.Expressions;

class Test
{
    public static int M(__arglist)
    {
        return 1;
    }

    static int Main()
    {
        Expression<Func<int, int>> f = x => Test.M(__arglist(x)); // CS1952
        return 1;
    }
}


编译器错误 CS1953

[image]

Visual C# 参考：错误和警告
编译器错误 CS1953
示例 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树 lambda 不能包含方法组。

方法调用需要 () 运算符。不具有该运算符的方法名称将引用方法组，该方法组是所有具有该名称的重载方法集。

更正此错误

  • 如果打算调用该方法，请添加 () 运算符。

示例

下面的示例将生成 CS1953：

复制代码
// cs1953.cs
using System;
using System.Linq.Expressions;
class CS1953
{
    public static void Main()
    {
        double num = 10;
        Expression<Func<bool>> testExpr =
              () => num.GetType is int; // CS1953
    }
}


编译器错误 CS1954

[image]

Visual C# 参考：错误和警告
编译器错误 CS1954
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

无法使用集合初始值设定项元素的最佳重载方法匹配项“method”。集合初始值设定项“Add”方法不能具有 ref 或 out 参数。

为了使用集合初始值设定项初始化集合类，此类必须具有 public Add 方法，且该方法不是 ref 或 out 参数。

更正此错误

  • 如果可以修改该集合类的源代码，则提供一个不采用 ref 或 out 参数的 Add 方法。

  • 如果无法修改该集合类，则使用它所提供的构造函数对其进行初始化。集合初始值设定项无法与集合类一起使用。

示例

下面的示例将生成 CS1954，因为 MyList 中的 Add 列表的唯一可用重载具有 ref 参数。

复制代码
// cs1954.cs
using System.Collections.Generic;
class MyList<T> : IEnumerable<T>
{
    List<T> _list;
    public void Add(ref T item)
    {
        _list.Add(item);
    }

    public System.Collections.Generic.IEnumerator<T> GetEnumerator()
    {
        int index = 0;
        T current = _list[index];
        while (current != null)
        {
            yield return _list[index++];
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class MyClass
{
    public string tree { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        MyList<MyClass> myList = new MyList<MyClass> { new MyClass { tree = "maple" } }; // CS1954
    }
}

请参见

参考

对象和集合初始值设定项（C# 编程指南）


编译器错误 CS1955

[image]

Visual C# 参考：错误和警告
编译器错误 CS1955
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能像使用方法那样使用不可调用的成员“name”。

只能调用方法和委托。如果尝试将空括号与除方法或委托之外的其他内容一起使用，则将生成此错误。

更正此错误

  • 从表达式中移除括号。

示例

下面的代码将生成 CS 1955，因为该代码尝试开始使用整数和属性：

复制代码
// cs1955.cs
class A
{
    public int x = 0;
    public int X
    {
        get { return x; }
        set { x = value; }
    }
}

class Test
{
    static int Main()
    {
        A a = new A();
        a.x(); // CS1955
        a.X(); // CS1955
    }
}


编译器错误 CS1958

[image]

Visual C# 参考：错误和警告
编译器错误 CS1958
示例 发送反馈意见

更新：2007 年 11 月

错误消息

对象和集合初始值设定项表达式不能应用于委托创建表达式，

与类或结构不同，委托不具有任何成员，因此对象初始值设定项没有要进行初始化的内容。如果遇到此错误，则可能是因为委托创建表达式后面有大括号。只需移除这些大括号，此错误即会消失。

更正此错误

  • 移除大括号。

示例

下面的代码将产生 CS1958：

复制代码
// cs1958.cs
public class MemberInitializerTest
{
    delegate void D<T>();
    public static void GenericMethod<T>() { }
    public static void Run()
    {
        D<int> genD = new D<int>(GenericMethod<int>) { }; // CS1958
       // Try the following line instead
      // D<int> genD = new D<int>(GenericMethod<int>);
    }
}


编译器错误 CS2001

[image]

Visual C# 参考：错误和警告
编译器错误 CS2001
发送反馈意见

更新：2007 年 11 月

错误消息

未能找到源文件“file”

给编译器传递了源文件名，但未能找到该文件。请检查文件名的拼写和文件的位置。


编译器错误 CS2003

[image]

Visual C# 参考：错误和警告
编译器错误 CS2003
发送反馈意见

更新：2007 年 11 月

错误消息

响应文件“file”被包含多次

响应文件多次传递给了编译器。对于每个输出文件，响应文件只能传递给编译器一次。

有关响应文件的详细信息，请参见 @（指定响应文件）。


编译器错误 CS2005

[image]

Visual C# 参考：错误和警告
编译器错误 CS2005
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“option”选项缺少文件规范

只部分指定了编译器选项。

例如，当使用 /recurse 时，必须指定要搜索的文件：/recurse:filename.cs。

示例

下面的示例生成 CS2005。

复制代码
// CS2005.cs
// compile with: /recurse:
// CS2005 expected
class x
{
   public static void Main() {}
}


编译器错误 CS2006

[image]

Visual C# 参考：错误和警告
编译器错误 CS2006
发送反馈意见

更新：2007 年 11 月

错误消息

命令行语法错误:“option”选项缺少“text”

option 的语法要求附加的文本。有关信息，请参见编译器选项。


编译器错误 CS2007

[image]

Visual C# 参考：错误和警告
编译器错误 CS2007
发送反馈意见

更新：2007 年 11 月

错误消息

无法识别的命令行选项：“option”

传递给编译器的字符串不是编译器选项，即使它以正斜杠 (/) 开头。

下面的示例生成 CS2007：

复制代码
// CS2007.cs
// compile with: /recur
// CS2007 expected
class x
{
   public static void Main() {}
}


编译器错误 CS2008

[image]

Visual C# 参考：错误和警告
编译器错误 CS2008
发送反馈意见

更新：2007 年 11 月

错误消息

未指定输入

调用了编译器并指定了编译器选项，但未传递任何源代码文件。


编译器错误 CS2011

[image]

Visual C# 参考：错误和警告
编译器错误 CS2011
发送反馈意见

更新：2007 年 11 月

错误消息

无法打开响应文件“file”

在编译中指定了响应文件，但编译器未能找到并打开该文件。

有关响应文件的详细信息，请参见 @（指定响应文件）。


编译器错误 CS2012

[image]

Visual C# 参考：错误和警告
编译器错误 CS2012
发送反馈意见

更新：2007 年 11 月

错误消息

无法打开“file”进行写入

当使用 /bugreport:file 编译器选项时，未能打开文件进行写入。确保指定的文件名有效并且该文件不是只读的。


编译器错误 CS2013

[image]

Visual C# 参考：错误和警告
编译器错误 CS2013
发送反馈意见

更新：2007 年 11 月

错误消息

无效的图像基数“value”

传递给 /baseaddress 编译器选项的值无效（不是数字）。

下面的示例生成 CS2013：

复制代码
// CS2013.cs
// compile with: /target:library /baseaddress:x
// CS2013 expected
class MyClass
{
}


编译器错误 CS2015

[image]

Visual C# 参考：错误和警告
编译器错误 CS2015
发送反馈意见

更新：2007 年 11 月

错误消息

“file”是二进制文件而不是文本文件

传递给编译器的文件是二进制文件。而编译器需要的是源代码文件。


编译器错误 CS2016

[image]

Visual C# 参考：错误和警告
编译器错误 CS2016
发送反馈意见

更新：2007 年 11 月

错误消息

代码页“codepage”无效或未安装

传递给 /codepage 编译器选项的值无效。

下面的示例生成 CS2016：

复制代码
// CS2016.cs
// compile with: /codepage:x
// CS2016 expected
class MyClass
{
   public static void Main()
   {
   }
}


编译器错误 CS2017

[image]

Visual C# 参考：错误和警告
编译器错误 CS2017
发送反馈意见

更新：2007 年 11 月

错误消息

如果生成模块或库，则无法指定 /main

当正在生成 /target:library 时不能指定 Main 入口点。

下面的示例生成 CS2017：

复制代码
// CS2017.cs
// compile with: /main:MyClass /target:library
// CS2017 expected
class MyClass
{
   public static void Main()
   {
   }
}


编译器错误 CS2018

[image]

Visual C# 参考：错误和警告
编译器错误 CS2018
发送反馈意见

更新：2007 年 11 月

错误消息

无法找到消息文件“cscmsgs.dll”

未找到包含编译器的错误信息和警告消息的 .dll 文件。该文件与其他编译器支持文件必须在同一目录中。


编译器错误 CS2019

[image]

Visual C# 参考：错误和警告
编译器错误 CS2019
发送反馈意见

更新：2007 年 11 月

错误消息

/target 的目标类型无效: 必须指定“exe”、“winexe”、“library”或“module”

使用了 /target 编译器选项，但传递了无效参数。若要解决该错误，请使用适合输出文件的 /target 选项格式重新编译程序。

下面的示例生成 CS2017：

复制代码
// CS2019.cs
// compile with: /target:libra
// CS2019 expected
class MyClass
{
}


编译器错误 CS2020

[image]

Visual C# 参考：错误和警告
编译器错误 CS2020
发送反馈意见

更新：2007 年 11 月

错误消息

只有第一组输入文件能生成非“模块”的目标

在多输出编译中，第一个输出文件必须用 /target:exe、/target:winexe 或 /target:library 生成。后面的任何输出文件必须用 /target:module 生成。


编译器错误 CS2021

[image]

Visual C# 参考：错误和警告
编译器错误 CS2021
发送反馈意见

更新：2007 年 11 月

错误消息

文件名“file”太长或无效

传递给 C# 编译器的所有文件名的长度都必须小于 _MAX_PATH（定义在 Windows 头文件中），该编译器将在以下情况中给出该错误：

  • 文件名（包括路径）的长度大于 _MAX_PATH。

  • 文件名包含无效字符。

  • 文件名包含某些通配符，而在此处（如资源文件名中）并不允许使用通配符。


编译器错误 CS2022

[image]

Visual C# 参考：错误和警告
编译器错误 CS2022
发送反馈意见

更新：2007 年 11 月

错误消息

选项“/out”和“/target”必须出现在源文件名之前

当在命令行中指定 /out（设置输出文件名）和 /target（指定输出文件格式）编译器选项时，它们必须位于源代码文件之前。


编译器错误 CS2024

[image]

Visual C# 参考：错误和警告
编译器错误 CS2024
发送反馈意见

更新：2007 年 11 月

错误消息

文件节的对齐编号“#”无效

给 /filealign 编译器选项传递的值无效。

下面的示例生成 CS2024：

复制代码
// CS2024.cs
// compile with: /filealign:ex
// CS2024 expected
class MyClass
{
   public static void Main()
   {
   }
}


编译器错误 CS2032

[image]

Visual C# 参考：错误和警告
编译器错误 CS2032
发送反馈意见

更新：2007 年 11 月

错误消息

命令行中或响应文件中不允许有字符“character”。

csc.exe 的响应文件和命令行选项不允许包含 0-31 范围内的低 ASCII 码控制字符或者管道 (|) 字符。您通常无法从命令行中直接生成此错误，因为命令行处理器将筛选出无效的字符，这一点与集成开发环境 (IDE) 一样。您可以使用响应文件生成此错误，如下所示：

生成此错误

  1. 在“我的文档”中创建一个名为 CS2032.rsp 的文件，它包含行 /target:exe /out:cs|2032.exe cs2032.cs。

  2. 创建一个名为 cs2032.cs 的文件，它包含“我的文档”中的一切。

  3. 单击“开始”，然后指向“所有程序”，指向“Microsoft Visual Studio 2005”，指向“Visual Studio 2005 工具”，然后单击“Visual Studio 命令提示”。

    “Visual Studio 命令提示”窗口打开。

  4. 在“Visual Studio 命令提示”窗口中，将当前的目录更改为“我的文档”。

  5. 从“Visual Studio 命令提示”中运行以下内容：csc @cs2032.rsp

  6. 出现 CS2032 错误信息。


编译器错误 CS2033

[image]

Visual C# 参考：错误和警告
编译器错误 CS2033
发送反馈意见

更新：2007 年 11 月

错误消息

包含短文件名“filename”的长文件名已存在，无法创建同名短文件名

编译一个名称长度超过八个字符的 C# 文件。然后编译另一个文件，但其名称比上一个文件短，例如在名称的前六个字符后面加上“~1”。第二个编译将生成此错误。

要解决此错误，请将短文件名重命名为一个和长文件名不冲突的名称。


编译器错误 CS2034

[image]

Visual C# 参考：错误和警告
编译器错误 CS2034
示例 发送反馈意见

更新：2007 年 11 月

错误消息

一个声明外部别名的 /reference 选项只能有一个文件名。若要指定多个别名或文件名，请使用多个 /reference 选项。

若要指定两个别名和/或文件名，请使用两个 /reference 选项，如下所示：

示例

下面的代码将生成错误 CS2034。

复制代码
// CS2034.cs
// compile with: /r:A1=cs2034a1.dll;A2=cs2034a2.dll
// to fix, compile with: /r:A1=cs2034a1.dll /r:A2=cs2034a2.dll
// CS2034
extern alias A1;
extern alias A2;
using System;


编译器错误 CS2035

[image]

Visual C# 参考：错误和警告
编译器错误 CS2035
示例 发送反馈意见

更新：2007 年 11 月

错误消息

命令行语法错误:“compiler_option”选项缺少“:<number>”

有些编译器选项需要一个值。

示例

下面的示例生成 CS2035。

复制代码
// CS2035.cs
// compile with: /baseaddress
// CS2035 expected


编译器错误 CS2036

[image]

Visual C# 参考：错误和警告
编译器错误 CS2036
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

要使用 /pdb 选项，必须同时使用 /debug 选项。

仅为调试版本生成程序数据库文件。因此在零售内部版本中 /pdb 选项无意义。

更正此错误

  • 添加 /debug 编译器选项。

  • 移除 /pdb 编译器选项。

示例

在使用 /pdb 选项但未使用 /debug 选项编译下面的示例时，它将生成 CS2036：

复制代码
// cs2036.cs
// Compile with: /pdb
// CS2036
class Test
{
    public static int Main()
    {
        return 1;
    }
}

请参见

参考

/pdb（指定调试符号文件）（C# 编译器选项）
/debug（发出调试信息）（C# 编译器选项）


编译器错误 CS5001

[image]

Visual C# 参考：错误和警告
编译器错误 CS5001
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

程序“program”不包含适合入口点的静态“Main”方法

在生成可执行文件的代码中找不到具有正确签名的静态 Main 方法时会发生此错误。如果使用错误的大小写定义入口点函数 Main（如小写的 main），也会发生此错误。

  • Main 必须声明为静态且必须返回 void 或 int，此外它还必须或者没有任何参数或者有一个 string[] 类型的参数。

示例

下面的示例生成 CS5001：

复制代码
// CS5001.cs
// CS5001 expected
public class a
{
   // Uncomment the following line to resolve.
   // static void Main() {}
}

请参见

概念

Main() 和命令行参数（C# 编程指南）


编译器警告 CS3024

[image]

Visual C# 参考：错误和警告
编译器警告 CS3024
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

约束类型“type”不符合 CLS。

编译器发出此警告是因为：将不符合 CLS 的类型用作泛型类型约束会导致用某些语言编写的代码无法使用泛型类。

消除此警告

  • 对类型约束使用符合 CLS 的类型。

示例

下面的示例在多个位置生成 CS3024：

复制代码
// cs3024.cs
// Compile with: /target:library
 [assembly: System.CLSCompliant(true)]

[type: System.CLSCompliant(false)]
public class TestClass // CS3024
{
    public ushort us;
}
[type: System.CLSCompliant(false)]
public interface ITest // CS3024
{}
public interface I<T> where T : TestClass
{}
public class TestClass_2<T> where T : ITest
{}
public class TestClass_3<T> : I<T> where T : TestClass
{}
public class TestClass_4<T> : TestClass_2<T> where T : ITest
{}
public class Test
{
    public static int Main()
    {
        return 0;
    }
}

请参见

参考

类型参数的约束（C# 编程指南）


编译器警告（等级 1）CS0183

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0183
发送反馈意见

更新：2007 年 11 月

错误消息

给定表达式始终为所提供的（“type”）类型

如果条件语句始终计算为 true，则不需要条件语句。当您试图使用 is 运算符计算类型时，会出现此警告。如果计算的是值类型，则检查是不必要的。

下面的示例生成 CS0183：

复制代码
// CS0183.cs
// compile with: /W:1
using System;
public class Test
{
   public static void F(Int32 i32, String str)
   {
      if (str is Object)          // OK
         Console.WriteLine( "str is an object" );
      else
         Console.WriteLine( "str is not an object" );

      if (i32 is Object)   // CS0183
         Console.WriteLine( "i32 is an object" );
      else
         Console.WriteLine( "i32 is not an object" ); // never reached
   }

   public static void Main()
   {

      F(0, "CS0183");
      F(120, null);
   }
}


编译器警告（等级 1）CS0184

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0184
发送反馈意见

更新：2007 年 11 月

错误消息

给定表达式始终不是所提供的 (“type”) 类型

表达式决不可能为 true，因为正测试的变量既没有声明为 type，也不是从 type 派生的。

下面的示例生成 CS0184：

复制代码
// CS0184.cs
// compile with: /W:1
class MyClass
{
   public static void Main()
   {
      int i = 0;
      if (i is string)   // CS0184
         i++;
   }
}


编译器警告（等级 1）CS0197

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0197
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于“argument”是引用封送类的字段，将它作为 ref 或 out 参数传递或获取它的地址可能导致运行时异常

从 MarshalByRefObject 派生（直接或间接）的任何类均是引用封送类。这样的类可以通过引用跨越进程和计算机边界进行封送。因此，该类的实例可以是远程对象的代理。不能将代理对象的字段作为 ref 或 out 传递。因此，不能将这种类的字段作为 ref 或 out 传递，除非实例是 this（它不能是代理对象）。

示例

下面的示例生成 CS0197。

复制代码
// CS0197.cs
// compile with: /W:1
class X : System.MarshalByRefObject
{
   public int i;
}

class M
{
   public int i;
   static void AddSeventeen(ref int i)
   {
      i += 17;
   }

   static void Main()
   {
      X x = new X();
      x.i = 12;
      AddSeventeen(ref x.i);   // CS0197

      // OK
      M m = new M();
      m.i = 12;
      AddSeventeen(ref m.i);
   }
}


编译器警告（等级 1）CS0420

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0420
发送反馈意见

更新：2007 年 11 月

错误消息

“identifier”：对 volatile 字段的引用不被视为 volatile

volatile 字段不能像正常情况那样使用 ref 或 out 参数进行传递，因为它在函数的范围内不会被视为 volatile。这也有例外情况，例如当调用互锁 API 时。对于任何警告，您可以使用 #pragma warning 在您特意使用 volatile 字段作为引用参数的情况（这种情况很少出现）中禁用此警告。

下面的示例生成 CS0420：

复制代码
// CS0420.cs
// compile with: /W:1
using System;

class TestClass
{
   private volatile int i;

   public void TestVolatile(ref int ii)
   {
   }

   public static void Main()
   {
      TestClass x = new TestClass();
      x.TestVolatile(ref x.i);   // CS0420
   }
}


编译器警告（等级 1）CS0465

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0465
示例 发送反馈意见

更新：2007 年 11 月

错误消息

引入“Finalize”方法会妨碍析构函数调用。您是要声明析构函数吗？

当您使用签名为 public virtual void Finalize 的方法创建类时，将发生此警告。

如果将这种类用作基类并且派生类定义一个析构函数，则该析构函数将重写基类的 Finalize 方法，而不是 Finalize。

示例

下面的示例生成 CS0465。

复制代码
// CS0465.cs
// compile with: /target:library
class A
{
   public virtual void Finalize() {}   // CS0465
}

// OK
class B
{
   ~B() {}
}


编译器警告（等级 1）CS0602

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0602
发送反馈意见

更新：2007 年 11 月

错误消息

“old_feature”功能已被否决。请改用“new_feature”

仍然支持在代码中使用的语言功能 (old_feature)，但这种支持可能会在未来的版本中被移除。您应使用推荐的语法 (new_feature)。


编译器警告（等级 1）CS0612

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0612
发送反馈意见

更新：2007 年 11 月

错误消息

“member”已过时

类设计器使用 Obsolete 属性标记了成员。这意味着在类的未来版本中可能不支持该成员。

下面的示例显示访问过时的成员如何生成 CS0612：

复制代码
// CS0612.cs
// compile with: /W:1
using System;

class MyClass
{
   [Obsolete]
   public static void ObsoleteMethod()
   {
   }

   [Obsolete]
   public static int ObsoleteField;
}

class MainClass
{
   static public void Main()
   {
      MyClass.ObsoleteMethod();    // CS0612 here: method is deprecated
      MyClass.ObsoleteField = 0;   // CS0612 here: field is deprecated
   }
}


编译器警告（等级 1）CS0626

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0626
发送反馈意见

更新：2007 年 11 月

错误消息

方法、运算符或访问器“method”被标记为 external 并且没有属性。请考虑添加 DllImport 属性以指定外部实现。

标记为 extern 的方法也应使用属性（如 DllImport 属性）进行标记。

属性指定在哪个位置实现方法。在运行时，程序将需要此信息。

下面的示例生成 CS0626：

复制代码
// CS0626.cs
// compile with: /warnaserror
using System.Runtime.InteropServices;

public class MyClass
{
   static extern public void M(); // CS0626
   // try the following line
   // [DllImport("mydll.dll")] static extern public void M();

   public static void Main()
   {
   }
}


编译器警告（等级 1）CS0657

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0657
发送反馈意见

更新：2007 年 11 月

错误消息

“attribute modifier”对于此声明不是有效的属性位置。此声明的有效属性位置是“locations”。该块中的所有属性都将被忽略。

编译器在无效的位置找到了属性修饰符。有关更多信息，请参见属性目标。

下面的示例生成 CS0657：

复制代码
// CS0657.cs
// compile with: /target:library
public class TestAttribute : System.Attribute {}
[return: Test]   // CS0657 return not valid on a class
class Class1 {}


编译器警告（等级 1）CS0658

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0658
发送反馈意见

更新：2007 年 11 月

错误消息

“attribute modifier”不是可识别的属性位置。该块中的所有属性都将被忽略。

指定了无效的属性修饰符。有关更多信息，请参见属性目标。

下面的示例生成 CS0658：

复制代码
// CS0658.cs
using System;
public class TestAttribute : Attribute{}
[badAttributeLocation: Test]   // CS0658, badAttributeLocation is invalid
class ClassTest
{
   public static void Main()
   {
   }
}


编译器警告（等级 1）CS0672

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0672
发送反馈意见

更新：2007 年 11 月

错误消息

成员“member1”重写已过时的成员“member2”。向“member1”中添加 Obsolete 属性

编译器找到了标记为 obsolete 的方法的 override。然而，重写方法本身未标记为 obsolete。如果被调用，重写方法仍将生成 CS0612。

查看您的方法声明并显式指示是否应将方法（及其所有重写）标记为 obsolete。

下面的示例生成 CS0672：

复制代码
// CS0672.cs
// compile with: /W:1
class MyClass
{
   [System.Obsolete]
   public virtual void ObsoleteMethod()
   {
   }
}

class MyClass2 : MyClass
{
   public override void ObsoleteMethod()   // CS0672
   {
   }
}

class MainClass
{
   static public void Main()
   {
   }
}


编译器警告（等级 1）CS0684

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0684
发送反馈意见

更新：2007 年 11 月

错误消息

“interface”接口被标记为“CoClassAttribute”而不是“ComImportAttribute”

如果在接口上指定 CoClassAttribute，则还必须指定 ComImportAttribute。

下面的示例生成 CS0684：

复制代码
// CS0684.cs
// compile with: /W:1
using System;
using System.Runtime.InteropServices;

[CoClass(typeof(C))] // CS0684
// try the following line instead
// [CoClass(typeof(C)), ComImport]
interface I
{
}

class C
{
   static void Main() {}
}


编译器警告（等级 1）CS0688

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0688
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“method1”具有链接要求，但重写或实现的“method2”没有链接要求。可能存在安全漏洞。

对派生类方法设置的链接要求，可以通过调用基类方法轻松规避此要求。若要关闭此安全漏洞，基类方法同样需要使用该链接要求。有关更多信息，请参见 Demand vs. LinkDemand。

示例

下面的示例生成 CS0688。若要解决此警告但不修改基类，请从重写方法中移除安全属性。这不能解决安全问题。

复制代码
// CS0688.cs
// compile with: /W:1
using System;
using System.Security.Permissions;

class Base
{
    //Uncomment the following line to close the security hole
    //[FileIOPermission(SecurityAction.LinkDemand, All=@"C:\\")]
    public virtual void DoScaryFileStuff()
    {
    }
}

class Derived: Base
{
    [FileIOPermission(SecurityAction.LinkDemand, All=@"C:\\")] // CS0688
    public override void DoScaryFileStuff()
    {
    }
    static void Main()
    {
    }
}


编译器警告（等级 1）CS0809

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0809
示例 发送反馈意见

更新：2007 年 11 月

错误消息

过时成员“memberA”重写未过时成员“memberB”。

通常，标记为过时的成员不应该重写没有标记为过时的成员。在 Visual Studio 2008 中将生成此警告，但在 Visual Studio 2005 中不会生成此警告。

更正此错误

  • 从重写成员中移除 Obsolete 属性，或将该属性添加到基类成员中。

示例

复制代码
// CS0809.cs
public class Base
{
    public virtual void Test1()
    {
    }
}
public class C : Base
{
    [System.Obsolete()]
    public override void Test1() // CS0809
    {
    }
}


编译器警告（等级 1）CS0824

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS0824
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

构造函数“name”标记为 external。

构造函数可能标记为 extern。但是，编译器无法验证构造函数是否确实存在。因此将生成该警告。

移除此警告

  1. 使用 pragma 警告指令忽略此警告。

  2. 将构造函数移到类型内。

示例

下面的代码将生成 CS0824：

复制代码
// cs0824.cs
public class C
{
    extern C(); // CS0824
    public static int Main()
    {
        return 1;
    }
}

请参见

参考

extern（C# 参考）
#pragma warning（C# 参考）


编译器警告（等级 1）CS1030

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1030
发送反馈意见

更新：2007 年 11 月

错误消息

#warning:“text”

显示用 #warning 指令定义的警告文本。

下面的示例显示如何创建用户定义的警告：

复制代码
// CS1030.cs
class Sample
{
   static void Main()
   {
      #warning Let's give a warning here   // CS1030
   }
}


编译器警告（等级 1）CS1058

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1058
示例 发送反馈意见

更新：2007 年 11 月

错误消息

前一个 catch 子句已经捕获所有异常。引发的所有异常均被包装在 System.Runtime.CompilerServices.RuntimeWrappedException 中

如果 catch() 块在 catch (System.Exception e) 块后没有指定的异常类型，则该属性会导致 CS1058。该警告建议 catch() 块将不捕获任何异常。

如果在 AssemblyInfo.cs 文件中将 RuntimeCompatibilityAttribute 设置为 False，则 catch (System.Exception e) 块之后的 catch() 块可捕获非 CLS 异常：[assembly: RuntimeCompatibilityAttribute(WrapNonExceptionThrows = false)]。如果未将此属性显式设置为 False，则所有引发的非 CLS 异常将包装为异常，并且 catch (System.Exception e) 块将捕获它们。有关更多信息，请参见如何：捕捉非 CLS 异常。

示例

下面的示例生成 CS1058。

复制代码
// CS1058.cs
// CS1058 expected
using System.Runtime.CompilerServices;

// the following attribute is set to true by default in the C# compiler
// set to false in your source code to resolve CS1058
[assembly: RuntimeCompatibilityAttribute(WrapNonExceptionThrows = true)]

class TestClass
{
   static void Main()
   {
      try {}

      catch (System.Exception e) {
         System. Console.WriteLine("Caught exception {0}", e);
      }

      catch {}   // CS1058. This line will never be reached.
   }
}


编译器警告（等级 1）CS1060

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1060
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

使用了可能未赋值的字段“name”。如果结构未赋值，则结构实例变量在初始化时将不被赋值。

在未进行显式初始化的情况下，结构成员将初始化为其默认值。类类型（及其他引用类型）的默认值为 null。如果在没有对类进行初始化的情况下就尝试访问它，则会在运行时引发 NullReferenceException。编译器无法最终确定类成员是否将进行初始化，因此 CS1060 是警告而不是错误。

更正此错误

  • 为 struct 提供一个初始化其所有成员的构造函数。

示例

下面的代码生成 CS1060，因为类类型 U 是 struct S 的成员但从未初始化。

复制代码
// cs1060.cs
namespace CS1060
{
    public class U
    {
        public int i;
    }

    public struct S
    {
        public U u;
        // Add constructor to correct the error.
        //public S(int val)
        //{
        //    u = new U() { i = val };
        //}
    }
    public class Test
    {
        static void Main()
        {
            S s;
            s.u.i = 5;  // CS1060

            //Try these lines instead, and uncomment the constructor in S
            // S s = new S(0);
            // s.u.i = 5;

        }
    }
}

请参见

参考

结构（C# 编程指南）


编译器警告（等级 1）CS1200

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1200
发送反馈意见

更新：2007 年 11 月

错误消息

“无效功能”这一功能已不推荐使用。请改用“有效功能”。

您所尝试使用的功能已不推荐使用。请更新代码以改用有效功能。


编译器警告（等级 1）CS1201

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1201
发送反馈意见

更新：2007 年 11 月

错误消息

“无效功能”这一功能已不推荐使用。请改用“有效功能”。

您所尝试使用的功能已不推荐使用。请更新代码以改用有效功能。


编译器警告（等级 1）CS1202

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1202
发送反馈意见

更新：2007 年 11 月

错误消息

“无效功能”这一功能已不推荐使用。请改用“有效功能”。

您所尝试使用的功能已不推荐使用。请更新代码以改用有效功能。


编译器警告（等级 1）CS1203

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1203
发送反馈意见

更新：2007 年 11 月

错误消息

功能“feature”被否决。请改用“feature”。

“无效功能”这一功能已不推荐使用。请改用“有效功能”。

您所尝试使用的功能已不推荐使用。请更新代码以改用有效功能。


编译器警告（等级 1）CS1522

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1522
发送反馈意见

更新：2007 年 11 月

错误消息

空的 switch 块

编译器检测到了没有 case 或 default 语句的 switch 块。 switch 块必须有一个或多个 case 或 default 语句。

下面的示例生成 CS1522：

复制代码
// CS1522.cs
// compile with: /W:1
using System;
class x
{
   public static void Main()
   {
      int i = 6;

      switch(i)   // CS1522
      {
         // add something to the switch block, for example:
         /*
         case (5):
            Console.WriteLine("5");
            return;
         default:
            Console.WriteLine("not 5");
            return;
         */
      }
   }
}


编译器警告（等级 1）CS1570

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1570
发送反馈意见

更新：2007 年 11 月

错误消息

“construct”上的 XML 注释出现 XML 格式错误 —“reason”

当使用 /doc 时，源代码中的任何注释都必须为 XML 格式。XML 标记若有任何错误，将导致生成 CS1570。例如：

  • 如果向 cref 传递字符串（如在 <exception> 标记中），字符串必须用双引号引起来。

  • 如果使用标记（如没有结束标记的 <seealso>），必须在右尖括号的前面指定正斜杠 (/)。

  • 如果需要在说明文本中使用大于号或小于号，需要用 &gt; 或 &lt; 表示它们。

  • <include> 标记上缺少文件或路径属性，或者它们的格式不正确。

下面的示例生成 CS1570：

复制代码
// CS1570.cs
// compile with: /W:1
namespace ns
{
   // the following line generates CS1570
   /// <summary> returns true if < 5 </summary>
   // try this instead
   // /// <summary> returns true if &lt;5 </summary>

   public class MyClass
   {
      public static void Main ()
      {
      }
   }
}


编译器警告（等级 1）CS1574

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1574
发送反馈意见

更新：2007 年 11 月

错误消息

“construct”上的 XML 注释中有语法错误的 cref 属性“name”

传递给 cref 标记的字符串（如在 <exception> 标记中）引用的成员在当前生成环境中不可用。传递给 cref 标记的字符串在语法上必须是成员或字段的正确名称。

有关更多信息，请参见建议的文档注释标记。

下面的示例生成 CS1574：

复制代码
// CS1574.cs
// compile with: /W:1 /doc:x.xml
using System;

/// <exception cref="System.Console.WriteLin">An exception class.</exception>   // CS1574
// instead, uncomment and try the following line
// /// <exception cref="System.Console.WriteLine">An exception class.</exception>
class EClass : Exception
{
}

class TestClass
{
   public static void Main()
   {
      try
      {
      }
      catch(EClass)
      {
      }
   }
}


编译器警告（等级 1）CS1580

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1580
发送反馈意见

更新：2007 年 11 月

错误消息

XML 注释 cref 属性中的参数“parameter number”的类型无效

当试图引用重载格式的方法时，编译器检测到语法错误。这通常表明所指定的是参数名而不是类型。格式不正确的行将出现在生成的 XML 文件中。

下面的示例生成 CS1580：

复制代码
// CS1580.cs
// compile with: /W:1 /doc:x.xml
using System;

/// <seealso cref="Test(i)"/>   // CS1580
// try the following line instead
// /// <seealso cref="Test(int)"/>
public class MyClass
{
   /// <summary>help text</summary>
   public static void Main()
   {
   }

   /// <summary>help text</summary>
   public void Test(int i)
   {
   }

   /// <summary>help text</summary>
   public void Test(char i)
   {
   }
}


编译器警告（等级 1）CS1581

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1581
示例 发送反馈意见

更新：2007 年 11 月

错误消息

XML 注释的 cref 属性中的返回类型无效

当试图引用方法时，编译器检测到由于返回类型无效而产生的错误。

示例

下面的示例生成 CS1581：

复制代码
// CS1581.cs
// compile with: /W:1 /doc:x.xml

/// <summary>help text</summary>
public class MyClass
{
    /// <summary>help text</summary>
    public static void Main()
    {
    }

    /// <summary>help text</summary>
    public static explicit operator int(MyClass f)
    {
        return 0;
    }
}

/// <seealso cref="MyClass.explicit operator intt(MyClass)"/>  // CS1581
// try the following line instead
// /// <seealso cref="MyClass.explicit operator int(MyClass)"/>
public class MyClass2
{
}


编译器警告（等级 1）CS1584

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1584
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“member”上的 XML 注释中有语法错误的 cref 属性“invalid_syntax”

传递给文档注释标记的参数之一有无效语法。有关更多信息，请参见 建议的文档注释标记（C# 编程指南）。

示例

下面的示例生成 CS1584。

复制代码
// CS1584.cs
// compile with: /W:1 /doc:CS1584.xml
/// <remarks>Test class</remarks>
public class Test
{
   /// <remarks>Called in <see cref="Test.Mai()n"/>.</remarks>   // CS1584
   // try the following line instead
   // /// <remarks>Called in <see cref="Test.Main()"/>.</remarks>
   public static void Test2() {}

   /// <remarks>Main method</remarks>
   public static void Main() {}
}


编译器警告（等级 1）CS1589

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1589
发送反馈意见

更新：2007 年 11 月

错误消息

无法包括文件“file”的 XML 片段“fragment”-- reason

由于指定的 reason，引用文件 (file) 的 <include> 标记的语法 (fragment) 不正确。

格式不正确的行将放置在生成的 XML 文件中。

下面的示例生成 CS1589：

复制代码
// CS1589.cs
// compile with: /W:1 /doc:CS1589_out.xml

/// <include file='CS1589.doc' path='MyDocs/MyMembers[@name="test"]/' />   // CS1589
// try the following line instead
// /// <include file='CS1589.doc' path='MyDocs/MyMembers[@name="test"]/*' />
class Test
{
   public static void Main()
   {
   }
}


编译器警告（等级 1）CS1590

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1590
发送反馈意见

更新：2007 年 11 月

错误消息

无效的 XML 包含元素 — 缺少文件属性

缺少传递给 <include> 标记的路径或文档属性，或者它们不完整。

下面的示例生成 CS1590：

复制代码
// CS1590.cs
// compile with: /W:1 /doc:x.xml

/// <include path='MyDocs/MyMembers[@name="test"]/*' />   // CS1590
// try the following line instead
// /// <include file='x.doc' path='MyDocs/MyMembers[@name="test"]/*' />
class Test
{
   public static void Main()
   {
   }
}


编译器警告（等级 1）CS1592

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1592
发送反馈意见

更新：2007 年 11 月

错误消息

所包含的注释文件中有格式错误的 XML —“reason”

在由 <include> 标记指定的文件中发现了报告为 reason 的问题。


编译器警告（等级 1）CS1598

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1598
发送反馈意见

更新：2007 年 11 月

错误消息

未能加载 XML 分析器，原因如下：“reason”。将不生成 XML 文档文件“file”。

指定了 /doc 选项，但编译器未能找到并加载 msxml3.dll。请确保 msxml3.dll 文件已安装并注册。


编译器警告（等级 1）CS1607

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1607
发送反馈意见

更新：2007 年 11 月

错误消息

程序集生成 — reason

在编译的程序集创建阶段生成了警告。

如果要在 32 位操作系统上生成一个 64 位应用程序，必须确保在目标操作系统上安装了所有引用的程序集的 64 位版本。

所有特定于 x86 的公共语言运行库 (CLR) 程序集都具有 64 位的对应项（每个 CLR 程序集都存在于所有操作系统上）。因此，对于 CLR 程序集可以放心地忽略 CS1607。

如果在创建 AssemblyInformationalVersionAttribute 时遇到此警告，则可忽略此警告。信息性版本是一个字符串，它将附加的版本信息附加到一个程序集；此信息不在运行时使用。虽然可以指定任意文本，但是如果字符串的格式不是程序集版本号使用的格式，或者虽然是这种格式但包含通配符，则在编译时会显示一条警告消息。此警告无碍。

有关更多信息，请参见 Al.exe 工具错误和警告。


编译器警告（等级 1）CS1616

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1616
发送反馈意见

更新：2007 年 11 月

错误消息

选项“option”重写源文件或附加模块中给出的属性“attribute”

如果源文件中的程序集属性 AssemblyKeyFile 或 AssemblyKeyName 与项目属性中指定的 /keyfile 或 /keycontainer 命令行选项或密钥文件名或密钥容器冲突，则会出现此警告。

在下面的示例中，假定您有一个名为 cs1616.snk 的密钥文件。该文件可以用命令行生成：

复制代码
sn –k CS1616.snk

下面的示例生成 CS1616：

复制代码
// CS1616.cs
// compile with: /keyfile:cs1616.snk
using System.Reflection;

// To fix the error, remove the next line
[assembly: AssemblyKeyFile("cs1616b.snk")]  // CS1616

class C
{
  public static void Main()
  {
  }
}


编译器警告（等级 1）CS1633

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1633
发送反馈意见

更新：2007 年 11 月

错误消息

无法识别的 #pragma 指令

所使用的杂注不是 C# 编译器支持的已知杂注。若要解决此错误，请仅使用受支持的杂注。

下面的示例生成 CS1633：

复制代码
// CS1633.cs
// compile with: /W:1
#pragma unknown  // CS1633

class C
{
   public static void Main()
   {
   }
}


编译器警告（等级 1）CS1634

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1634
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应为 disable 或 restore

如果 #pragma warning 子句的格式错误，例如省略了 disable 或 restore，则会出现此错误。有关更多信息，请参见 #pragma warning 主题。

示例

下面的示例生成 CS1634：

复制代码
// CS1634.cs
// compile with: /W:1

#pragma warning   // CS1634
// Try this instead:
// #pragma warning disable 0219

class MyClass
{
  public static void Main()
  {
  }
}


编译器警告（等级 1）CS1635

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1635
发送反馈意见

更新：2007 年 11 月

错误消息

警告“warning code”已被全局禁用，无法还原

如果您使用 /nowarn 命令行选项或项目设置禁用整个编译单元的警告，但您又使用 #pragma warning restore 试图还原该警告，则会发生此警告。若要解决此错误，请移除 /nowarn 命令行选项或项目设置，或者移除您通过命令行或项目设置禁用的任何警告的 #pragma warning restore。有关更多信息，请参见 #pragma warning 主题。

下面的示例生成 CS1635：

复制代码
// CS1635.cs
// compile with: /w:1 /nowarn:162

enum MyEnum {one=1,two=2,three=3};

class MyClass
{
    public static void Main()
    {
#pragma warning disable 162

    if (MyEnum.three == MyEnum.two)
        System.Console.WriteLine("Duplicate");

#pragma warning restore 162
    }
}


编译器警告（等级 1）CS1645

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1645
发送反馈意见

更新：2007 年 11 月

错误消息

功能“feature”不是标准化 ISO C# 语言规范的一部分，有些编译器可能不接受它

您正在使用的功能不是 ISO 标准的一部分。使用此功能的代码可能无法在其他编译器上进行编译。

复制代码
// CS1645.cs
// compile with: /W:1 /t:module /langversion:ISO-1
[assembly:System.CLSCompliant(false)]
// To supress the warning use the switch: /nowarn:1645
[module:System.CLSCompliant(false)]   // CS1645
class Test
{
}


编译器警告（等级 1）CS1658

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1658
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“warning text”。另请参见错误“error code”

编译器在用警告重写错误时会发出此警告。有关此问题的信息，请参考上面所提到的错误。若要从 Visual Studio IDE 内查找相应的错误，请使用索引。例如，如果上面的文本是“另请参见错误‘CS1037’”，则请在索引中查找 CS1037。

示例

下面的示例生成 CS1658。

复制代码
// CS1658.cs
// compile with: /doc:x.xml
// CS1584 expected
/// <summary>
/// </summary>
public class C
{
    /// <see cref="C.F(params object[])"/>  // CS1658
    public static void M()
    {
    }

    /// <summary>
    /// </summary>
    public void F(params object[] o)
    {
    }

    static void Main()
    {
    }
}


编译器警告（等级 1）CS1682

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1682
示例 发送反馈意见

更新：2007 年 11 月

错误消息

对类型“type”的引用声明它嵌套在“nested type”中，但是未能找到它

当您导入的引用与其他引用或者您编写的代码不一致时，会发生此错误。发生此错误通常是因为编写了在元数据中引用类的代码，而此后或是删除了该类或是修改了类的定义。

示例

复制代码
// CS1682.cs
// compile with: /target:library /keyfile:mykey.snk
public class A {
   public class N1 {}
}
复制代码
// CS1682_b.cs
// compile with: /target:library /reference:CS1682.dll
using System;
public class Ref {

   public static A A1() {
      return new A();
   }

   public static A.N1 N1() {
      return new A.N1();
   }
}
复制代码
// CS1682_c.cs
// compile with: /target:library /keyfile:mykey.snk /out:CS1682.dll
public class A {
   public void M1() {}
}

下面的示例生成 CS1682。

复制代码
// CS1682_d.cs
// compile with: /reference:CS1682.dll /reference:CS1682_b.dll /W:1
// CS1682 expected
class Tester {
   static void Main()
   {
      Ref.A1().M1();
   }
}


编译器警告（等级 1）CS1683

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1683
发送反馈意见

更新：2007 年 11 月

错误消息

对类型“Type Name”的引用声称在此程序集中定义了该类型，但源代码或任何添加的模块中并未定义该类型

当您导入的程序集反过来包含对您当前正在编译的程序集的引用，而正在编译的程序集不包含任何与该引用匹配的内容时，会出现此错误。导致此情况的一种操作是，编译您的程序集，它最初确实包含所导入的程序集引用的成员。然后更新您的程序集，不小心移除了导入的程序集所引用的成员。


编译器警告（等级 1）CS1684

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1684
示例 发送反馈意见

更新：2007 年 11 月

错误消息

对类型“Type Name”的引用声明它是在“Namespace”中定义的，但却没有找到它

当一个命名空间中的引用引用了一个类型，并声明该类型存在于另一个命名空间中，但该类型并不存在时，可能导致此错误。例如，mydll.dll 声明类型 A 存在于 yourdll.dll 中，但 yourdll.dll 中并没有这种类型。导致此错误的一种可能是，您使用的 yourdll.dll 版本太旧，尚未定义 A。

下面的示例生成 CS1684。

示例

复制代码
// CS1684_a.cs
// compile with: /target:library /keyfile:CS1684.key
public class A {
   public void Test() {}
}

public class C2 {}
复制代码
// CS1684_b.cs
// compile with: /target:library /r:cs1684_a.dll
// post-build command: del /f CS1684_a.dll
using System;
public class Ref
{
   public static A GetA() { return new A(); }
   public static C2 GetC() { return new C2(); }
}

现在，我们重新生成第一个程序集，并在重新编译中对类 C2 的定义不做任何定义。

复制代码
// CS1684_c.cs
// compile with: /target:library /keyfile:CS1684.key /out:CS1684_a.dll
public class A {
   public void Test() {}
}

此模块通过标识符 Ref 引用第二个模块。但是第二个模块包含对类 C2 的引用，由于上一步中的编译，该类已不存在，所以此模块的编译返回 CS1684 错误信息。

复制代码
// CS1684_d.cs
// compile with: /reference:cs1684_a.dll /reference:cs1684_b.dll
// CS1684 expected
class Tester
{
   public static void Main()
   {
      Ref.GetA().Test();
   }
}


编译器警告（等级 1）CS1685

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1685
发送反馈意见

更新：2007 年 11 月

错误消息

预定义类型“System.type name”是在全局别名的多个程序集中定义的；请使用“文件名”中的定义

当在两个程序集中都找到一个预定义系统类型（例如 System.int32）时，会出现此错误。如果您从两个不同的位置引用 mscorlib（例如尝试并列运行 1.0 和 1.1 版的 .NET Framework），会发生此错误。

编译器仅使用其中一个程序集中的定义。编译器只搜索全局别名，不搜索定义 /reference 的库。如果您指定了 /nostdlib，编译器将搜索 Object，并且以后所有预定义类型的搜索都将在包含 Object 的文件中进行。


编译器警告（等级 1）CS1687

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1687
发送反馈意见

更新：2007 年 11 月

错误消息

源文件已超过在 PDB 中可表示的 16,707,565 行的限制，调试信息将不正确

PDB 和调试器有一些关于文件大小的限制。如果源文件太大，调试器在超过该限制后将不能正常工作。用户应该要么不发出源文件的调试信息（一种可能的方式是使用 #line hidden），要么找到缩小源文件的办法（一种可能的方式是将源文件分割成多个文件）。他们可能需要使用 partial 关键字来分割大类。


编译器警告（等级 1）CS1690

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1690
发送反馈意见

更新：2007 年 11 月

错误消息

由于“member”是引用封送类的字段，访问上面的成员可能导致运行时异常

当您尝试对从 MarshalByRefObject 派生的类成员调用方法、属性或索引器时，并且该成员是值类型时，会出现此警告。若要解决此警告，请将该成员复制到局部变量并对此变量调用方法。

下面的示例生成 CS1690：

复制代码
// CS1690.cs
using System;

class WarningCS1690: MarshalByRefObject
{
   int i = 5;

   public static void Main()
   {
     WarningCS1690 e = new WarningCS1690();
     e.i.ToString();   // CS1690

     // OK
     int i = e.i;
     i.ToString();
     e.i = i;
   }
}


编译器警告（等级 1）CS1691

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1691
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“number”不是有效的警告编号

传递给 #pragma warning 预处理器指令的数字不是有效的警告编号。请验证该数字表示警告，而不是错误或其他字符序列。

示例

下面的示例生成 CS1691。

复制代码
// CS1691.cs
public class C
{
    int i = 1;
    public static void Main()
    {
        C myC = new C();
#pragma warning disable 151  // CS1691
// Try the following line instead:
// #pragma warning disable 1645
        myC.i++;
#pragma warning restore 151  // CS1691
// Try the following line instead:
// #pragma warning restore 1645
    }
}


编译器警告（等级 1）CS1692

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1692
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无效数字

许多预处理器指令（例如 #pragma 和 #line）使用数字作为参数。由于太大、格式错误或者包含非法字符等原因，这些数字中的一个无效。若要更正此错误，请更正数字。

示例

下面的示例生成 CS1692。

复制代码
// CS1692.cs

#pragma warning disable a  // CS1692
// Try this instad:
// #pragma warning disable 1691

class A
{
    static void Main()
    {
    }
}


编译器警告（等级 1）CS1694

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1694
示例 发送反馈意见

更新：2007 年 11 月

错误消息

为预处理器指令指定的文件名无效。文件名太长或者不是有效的文件名。

使用 #pragma checksum 预处理器指令时出现此警告。指定的文件名长度超过 256 个字符。若要解决此警告，请使用较短的文件名。

示例

下面的示例生成 CS1694。

复制代码
// cs1694.cs
#pragma checksum "MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890MyFile1234567890.txt" {00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F}   // CS1694
class MyClass {}


编译器警告（等级 1）CS1695

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1695
示例 发送反馈意见

更新：2007 年 11 月

错误消息

无效的 #pragma 校验和语法；应为 #pragma 校验和“filename” “{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}”“XXXX...”

您应该很少会遇到此错误，因为如果您是通过 Code Dom API 生成代码，校验和通常在运行时插入。

但是，如果您要在此 #pragma 语句中键入内容，并且错误地键入了 GUID 或校验和，您就会遇到此错误。编译器的语法检查并不验证您是否输入了正确的 GUID，但它会检查数字和分隔符的位数是否正确，以及这些数字是否为十六进制。同样，它还验证校验和是否包含偶数位数，以及这些数字是否为十六进制。

示例

下面的示例生成 CS1695。

复制代码
// CS1695.cs

#pragma checksum "12345"  // CS1695

public class Test
{
    static void Main()
    {
    }
}


编译器警告（等级 1）CS1696

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1696
示例 发送反馈意见

更新：2007 年 11 月

错误消息

应输入单行注释或行尾

编译器要求预处理器指令后面必须跟行尾结束符或单行注释。编译器已完成对有效的预处理器指令的处理，并且遇到了违反此语法约束的内容。

示例

下面的示例生成 CS1696。

复制代码
// CS1696.cs
class Test
{
   public static void Main()
   {
      #pragma warning disable 1030;219   // CS1696
      #pragma warning disable 1030   // OK
   }
}


编译器警告（等级 1）CS1697

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1697
发送反馈意见

更新：2007 年 11 月

错误消息

为“file name”提供了不同的校验和值

您为给定的文件指定了一个以上的校验和。当一个项目中有一个以上的同名文件时，调试器使用校验和值确定调试哪个文件。大多数用户不会遇到此错误，但是如果您正在编写生成代码的应用程序，您可能会偶然遇到此错误。若要解决此错误，请确保对任何给定的代码文件仅生成一次校验和。


编译器警告（等级 1）CS1699

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1699
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

使用命令行选项“compiler_option”或适当项目设置代替“attribute_name”

为给程序集签名，必须指定密钥文件。在 Microsoft Visual C# 2005 之前，您在源代码中使用 CLR 属性指定了密钥文件。现在，这些属性都被否决了。

从 Microsoft Visual C# 2005 开始，您应使用“项目设计器”的“签名页”或程序集链接器指定密钥文件。

“项目设计器”的“签名页”是首选方法；有关更多信息，请参见 “项目设计器”->“签名”页 和 管理程序集签名和清单签名。

如何：使用强名称为程序集签名 使用如下编译器选项：

  • /keyfile（指定强名称密钥文件）（C# 编译器选项） 代替 AssemblyKeyFileAttribute 属性。

  • /keycontainer（指定强名称密钥容器）（C# 编译器选项）代替 AssemblyKeyNameAttribute。

  • /delaysign（延迟为程序集签名）（C# 编译器选项） 代替 AssemblyDelaySignAttribute。

否决这些属性的原因是：

  • 存在安全问题，因为这些属性嵌入在由编译器生成的二进制文件中。因此，每个拥有您的二进制文件的人都会获得存储在其中的密钥。

  • 存在可用性问题，因为属性中指定的路径是相对于当前工作目录（此目录在集成开发环境 (IDE) 中可能会发生更改）或输出目录的。因此，大多数情况下密钥文件有可能是 ..\\..\\mykey.snk。这些属性还增加了项目系统为附属程序集正确签名的难度。当您使用编译器选项代替这些属性时，可以使用密钥的完全限定路径和文件名，而无需在输出文件中嵌入任何内容；项目系统和源代码管理系统可在项目移动时正确处理该完整路径；项目系统可保留密钥文件的项目相对路径，同时仍将完整路径传递给编译器；其他生成程序可通过直接将适当的路径传递给编译器（而不是用正确的属性生成源文件）更加容易地为输出签名。

  • 将属性用于友元程序集会影响编译器的效率。当您使用属性时，编译器在必须决定是否授予友元关系时不知道密钥是什么，因此它必须进行推测。在编译的最后，一旦编译器最后知道了密钥，它就能够对推测进行验证。当使用编译器选项指定密钥文件时，编译器可立即决定是否授予友元关系。

示例

下面的示例生成 CS1699。若要解决此错误，请移除该属性并使用 /delaysign 进行编译。

复制代码
// CS1699.cs
// compile with: /target:library
[assembly:System.Reflection.AssemblyDelaySign(true)]   // CS1699

请参见

任务

如何：使用强名称为程序集签名

参考

“项目设计器”->“签名”页

其他资源

管理程序集签名和清单签名


编译器警告（等级 1）CS1707

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1707
发送反馈意见

更新：2007 年 11 月

错误消息

根据新的语言规则，委托“DelegateName”已绑定到“MethodName1”而不是“MethodName2”

C# 2.0 实现了将委托绑定到方法的新规则。新规则考虑了其他一些以前未考虑的内容。此警告指示委托现在绑定到的方法重载不是它以前绑定到的方法重载。您可能需要验证委托是否确实应该绑定到“MethodName1”而不是“MethodName2”。

有关编译器如何确定将委托绑定到哪个方法的说明，请参见委托中的协变和逆变（C# 编程指南）。


编译器警告（等级 1）CS1709

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1709
示例 发送反馈意见

更新：2007 年 11 月

错误消息

为预处理器指令指定的文件名为空

您指定了一个包含文件名的预处理器指令，但该文件名是空的。若要解决此警告，请将所需的内容放入该文件。

示例

下面的示例生成 CS1709。

复制代码
// CS1709.cs
class Test
{
    static void Main()
    {
        #pragma checksum "" "{406EA660-64CF-4C82-B6F0-42D48172A799}" ""  // CS1709
    }
}


编译器警告（等级 1）CS1720

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1720
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于“generic type”的默认值为 null，因此表达式总会导致 System.NullReferenceException

如果您编写的表达式涉及泛型类型变量的默认值，而该泛型类型变量是引用类型（例如，类），则将发生此错误。请看下面的表达式：

复制代码
default(T).ToString()

由于 T 是引用类型，它的默认值为 null，因此试图对其应用 ToString 方法将引发 NullReferenceException。

示例

类型 T 上的类引用约束确保 T 是引用类型。

下面的示例生成 CS1720。

复制代码
// CS1720.cs
using System;
public class Tester
{
    public static void GenericClass<T>(T t1) where T : class
    {
        Console.WriteLine(default(T).ToString());  // CS1720
    }
    public static void Main() {}
}


编译器警告（等级 1）CS1723

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1723
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“param”上的 XML 注释具有引用类型参数的 cref 属性“attribute”

此错误由引用类型参数的 XML 注释生成。

示例

下面的示例生成 CS1723。

复制代码
// CS1723.cs
// compile with: /t:library /doc:filename.XML
///<summary>A generic list class.</summary>
///<see cref="T" />   // CS1723
// To resolve comment the previous line.
public class List<T>
{
}


编译器警告（等级 1）CS1911

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1911
示例 发送反馈意见

更新：2007 年 11 月

错误消息

从匿名方法、lambda 表达式、查询表达式或迭代器通过“base”关键字访问成员“name”会导致代码无法验证。请考虑将这种访问移入针对包含类型的辅助方法中。

使用 base 关键字在迭代器的方法体或匿名方法内调用虚函数将导致无法验证的代码。无法验证的代码在部分受信任的环境中将无法运行。

一个针对 CS1911 的解决方法是将虚函数调用移至 Helper 函数。

示例

下面的示例生成 CS1911。

复制代码
// CS1911.cs
// compile with: /W:1
using System;

delegate void D();
delegate D RetD();

class B {
   protected virtual void M() {
      Console.WriteLine("B.M");
   }
}

class Der : B {
   protected override void M() {
      Console.WriteLine("D.M");
   }

   void Test() { base.M(); }
   D Test2() { return new D(base.M); }

   public D CallBaseM() {
      return delegate () { base.M(); };   // CS1911

      // try the following line instead
      // return delegate () { Test(); };
   }

   public RetD DelToBaseM() {
      return delegate () { return new D(base.M); };   // CS1911

      // try the following line instead
      // return delegate () { return Test2(); };
   }
}

class Program {
   public static void Main() {
      Der der = new Der();
      D d = der.CallBaseM();
      d();
      RetD rd = der.DelToBaseM();
      rd()();
   }
}


编译器警告（等级 1）CS1956

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1956
示例 发送反馈意见

更新：2007 年 11 月

错误消息

成员“name”实现类型“type”中的接口成员“name”。在运行时有多个与该接口成员相匹配的项。将调用哪个方法取决于具体的实现。

如果两个接口方法的唯一区别在于：某个特定参数是 ref 还是 out，则将生成此警告。最好更改代码以避免此警告，因为在运行时实际将调用哪个方法并不十分明显，或者无法保证在运行时将实际调用此方法。

尽管 C# 可以区分 out 和 ref，CLR 却无法区分它们。决定实现接口的方法时，CLR 只是从中任选一个。

避免此警告

  • 为编译器提供某种方式来区分这两个方法。例如，可以为这两个方法提供不同的名称，或者为其中一个方法提供附加参数。

示例

下面的代码将生成 CS1956，因为 Base 中这两个 Test 方法的唯一区别在于它们的第一个参数上的 ref/out 修饰符：

复制代码
// cs1956.cs
class Base<T, S>
{
    // This is the method that should be called.
    public virtual int Test(out T x) // CS1956
    {
        x = default(T);
        return 0;
    }

    // This is the "last" method and is the one that ends up being called
    public virtual int Test(ref S x)
    {
        return 1;
    }
}

interface IFace
{
    int Test(out int x);
}

class Derived : Base<int, int>, IFace
{
    static int Main()
    {
        IFace x = new Derived();
        int y;
        return x.Test(out y);
    }
}


编译器警告（等级 1）CS1957

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS1957
示例 发送反馈意见

更新：2007 年 11 月

错误消息

成员“name”重写“method”。在运行时有多个重写候选项。将调用哪个方法取决于具体的实现。

有些方法参数的唯一区别在于是 ref 还是 out，对于这些参数，无法在运行时加以区分。

避免此警告

  • 为其中一个方法提供不同的名称或参数数目。

示例

下面的代码将生成 CS1957：

复制代码
// cs1957.cs
class Base<T, S>
{
    public virtual string Test(out T x) // CS1957
    {
        x = default(T);
        return "Base.Test";
    }
    public virtual void Test(ref S x) { }
}

class Derived : Base<int, int>
{
    public override string Test(out int x)
    {
        x = 0;
        return "Derived.Test";
    }

    static int Main()
    {
        int x;
        if (new Derived().Test(out x) == "Derived.Test")
            return 0;
        return 1;
    }
}


编译器警告（等级 1）CS2002

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS2002
发送反馈意见

更新：2007 年 11 月

错误消息

源文件“file”被指定多次

源文件名多次传递给了编译器。为生成输出文件，只能将文件指定给编译器一次。

/nowarn 选项无法禁止显示此警告。

下面的示例生成 CS2002：

复制代码
// CS2002.cs
// compile with: CS2002.cs
public class A
{
public static void Main(){}
}

若要生成该错误，请用以下命令行编译该示例：

复制代码
csc CS2002.cs CS2002.cs


编译器警告（等级 1）CS2014

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS2014
发送反馈意见

更新：2007 年 11 月

错误消息

编译器选项“old option”已过时，请使用“new option”

编译器选项的格式被否决。有关更多信息，请参见 C# 编译器选项。


编译器警告（等级 1）CS2023

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS2023
发送反馈意见

更新：2007 年 11 月

错误消息

因为 /noconfig 是在响应文件中指定的，所以忽略该选项

在响应文件中指定了 /noconfig 编译器选项，这是不允许的。

/nowarn 选项无法禁止显示此警告。


编译器警告（等级 1）CS2029

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS2029
发送反馈意见

更新：2007 年 11 月

错误消息

“/define”的值无效；“identifier”不是有效的标识符

如果 /define 选项中使用的值包含一些无效的字符，则将出现此警告。

/nowarn 选项无法禁止显示此警告。


编译器警告（等级 1）CS3000

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3000
发送反馈意见

更新：2007 年 11 月

错误消息

带有变量参数的方法不符合 CLS

此方法中使用的参数公开了不属于公共语言规范 (CLS) 的功能。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码。

下面的示例生成警告 CS3000。

复制代码
// CS3000.cs
// compile with: /target:library
// CS3000 expected
[assembly:System.CLSCompliant(true)]

public class Test
{
   public void AddABunchOfInts( __arglist ) {}   // CS3000
}


编译器警告（等级 1）CS3001

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3001
示例 发送反馈意见

更新：2007 年 11 月

错误消息

参数类型“type”不符合 CLS

public、protected 或 protectedinternal 方法必须接受类型符合公共语言规范 (CLS) 的参数。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3001：

复制代码
// CS3001.cs

[assembly:System.CLSCompliant(true)]
public class a
{
    public void bad(ushort i)   // CS3001
    {
    }

    private void OK(ushort i)   // OK, private method
    {
    }

    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3002

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3002
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“method”的返回类型不符合 CLS

public、protected 或 protectedinternal 方法返回的值的类型必须符合公共语言规范 (CLS)。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3002：

复制代码
// CS3002.cs

[assembly:System.CLSCompliant(true)]
public class a
{
    public ushort bad()   // CS3002, public method
    {
        ushort a;
        a = ushort.MaxValue;
        return a;
    }

    private ushort OK()   // OK, private method
    {
        ushort a;
        a = ushort.MaxValue;
        return a;
    }

    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3003

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3003
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“variable”的类型不符合 CLS

public、protected 或 protectedinternal 变量的类型必须符合公共语言规范 (CLS)。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3003：

复制代码
// CS3003.cs

[assembly:System.CLSCompliant(true)]
public class a
{
    public ushort a1;   // CS3003, public variable
    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3004

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3004
发送反馈意见

更新：2007 年 11 月

错误消息

混合的和分解的 Unicode 字符不符合 CLS

为了符合公共语言规范 (CLS)，在 public、protected 或 protectedinternal 标识符中只允许使用组合的 UNICODE 字符。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。


编译器警告（等级 1）CS3005

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3005
示例 发送反馈意见

更新：2007 年 11 月

错误消息

只有大小写不同的标识符“identifier”不符合 CLS

只有一个或多个字母的大小写与其他 public、protected 或 protectedinternal 标识符不同的 public、protected 或 protectedinternal 标识符不符合公共语言规范 (CLS)。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3003：

复制代码
// CS3005.cs

using System;

[assembly:CLSCompliant(true)]
public class a
{
    public static int a1 = 0;
    public static int A1 = 1;   // CS3005

    public static void Main()
    {
        Console.WriteLine(a1);
        Console.WriteLine(A1);
    }
}


编译器警告（等级 1）CS3006

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3006
示例 发送反馈意见

更新：2007 年 11 月

错误消息

只是 ref、out 或数组秩不同的重载方法“method”不符合 CLS

方法不能基于 ref 或 out 参数重载，但仍遵守公共语言规范 (CLS)。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3006： 若要解决此警告，请注释掉程序集级属性或移除其中一个方法定义。

复制代码
// CS3006.cs

using System;

[assembly: CLSCompliant(true)]
public class MyClass
{
    public void f(int i)
    {
    }

    public void f(ref int i)   // CS3006
    {
    }

    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3007

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3007
示例 发送反馈意见

更新：2007 年 11 月

错误消息

只是未命名数组类型不同的重载方法“method”不符合 CLS。

如果您有一个接受交错数组的重载方法，并且方法签名的唯一区别是数组的元素类型，则将发生此错误。为避免此错误，可考虑使用矩形数组而不是交错数组，使用一个额外的参数消除函数调用的歧义，重命名一个或多个重载方法，或者如果不需要 CLS 遵从性，则移除 CLSCompliantAttribute 属性。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3007：

复制代码
// CS3007.cs
[assembly: System.CLSCompliant(true)]
public struct S
{
    public void F(int[][] array) { }
    public void F(byte[][] array) { }  // CS3007
    // Try this instead:
    // public void F1(int[][] array) {}
    // public void F2(byte[][] array) {}
    // or
    // public void F(int[,] array) {}
    // public void F(byte[,] array) {}
}


编译器警告（等级 1）CS3008

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3008
示例 发送反馈意见

更新：2007 年 11 月

错误消息

只有大小写不同的标识符“identifier”不符合 CLS

如果 public、protected 或 protectedinternal 标识符以下划线字符 (_) 开头，它就违反了公共语言规范 (CLS)。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和 公共语言规范。

示例

下面的示例生成 CS3008：

复制代码
// CS3008.cs

using System;

[assembly:CLSCompliant(true)]
public class a
{
    public static int _a = 0;  // CS3008
    // OK, private
    // private static int _a1 = 0;

    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3009

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3009
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”：基类型“type”不符合 CLS

在标记为符合公共语言规范 (CLS) 的程序集中，基类型被标记为不必符合 CLS。请移除指定程序集符合 CLS 的属性，或移除指示类型不符合 CLS 的属性。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3009：

复制代码
// CS3009.cs

using System;

[assembly:CLSCompliant(true)]
[CLSCompliant(false)]
public class B
{
}

public class C : B   // CS3009
{
    public static void Main () {}
}


编译器警告（等级 1）CS3010

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3010
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“member”：符合 CLS 的接口必须只有符合 CLS 的成员

在用 [assembly:CLCSompliant(true)] 标记的程序集中，接口包含用 [CLCSompliant(false)] 标记的成员。移除其中一个符合公共语言规范 (CLS) 的属性。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3010：

复制代码
// CS3010.cs

using System;

[assembly:CLSCompliant(true)]
public interface I
{
    [CLSCompliant(false)]
    int M();   // CS3010
}

public class C : I
{
    public int M()
    {
        return 1;
    }

    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3011

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3011
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“member”: 只有符合 CLS 的成员才能是抽象的

类成员不能既 abstract，又不符合公共语言规范 (CLS)。 CLS 指定应实现所有的类成员。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3011：

复制代码
// CS3011.cs

using System;

[assembly:CLSCompliant(true)]
public abstract class I
{
    [CLSCompliant(false)]
    public abstract int M();   // CS3011

    // OK
    [CLSCompliant(false)]
    public void M2()
    {
    }
}

public class C : I
{
    public override int M()
    {
        return 1;
    }

    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3012

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3012
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能在模块上指定与程序集的 CLSCompliant 属性不同的 CLSCompliant 属性

为使模块可以通过 [module:System.CLCSompliant(true)] 符合公共语言规范 (CLS)，必须使用 /target:module 编译器选项生成它。有关 CLS 的更多信息，请参见 公共语言规范。

示例

当不使用 /target:module 生成时，下面的示例生成 CS3012：

复制代码
// CS3012.cs
// compile with: /W:1

[module:System.CLSCompliant(true)]   // CS3012
public class C
{
    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3013

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3013
发送反馈意见

更新：2007 年 11 月

错误消息

添加的模块必须用 CLSCompliant 属性标记才能与程序集匹配

用 /target:module 编译器选项编译的模块添加到了使用 /addmodule 的编译中。但是，模块的公共语言规范 (CLS) 遵从性与当前编译的 CLS 状态不一致。

CLS 遵从性由模块属性指示。例如，[module:CLSCompliant(true)] 指示模块符合 CLS，而 [module:CLSCompliant(false)] 指示模块不符合 CLS。默认值为 [module:CLSCompliant(false)]。有关 CLS 的更多信息，请参见 公共语言规范。


编译器警告（等级 1）CS3014

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3014
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于程序集没有 CLSCompliant 属性，因此“member”不需要 CLSCompliant 属性

在未指定公共语言规范 (CLS) 遵从性的源代码文件中，文件中的构造被标记为了符合 CLS。这是不允许的。若要解决此警告，请将程序集级符合 CLS 的属性添加到文件中（在以下示例中，取消对包含程序集级属性的代码行的注释）。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3014：

复制代码
// CS3014.cs

using System;

// [assembly:CLSCompliant(true)]
public class I
{
    [CLSCompliant(true)]   // CS3014
    public void M()
    {
    }

    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3015

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3015
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“method signature”没有只使用符合 CLS 类型的可访问的构造函数

为了符合公共语言规范 (CLS)，属性类的参数列表不能包含数组。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 C3015。

复制代码
// CS3015.cs
// compile with: /target:library
using System;

[assembly:CLSCompliant(true)]
public class MyAttribute : Attribute
{
   public MyAttribute(int[] ai) {}   // CS3015
   // try the following line instead
   // public MyAttribute(int ai) {}
}


编译器警告（等级 1）CS3016

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3016
示例 发送反馈意见

更新：2007 年 11 月

错误消息

作为属性参数的数组不符合 CLS

将数组传递给属性不符合公共语言规范 (CLS)。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3016：

复制代码
// CS3016.cs

using System;

[assembly : CLSCompliant(true)]
[C(new int[] {1, 2})]   // CS3016
// try the following line instead
// [C()]
class C : Attribute
{
    public C()
    {
    }

    public C(int[] a)
    {
    }

    public static void Main ()
    {
    }
}


编译器警告（等级 1）CS3017

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3017
示例 发送反馈意见

更新：2007 年 11 月

错误消息

不能在模块上指定与程序集的 CLSCompliant 属性不同的 CLSCompliant 属性

如果您的程序集 CLSCompliant 属性与模块的 CLSCompliant 属性冲突，则会出现此警告。与 CLS 兼容的程序集不能包含与 CLS 不兼容的模块。若要解决此警告，请确保程序集和模块的 CLSCompliant 属性全都为 true 或者全都为 false，或者移除其中一个属性。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3017：

复制代码
// CS3017.cs
// compile with: /target:module

using System;

[module: CLSCompliant(true)]
[assembly: CLSCompliant(false)]  // CS3017
// Try this line instead:
// [assembly: CLSCompliant(true)]
class C
{
    static void Main() {}
}


编译器警告（等级 1）CS3018

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3018
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”是不符合 CLS 的类型“type”的成员，因此不能将其标记为符合 CLS

如果将 CLSCompliant 属性设置为 true 的某一嵌套类声明为通过将 CLSCompliant 属性设置为 false 来声明的类的成员，将发生此警告。这是不允许的，因为如果嵌套类是不符合 CLS 的某一外部类的成员，它将无法符合 CLS。要解决此警告，请从嵌套类中移除 CLSCompliant 属性，或将其从 true 更改为 false。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码和公共语言规范。

示例

下面的示例生成 CS3018。

复制代码
// CS3018.cs
// compile with: /target:library
using System;

[assembly: CLSCompliant(true)]
[CLSCompliant(false)]
public class Outer
{
   [CLSCompliant(true)]   // CS3018
   public class Nested {}

   // OK
   public class Nested2 {}

   [CLSCompliant(false)]
   public class Nested3 {}
}


编译器警告（等级 1）CS3022

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3022
示例 发送反馈意见

更新：2007 年 11 月

错误消息

CLSCompliant 属性在应用于参数时无意义。请尝试将该属性应用于方法。

由于 CLS 遵从性规则适用于方法和类型声明，因此不检查方法参数是否符合 CLS 遵从性。

示例

下面的示例生成 CS3022：

复制代码
// CS3022.cs
// compile with: /W:1

using System;

[assembly: CLSCompliant(true)]
[CLSCompliant(true)]
public class C
{
    public void F([CLSCompliant(true)] int i)
    {
    }

    public static void Main()
    {
    }
}


编译器警告（等级 1）CS3023

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3023
示例 发送反馈意见

更新：2007 年 11 月

错误消息

CLSCompliant 属性在应用于返回类型时没有意义。 请尝试将该属性应用于方法。

由于 CLS 遵从性规则应用于方法和类型声明，因此不检查函数返回类型是否符合 CLS 遵从性。

示例

下面的示例生成警告 CS3023：

复制代码
// C3023.cs

[assembly:System.CLSCompliant(true)]
public class Test
{
    [return:System.CLSCompliant(true)]  // CS3023
    // Try this instead:
    // [method:System.CLSCompliant(true)]
    public static int Main()
    {
        return 0;
    }
}


编译器警告（等级 1）CS3026

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3026
示例 发送反馈意见

更新：2007 年 11 月

错误消息

符合 CLS 的字段“field”不能是可变字段

可变变量不应符合 CLS。

示例

下面的示例生成 CS3026。

复制代码
// CS3026.cs
[assembly:System.CLSCompliant(true)]
public class Test
{
public volatile int v0 =0;   // CS3026
      // To resolve remove the CLS-CComplient attribute.
public static void Main() { }
}


编译器警告（等级 1）CS3027

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS3027
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type_1”不符合 CLS，因为基接口“type_2”不符合 CLS

只有符合 CLS 的类型才能作为符合 CLS 的类型的基类型。

示例

下面的示例包含一个接口，此接口具有一个方法，可在其签名中使用不符合 CLS 的类型，从而使其类型不符合 CLS。

复制代码
// CS3027.cs
// compile with: /target:library
public interface IBase
{
   void IMethod(uint i);
}

下面的示例生成 CS3027。

复制代码
// CS3027_b.cs
// compile with: /reference:CS3027.dll /target:library /W:1
[assembly:System.CLSCompliant(true)]
public interface IDerived : IBase {}


编译器警告（等级 1）CS5000

[image]

Visual C# 参考：错误和警告
编译器警告（等级 1）CS5000
发送反馈意见

更新：2007 年 11 月

错误消息

未知的编译器选项“/option”

指定了无效的编译器选项。


编译器警告（等级 2）CS0108

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0108
发送反馈意见

更新：2007 年 11 月

错误消息

“member1”会隐藏继承的成员“member2”。如果打算隐藏，请使用 new 关键字。

声明变量所使用的名称与基类中的变量相同。但没有使用 new 关键字。此警告通知您应使用 new；声明变量时将按已在声明中使用了 new 来处理。

下面的示例生成 CS0108：

复制代码
// CS0108.cs
// compile with: /W:2
using System;

namespace x
{
   public class clx
   {
      public int i = 1;
   }

   public class cly : clx
   {
      public static int i = 2;   // CS0108, use the new keyword
      // the compiler parses the previous line as if you had specified:
      // public static new int i = 2;

      public static void Main()
      {
         Console.WriteLine(i);
      }
   }
}


编译器警告（等级 2）CS0114

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0114
发送反馈意见

更新：2007 年 11 月

错误消息

“function1”将隐藏继承的成员“function2”。若要用当前方法重写该实现，请添加 override 关键字。否则，添加关键字 new。

类中的声明与基类中的声明冲突，以致基类成员将被隐藏。

有关更多信息，请参见 base。

下面的示例生成 CS0114：

复制代码
// CS0114.cs
// compile with: /W:2 /warnaserror
abstract public class clx
{
   public abstract void f();
}

public class cly : clx
{
   public void f() // CS0114, hides base class member
   // try the following line instead
   // override public void f()
   {
   }

   public static void Main()
   {
   }
}


编译器警告（等级 2）CS0162

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0162
发送反馈意见

更新：2007 年 11 月

错误消息

检测到无法访问的代码

编译器检测到永远不执行的代码。

下面的示例生成 CS0162：

复制代码
// CS0162.cs
// compile with: /W:2
public class A
{
   public static void Main()
   {
      goto lab1;
      {
         // The following statements cannot be reached:
         int i = 9;   // CS0162
         i++;
      }
      lab1:
      {
      }
   }
}


编译器警告（等级 2）CS0164

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0164
发送反馈意见

更新：2007 年 11 月

错误消息

这个标签尚未被引用

声明了标签，但从未使用过。

下面的示例生成 CS0164：

复制代码
// CS0164.cs
// compile with: /W:2
public class a
{
   public int i = 0;

   public static void Main()
   {
      int i = 0;   // CS0164
      l1: i++;
      // the following lines resolve this error
      // if(i < 10)
      //    goto l1;
   }
}


编译器警告（等级 2）CS0251

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0251
发送反馈意见

更新：2007 年 11 月

错误消息

用负索引对数组进行索引(数组索引总是从零开始)

不要在数组中使用负数进行索引。

下面的示例生成 CS0251：

复制代码
// CS0251.cs
// compile with: /W:2
class MyClass
{
   public static void Main()
   {
      int[] myarray = new int[] {1,2,3};
      try
      {
         myarray[-1]++;   // CS0251
         // try the following line instead
         // myarray[1]++;
      }
      catch (System.IndexOutOfRangeException e)
      {
         System.Console.WriteLine("{0}", e);
      }
   }
}


编译器警告（等级 2）CS0252

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0252
发送反馈意见

更新：2007 年 11 月

错误消息

可能非有意的引用比较；若要获取值比较，请将左边强制转换为类型“type”

编译器正在进行引用比较。如果要比较字符串的值，请将表达式的左边转换为 type。

下面的示例生成 CS0252：

复制代码
// CS0252.cs
// compile with: /W:2
using System;

class MyClass
{
   public static void Main()
   {
      string s = "11";
      object o = s + s;

      bool b = o == s;   // CS0252
      // try the following line instead
      // bool b = (string)o == s;
   }
}


编译器警告（等级 2）CS0253

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0253
发送反馈意见

更新：2007 年 11 月

错误消息

可能非有意的引用比较；若要获取值比较，请将右边的值强制转换为类型“type”

编译器正在进行引用比较。如果要比较字符串的值，请将表达式的右边转换为 type。

下面的示例生成 CS0253：

复制代码
// CS0253.cs
// compile with: /W:2
using System;
class MyClass
{
   public static void Main()
   {
      string s = "11";
      object o = s + s;

      bool c = s == o;   // CS0253
      // try the following line instead
      // bool c = s == (string)o;
   }
}


编译器警告（等级 2）CS0278

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0278
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”不实现“pattern name”模式。“method name”与“method name”混淆。

在 C# 中有若干语句依赖定义的模式，如 foreach 和 using。例如，foreach 依赖于实现“enumerable”模式的集合类。

当编译器由于多义性无法进行匹配时，会发生 CS0278。例如，“enumerable”模式要求有一个名为 MoveNext 的方法，而您的代码可能包含两个名为 MoveNext 的方法。编译器将尝试找到一个接口来使用，但建议您找出导致二义性的原因并加以解决。

有关更多信息，请参见 如何：使用 foreach 访问集合类（C# 编程指南）。

示例

下面的示例生成 CS0278。

复制代码
// CS0278.cs
using System.Collections.Generic;
public class myTest
{
   public static void TestForeach<W>(W w)
      where W: IEnumerable<int>, IEnumerable<string>
   {
      foreach (int i in w) {}   // CS0278
   }
}


编译器警告（等级 2）CS0279

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0279
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type name”不实现“pattern name”模式。“method name”是静态的或不是公共的。

在 C# 中有几个依赖于已定义模式的语句，例如 foreach 和 using。例如，foreach 依赖于实现枚举模式的集合类。当由于一个方法被声明为 static 或者不是 public 而导致编译器无法完成匹配时，将出现此错误。模式中的方法必须是类的实例，并且必须是公共的。

示例

下面的示例生成 CS0279：

复制代码
// CS0279.cs

using System;
using System.Collections;

public class myTest : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator()
    {
        yield return 0;
    }

    internal IEnumerator GetEnumerator()
    {
        yield return 0;
    }

    public static void Main()
    {
        foreach (int i in new myTest()) {}  // CS0279
    }
}


编译器警告（等级 2）CS0280

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0280
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”不实现“pattern name”模式。“method name”有签名错误。

C# 中的两个语句 foreach 和 using 分别依赖预定义模式“collection”和“resource”。当由于方法的签名不正确，而使编译器无法将这些语句中的一个与它的模式相匹配时，将出现此警告。例如，“collection”模式要求有一个名为 MoveNext 的方法，该方法没有参数并且返回 boolean。您的代码可以包含一个有参数或者可能返回对象的 MoveNext 方法。

“resource”模式和 using 提供另一个示例。“resource”模式需要 Dispose 方法；如果您以同样的名称定义属性，您将得到此警告。

要解决此警告，请确保类型中的方法签名与模式中相应方法的签名相匹配，并确保您没有与模式所需的方法具有相同名称的属性。

示例

下面的示例生成 CS0280。

复制代码
// CS0280.cs
using System;
using System.Collections;

public class ValidBase: IEnumerable
{
   IEnumerator IEnumerable.GetEnumerator()
   {
      yield return 0;
   }

   internal IEnumerator GetEnumerator()
   {
      yield return 0;
   }
}

class Derived : ValidBase
{
   // field, not method
   new public int GetEnumerator;
}

public class Test
{
   public static void Main()
   {
      foreach (int i in new Derived()) {}   // CS0280
   }
}


编译器警告（等级 2）CS0435

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0435
发送反馈意见

更新：2007 年 11 月

错误消息

“assembly”中的命名空间“namespace”与“assembly”中的导入类型“type”冲突。请使用“assembly”中定义的命名空间。

当源文件 (file_2) 中的命名空间与 file_1 中的导入类型冲突时会发出此警告。编译器使用源文件中的命名空间。

下面的示例生成 CS0435：

首先编译此文件：

复制代码
// CS0435_1.cs
// compile with: /t:library
public class Util
{
   public class A { }
}

然后，编译此文件：

复制代码
// CS0435_2.cs
// compile with: /r:CS0435_1.dll

using System;

namespace Util
{
   public class A { }
}

public class Test
{
   public static void Main()
   {
      Console.WriteLine(typeof(Util.A)); // CS0435
   }
}


编译器警告（等级 2）CS0436

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0436
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“assembly”中的类型“type”与“assembly”中的导入类型“type2”冲突。请使用“assembly”中定义的类型。

当源文件 (file_2) 中的类型与 file _1 中的导入类型冲突时会发出此警告。编译器使用源文件中的类型。

示例

复制代码
// CS0436_a.cs
// compile with: /target:library
public class A {
   public void Test() {
      System.Console.WriteLine("CS0436_a");
   }
}

下面的示例生成 CS0436。

复制代码
// CS0436_b.cs
// compile with: /reference:CS0436_a.dll
// CS0436 expected
public class A {
   public void Test() {
      System.Console.WriteLine("CS0436_b");
   }
}

public class Test
{
   public static void Main()
   {
      A x = new A();
      x.Test();
   }
}


编译器警告（等级 2）CS0437

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0437
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“assembly2”中的类型“type”与“fassembly1”中的导入命名空间“namespace”冲突。请使用“assembly”中定义的类型。

当源文件 file_2 中的类型与 file _1 中的导入命名空间冲突时发出此警告。编译器使用源文件中的类型。

示例

复制代码
// CS0437_a.cs
// compile with: /target:library
namespace Util
{
   public class A {
      public void Test() {
         System.Console.WriteLine("CS0437_a.cs");
      }
   }
}

下面的示例生成 CS0437。

复制代码
// CS0437_b.cs
// compile with: /reference:CS0437_a.dll /W:2
// CS0437 expected
class Util
{
   public class A {
      public void Test() {
         System.Console.WriteLine("CS0437_b.cs");
      }
   }
}

public class Test
{
   public static void Main()
   {
      Util.A x = new Util.A();
      x.Test();
   }
}


编译器警告（等级 2）CS0440

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0440
示例 发送反馈意见

更新：2007 年 11 月

错误消息

由于“global::”始终引用全局命名空间而非别名，所以不建议定义名为“global”的别名

当您定义名为 global 的别名时会发出此警告。

示例

下面的示例生成 CS0440：

复制代码
// CS0440.cs
// Compile with: /W:1

using global = MyClass;   // CS0440
class MyClass
{
    static void Main()
    {
        // Note how global refers to the global namespace
        // even though it is redefined above.
        global::System.Console.WriteLine();
    }
}


编译器警告（等级 2）CS0444

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0444
发送反馈意见

更新：2007 年 11 月

错误消息

在“System namespace 1”中未找到预定义类型“type name 1”，却在“System namespace 2”中找到了

在编译器预期的位置未找到诸如 Int32 这样的预定义对象，而是在“System namespace 2”中找到了这类对象。

此错误可能表明 .NET Framework 没有正确安装。若要修复此错误，请重新安装 .NET Framework。

如果您正在编写您自己的基类库，可能也会遇到此错误。这种情况下，如果要解决此错误，请重新生成 mscorlib。


编译器警告（等级 2）CS0458

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0458
示例 发送反馈意见

更新：2007 年 11 月

错误消息

表示式的结果始终是“type name”类型的“null”

此警告由始终导致 null 的 nullable 表达式所致。

下面的代码生成警告 CS0458。

示例

此示例阐释了一些因 nullable 类型导致此错误的不同运算。

复制代码
// CS0458.cs
using System;
public  class Test
{
    public static void Main()
    {
        int a = 5;
        int? b = a + null;    // CS0458
        int? qa = 15;
        b = qa + null;        // CS0458
        b -= null;            // CS0458
        int? qa2 = null;
        b = qa2 + null;       // CS0458
        qa2 -= null;          // CS0458
    }
}


编译器警告（等级 2）CS0464

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0464
示例 发送反馈意见

更新：2007 年 11 月

错误消息

与类型“type”的 null 比较总是产生“false”

当您在可为 null 值的变量与 null 之间进行比较时，如果比较不是 == 或 !=，将出现此警告。若要解决此错误，请验证您是否确实要检查值是否为 null。类似 i == null 这样的比较结果既可以为 true，也可以为 false。类似 i > null 这样的比较结果始终为 false。

示例

下面的示例生成 CS0464。

复制代码
// CS0464.cs
class MyClass
{
   public static void Main()
   {
      int? i = 0;
      if (i < null) ;   // CS0464

      i++;
   }
}


编译器警告（等级 2）CS0467

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0467
示例 发送反馈意见

更新：2007 年 11 月

错误消息

方法“method”和非方法“non-method”之间存在多义性。正在使用方法组。

如果继承的成员具有相同的签名，但来自不同的接口，则会导致多义性错误。

示例

下面的示例生成 CS0467。

复制代码
// CS0467.cs
interface IList
{
int Count { get; set; }
}
interface ICounter
{
void Count(int i);
}

interface IListCounter : IList, ICounter {}

class Driver
{
    void Test(IListCounter x)
    {
        x.Count = 1;
        x.Count(1);   // CS0467
        // To resolve change the method name "Count" to another name.
    }

    static void Main()
    {
    }
}


编译器警告（等级 2）CS0469

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0469
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“goto case”值不可隐式转换为类型“type”

使用 goto case 时，必须将 goto case 的值隐式转换为 switch 的类型。

示例

下面的示例生成 CS0469。

复制代码
// CS0469.cs
// compile with: /W:2
class Test
{
   static void Main()
   {
      char c = (char)180;
      switch (c)
      {
         case (char)127:
            break;

         case (char)180:
            goto case 127;   // CS0469
            // try the following line instead
            // goto case (char) 127;
      }
   }
}


编译器警告（等级 2）CS0472

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0472
示例 发送反馈意见

更新：2007 年 11 月

错误消息

表达式的结果始终是“value1”，原因是类型“value2”的值永远不会等于类型为“value3”的“null”

如果使用具有恒定 null 值的运算符，编译器会发出警告。

示例

下面的示例生成 CS0472。

复制代码
public class Test
{
    public static int Main()
    {
        int i = 5;
        int counter = 0;

        // Comparison:
        if (i == null)  // CS0472
        // To resolve, use a valid value for i.
            counter++;
        return counter;
    }
}


编译器警告（等级 2）CS0618

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0618
发送反馈意见

更新：2007 年 11 月

错误消息

“member”已过时：“text”

类成员是用 Obsolete 属性标记的，以致在引用该类成员时会发出警告。

下面的示例生成 CS0618：

复制代码
// CS0618.cs
// compile with: /W:2
using System;

public class C
{
   [Obsolete("Use newMethod instead", false)]   // warn if referenced
   public static void m2()
   {
   }

   public static void newMethod()
   {
   }
}

class MyClass
{
   public static void Main()
   {
      C.m2();  // CS0618
   }
}


编译器警告（等级 2）CS0652

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0652
发送反馈意见

更新：2007 年 11 月

错误消息

与整数常数比较无意义；该常数不在“type”类型的范围之内

编译器检测到在常数和变量之间的比较中，常数超出了变量的范围。

下面的示例生成 CS0652：

复制代码
// CS0652.cs
// compile with: /W:2
public class Class1
{
   private static byte i = 0;
   public static void Main()
   {
      short j = 256;
      if (i == 256)   // CS0652, 256 is out of range for byte
         i = 0;
   }
}


编译器警告（等级 2）CS0728

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS0728
示例 发送反馈意见

更新：2007 年 11 月

错误消息

对局部变量“variable”的赋值可能不正确，该变量是 using 或 lock 语句的参数。 Dispose 调用或解锁将发生在该局部变量的原始值上。

在有些情况中，using 或 lock 块将导致临时资源泄漏。下面是一个示例：

thisType f = null;

using (f)

{

f = new thisType();

...

}

在这种情况下，在 using 块的执行结束后将会释放变量 thisType 的原始值（如 null），但不会释放在该块内创建的 thisType 对象，尽管它最终会被回收。

要解决此错误，请使用以下窗体：

using (thisType f = new thisType())

{

...

}

在这种情况中，将释放新分配的 thisType 对象。

示例

以下代码将生成警告 CS0728。

复制代码
// CS0728.cs

using System;
public class ValidBase : IDisposable
{
    public void Dispose() {  }
}

public class Logger
{
    public static void dummy()
    {
        ValidBase vb = null;
        using (vb)
        {
            vb = null;  // CS0728
        }
        vb = null;
    }
    public static void Main() { }
}


编译器警告（等级 2）CS1571

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1571
发送反馈意见

更新：2007 年 11 月

错误消息

“construct”上的 XML 注释中对“parameter”有重复的 param 标记

当使用 /doc 编译器选项时，找到了同一方法参数的多个注释。移除其中的一个重复行。

下面的示例生成 CS1571：

复制代码
// CS1571.cs
// compile with: /W:2 /doc:x.xml

/// <summary>help text</summary>
public class MyClass
{
   /// <param name='Int1'>Used to indicate status.</param>
   /// <param name='Char1'>An initial.</param>
   /// <param name='Int1'>Used to indicate status.</param> // CS1571
   public static void MyMethod(int Int1, char Char1)
   {
   }

   /// <summary>help text</summary>
   public static void Main ()
   {
   }
}


编译器警告（等级 2）CS1572

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1572
发送反馈意见

更新：2007 年 11 月

错误消息

“construct”上的 XML 注释有“parameter”的 param 标记，但是没有该名称的参数

当使用 /doc 编译器选项时，为某个参数指定了注释，但方法没有这样的参数。更改传递给名称属性的值或移除其中的一个注释行。

下面的示例生成 CS1572：

复制代码
// CS1572.cs
// compile with: /W:2 /doc:x.xml

/// <summary>help text</summary>
public class MyClass
{
   /// <param name='Int1'>Used to indicate status.</param>
   /// <param name='Char1'>Used to indicate status.</param>
   /// <param name='Char2'>???</param> // CS1572
   public static void MyMethod(int Int1, char Char1)
   {
   }

   /// <summary>help text</summary>
   public static void Main ()
   {
   }
}


编译器警告（等级 2）CS1587

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1587
示例 发送反馈意见

更新：2007 年 11 月

错误消息

XML 注释没有放在有效语言元素上

并非所有语言元素上都能使用建议的文档注释标记。例如，命名空间中不允许使用标记。有关 XML 注释的更多信息，请参见 建议的文档注释标记（C# 编程指南）。

示例

下面的示例生成 CS1587：

复制代码
// CS1587.cs
// compile with: /W:2 /doc:x.xml

/// <summary>test</summary>   // CS1587, tag not allowed on namespace
namespace MySpace
{
    class MyClass
    {
        public static void Main()
        {
        }
    }
}


编译器警告（等级 2）CS1668

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1668
发送反馈意见

更新：2007 年 11 月

错误消息

“path string”中指定的搜索路径“path”无效 --“system error message”

命令行中提供的 /lib 路径无效，或者 LIB 环境变量中的路径无效。检查所使用的路径，确认它是否存在并且可访问。引号中的错误消息是从操作系统返回的错误。

此错误的一个已知原因是，从同时安装了 Visual Studio 2005 的计算机上卸载 Visual Studio .NET 2002 或 Visual Studio .NET 2003。请执行下面的步骤修复此错误：

  1. 右击“我的电脑”，再单击“属性”。

  2. 单击“高级”选项卡，再单击“环境变量”。

  3. 单击顶部窗格中的 LIB 条目，确保其指向 v2.0 lib 文件。

    如果已将 Visual Studio 2005 安装到默认位置，则其完整路径为 C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Lib。


编译器警告（等级 2）CS1698

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1698
示例 发送反馈意见

更新：2007 年 11 月

错误消息

循环程序集引用“AssemblyName1”与输出程序集名称“AssemblyName2”不匹配。请尝试添加对“AssemblyName1”的引用，或者更改输出程序集的名称以使其匹配。

程序集引用不正确时将发生 CS1698。当引用的程序重新编译时会发生这种情况。若要解决此问题，请不要替换一个其自身就是所引用的程序集的依赖项的程序集。

示例

复制代码
// CS1698_a.cs
// compile with: /target:library /keyfile:mykey.snk
[assembly:System.Reflection.AssemblyVersion("2")]
public class CS1698_a {}
复制代码
// CS1698_b.cs
// compile with: /target:library /reference:CS1698_a.dll /keyfile:mykey.snk
public class CS1698_b : CS1698_a {}

下面的示例生成 CS1698。

复制代码
// CS1698_c.cs
// compile with: /target:library /out:cs1698_a.dll /reference:cs1698_b.dll /keyfile:mykey.snk
// CS1698 expected
[assembly:System.Reflection.AssemblyVersion("3")]
public class CS1698_c : CS1698_b {}
public class CS1698_a {}


编译器警告（等级 2）CS1701

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1701
示例 发送反馈意见

更新：2007 年 11 月

错误消息

假定程序集引用“Assembly Name #1”与“Assembly Name #2”匹配，您可能需要提供运行时策略

这两个程序集在发行号和/或版本号上有差异。为保证一致，必须在应用程序的 .config 文件中指定指令，并提供程序集的正确强名称，如以下代码示例所示。

示例

下面的多文件示例使用两个不同的外部别名引用一个程序集。第一个示例生成用于创建程序集 CS1701_d 的代码的较早版本。

复制代码
// CS1701_a.cs
// compile with: /target:library /out:cs1701_d.dll /keyfile:mykey.snk
using System.Reflection;
[assembly:AssemblyVersion("1.0")]
public class A {
   public void M1() {}
}

public class C1 {}

这是创建较新版本的程序集 CS1701_d 的代码。注意，由于这两个版本的输出文件同名，需要将较新版本编译到不同于较早版本的目录中。

复制代码
// CS1701_b.cs
// compile with: /target:library /out:c:\\cs1701_d.dll /keyfile:mykey.snk
using System.Reflection;
[assembly:AssemblyVersion("2.0")]
public class A {
   public void M2() {}
   public void M1() {}
}

public class C2 {}
public class C1 {}

此示例设置外部别名 A1 和 A2。

复制代码
// CS1701_c.cs
// compile with: /target:library /reference:A2=c:\\cs1701_d.dll /reference:A1=cs1701_d.dll

extern alias A1;
extern alias A2;
// using System;
using a1 = A1::A;
using a2 = A2::A;

public class Ref {
   public static a1 A1() { return new a1(); }
   public static a2 A2() { return new a2(); }

   public static A1::C1 M1() { return new A1::C1(); }
   public static A2::C2 M2() { return new A2::C2(); }
}

此示例使用 A 的两个不同别名调用方法。下面的示例生成 CS1701。

复制代码
// CS1701_d.cs
// compile with: /reference:c:\\CS1701_d.dll /reference:CS1701_c.dll
// CS1701 expected
class Tester {
   public static void Main() {
      Ref.A1().M1();
      Ref.A2().M2();
   }
}


编译器警告（等级 2）CS1710

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1710
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”上的 XML 注释中有“parameter”的重复 typeparam 标记

泛型类型的文档中包括此类型参数的重复标记。

示例

下面的代码将导致出现警告 CS1710。

复制代码
// CS1710.cs
// compile with: /doc:cs1710.xml
// To resolve this warning, delete one of the duplicate <typeparam>'s.
using System;
class Stack<ItemType>
{
}

/// <typeparam name="MyType">can be an int</typeparam>
/// <typeparam name="MyType">can be an int</typeparam>
class MyStackWrapper<MyType>
{
    // Open constructed type Stack<MyType>.
    Stack<MyType> stack;
    public MyStackWrapper(Stack<MyType> s)
    {
        stack = s;
    }
}

class CMain
{
    public static void Main()
    {
        // Closed constructed type Stack<int>.
        Stack<int> stackInt = new Stack<int>();
        MyStackWrapper<int> MyStackWrapperInt =
            new MyStackWrapper<int>(stackInt);
    }
}


编译器警告（等级 2）CS1711

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1711
示例 发送反馈意见

更新：2007 年 11 月

错误消息

“type”上的 XML 注释中有“parameter”的 typeparam 标记，但是不存在具有该名称的类型参数

泛型类型的文档中包含名称错误的类型参数的标记。

示例

下面的代码生成警告 CS1711。

复制代码
// cs1711.cs
// compile with: /doc:cs1711.xml
// CS1711 expected
using System;
///<typeparam name="WrongName">can be an int</typeparam>
class CMain
{
    public static void Main() { }
}


编译器警告（等级 2）CS1927

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS1927
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

对于模块忽略 /win32manifest，因为它仅应用于程序集。

win32 清单仅应用于程序集级。模板将会进行编译，但不具有清单。

更正此错误

  1. 移除 /win32manifest option。

  2. 将代码编译为程序集。

示例

下面的示例在同时用 /target:module 和 /win32manifest 编译器选项进行编译时生成 CS1927。

复制代码
// cs1927.cs
// Compile with: /target:module /win32manifest
using System;

class ManifestWithModule
{
    static int Main()
    {
        return 1;
    }
}

请参见

参考

/win32manifest（导入自定义 Win32 清单文件）（C# 编译器选项）
/target:module（创建模块以添加到程序集）（C# 编译器选项）


编译器警告（等级 2）CS3019

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS3019
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

CLS 遵从性检查不会在“type”上执行，因为它在此程序集的外部不可见。

当一个具有 CLSCompliantAttribute 属性的类型或成员从另一个程序集中不可见时，将出现此警告。若要解决此错误，请移除从其他程序集中不可见的所有类或成员的属性，或者使类型或成员可见。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码。

示例

下面的示例生成 CS3019：

复制代码
// CS3019.cs
// compile with: /W:2

using System;

[assembly: CLSCompliant(true)]

// To fix the error, remove the next line
[CLSCompliant(true)]  // CS3019
class C
{
    [CLSCompliant(false)]  // CS3019
    void Foo()
    {
    }

    static void Main()
    {
    }
}

请参见

概念

公共语言规范


编译器警告（等级 2）CS3021

[image]

Visual C# 参考：错误和警告
编译器警告（等级 2）CS3021
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

由于程序集没有 CLSCompliant 属性，因此“type”不需要 CLSCompliant 属性

如果 [CLSCompliant(false)] 出现在程序集中的类上，而该程序集没有将程序集级 CLSCompliant 属性设置为 true（即行 [assembly: CLSCompliant(true)]），则会出现此警告。由于程序集没有声明自身符合 CLS，因此程序集中的任何对象均不需要声明自身不符合 CLS，因为已假定不符合 CLS。有关 CLS 遵从性的更多信息，请参见编写符合 CLS 的代码。

若要消除此警告，请移除该属性或添加程序集级属性。

示例

下面的示例生成 CS3021：

复制代码
// CS3021.cs
using System;
// Uncomment the following line to declare the assembly CLS Compliant,
// and avoid the warning without removing the attribute on the class.
//[assembly: CLSCompliant(true)]

// Remove the next line to avoid the warning.
[CLSCompliant(false)]               // CS3021
public class C
{
    public static void Main()
    {
    }
}

请参见

概念

公共语言规范


编译器警告（等级 3）CS0067

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0067
发送反馈意见

更新：2007 年 11 月

错误消息

从未使用过事件“event”

声明了 event，但它从未在声明它的类中使用。

下面的示例生成 CS0067：

复制代码
// CS0067.cs
// compile with: /W:3
using System;
delegate void MyDelegate();

class MyClass
{
   public event MyDelegate evt;   // CS0067
   // uncomment TestMethod to resolve this CS0067
/*
   private void TestMethod()
   {
      if (evt != null)
         evt();
   }
*/
   public static void Main()
   {
   }
}


编译器警告（等级 3）CS0105

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0105
发送反馈意见

更新：2007 年 11 月

错误消息

“namespace”的 using 指令以前在此命名空间中出现过

namespace 只应声明一次，但却声明了多次；请移除所有重复的命名空间声明。

下面的示例生成 CS0105：

复制代码
// CS0105.cs
// compile with: /W:3
using System;
using System;   // CS0105

public class a
{
   public static void Main()
   {
   }
}


编译器警告（等级 3）CS0168

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0168
发送反馈意见

更新：2007 年 11 月

错误消息

变量“var”已赋值，但其值从未使用过

当声明了变量但不使用时，编译器会发出警告。

下面的示例生成两个 CS0168 警告：

复制代码
// CS0168.cs
// compile with: /W:3
public class clx
{
   public int i;
}

public class clz
{
   public static void Main()
   {
      int j = 0;   // CS0168, uncomment the following line
      // j++;
      clx a;       // CS0168, try the following line instead
      // clx a = new clx();
   }
}


编译器警告（等级 3）CS0169

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0169
发送反馈意见

更新：2007 年 11 月

错误消息

私有字段“class member”从未使用过

声明了私有变量，但从未引用过。在声明了类的私有成员却没有使用它时，通常将生成该警告。

下面的示例生成 CS0169：

复制代码
// compile with: /W:3
using System;
public class ClassX
{
   int i;   // CS0169, i is not used anywhere

   public static void Main()
   {
   }
}


编译器警告（等级 3）CS0219

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0219
发送反馈意见

更新：2007 年 11 月

错误消息

变量“variable”已赋值，但其值从未使用过

在声明了变量并对其赋值但不使用它时，编译器会发出第 3 级警告。

下面的示例生成 CS0219：

复制代码
// CS0219.cs
// compile with: /W:3
public class MyClass
{
   public static void Main()
   {
      int a = 0;   // CS0219
   }
}


编译器警告（等级 3）CS0282

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0282
示例 发送反馈意见

更新：2007 年 11 月

错误消息

在分部类或结构“type”的多个声明中的字段之间没有已定义的排序方式。要指定排序方式，所有实例字段必须位于同一声明中。

要解决此错误，请将所有的成员变量放在一个单独的分部类定义中。

遇到此错误的通常情况是：在多个位置定义分部 struct，其中某些成员变量在一个定义中，而另一些成员变量在其他定义中。

下面的代码生成 CS0282。

示例

此代码包含对 struct 的描述。通过以下命令，在一个步骤内将这两个模块编译到一起：

csc /targt:library cs0282_1.cs cs0282_2.cs

复制代码
partial struct A
{
    int i;
}

此代码包含对同一 struct 的冲突性描述。

复制代码
partial struct A
{
    int j;
}


编译器警告（等级 3）CS0414

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0414
另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

私有字段“field”已被赋值，但该值从未使用过

在编译器检测到从未引用过某个变量的几种情况下可能发生此警告：

  • 已为一个私有字段赋予一个常数值，但随后从未读取该字段。不必要的赋值可能影响性能。可考虑删除该字段。

  • 仅在初始值设定项中为一个私有或内部静态字段赋予一个常数值。可考虑将该字段更改为常量字段。

  • 已为一个私有或内部字段赋予常数值，并且该字段只在 #ifdef 指令排除的块中使用。可考虑将该字段放入 #ifdef 块中。

  • 已在多个位置为一个私有或内部字段赋予常数值，但没有以其他方式访问过该字段。如果不需要该字段，可以考虑将其移除。否则，请按照某种适当的方式加以使用。

在其他情况下，或在建议的解决方法不可接受时，请使用 #pragma 0414。

下面的示例演示了用于生成 CS0414 的一种方式：

复制代码
// CS0414
// compile with: /W3
class C
{
   private int i = 1;  // CS0414

   public static void Main()
   { }
}

注意 如果变量 i 声明为 protected or public，则不会生成错误，因为编译器无法知道派生类是否会使用该变量，也不知道其他一些客户端代码是否会实例化该类并引用该变量。

请参见

其他资源

C# 编译器错误
C# 编译器选项


编译器警告（等级 3）CS0419

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0419
发送反馈意见

更新：2007 年 11 月

错误消息

cref 属性中有不明确的引用：“Method Name1”。 假定为“Method Name2”，但可能还与其他重载匹配，包括“Method Name3”。

在代码的 XML 文档注释中，无法解析引用。如果重载了方法，或找到两个具有相同名称的不同标识符，则可能出现此警告。若要解决此警告，请使用限定名消除引用的歧义，或用括号将特定的重载括起来。

下面的示例生成 CS0419。

复制代码
// cs0419.cs
// compile with: /doc:x.xml /W:3
interface I
{
   /// text for F(void)
   void F();
   /// text for F(int)
   void F(int i);
}
/// text for class MyClass
public class MyClass
{
   /// <see cref="I.F"/>
   public static void MyMethod(int i)
   {
   }
/* Try this instead:
   /// <see cref="I.F(int)"/>
   public static void MyMethod(int i)
   {
   }
*/
   /// text for Main
   public static void Main ()
   {
   }
}


编译器警告（等级 3）CS0642

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0642
发送反馈意见

更新：2007 年 11 月

错误消息

空语句可能有错误

条件语句后面的分号可能导致代码不按预期执行。

您可以使用 /nowarn 编译器选项或 #pragmas warning 来禁用此警告；有关更多信息，请参见 /nowarn（取消显示指定警告）（C# 编译器选项） 或 #pragma warning（C# 参考）。

下面的示例生成 CS0642：

复制代码
// CS0642.cs
// compile with: /W:3
class MyClass
{
   public static void Main()
   {
      int i;

      for (i = 0; i < 10; i += 1);   // CS0642 semicolon intentional?
      {
         System.Console.WriteLine (i);
      }
   }
}


编译器警告（等级 3）CS0659

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0659
发送反馈意见

更新：2007 年 11 月

错误消息

“class”重写 Object.Equals(object o) 但不重写 Object.GetHashCode()

编译器检测到 Equals 函数的重写，但没有检测到 GetHashCode 的重写。重写 Equals 意味着也要重写 GetHashCode。

有关更多信息，请参见

  • Hashtable.

  • Equals 和相等运算符 (==) 的实现准则

  • 实现 Equals 方法

  • GetHashCode

下面的示例生成 CS0659：

复制代码
// CS0659.cs
// compile with: /W:3 /target:library
class Test
{
   public override bool Equals(object o) { return true; }   // CS0659
}

// OK
class Test2
{
   public override bool Equals(object o) { return true; }
   public override int GetHashCode() { return 0; }
}


编译器警告（等级 3）CS0660

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0660
发送反馈意见

更新：2007 年 11 月

错误消息

“class”定义运算符 == 或运算符 !=，但不重写 Object.Equals(object o)

编译器检测到用户定义的相等运算符或不相等运算符，但没有检测到 Equals 函数的重写。用户定义的相等运算符或不相等运算符意味着也要重写 Equals 函数。

下面的示例生成 CS0660：

复制代码
// CS0660.cs
// compile with: /W:3 /warnaserror
class Test   // CS0660
{
   public static bool operator == (object o, Test t)
   {
      return true;
   }

   // uncomment the Equals function to resolve
   // public override bool Equals(object o)
   // {
   //    return true;
   // }

   public override int GetHashCode()
   {
      return 0;
   }

   public static void Main()
   {
   }
}


编译器警告（等级 3）CS0661

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0661
发送反馈意见

更新：2007 年 11 月

错误消息

“class”定义运算符 == 或运算符 !=，但不重写 Object.GetHashCode()

编译器检测到用户定义的相等运算符或不相等运算符，但没有检测到 GetHashCode 函数的重写。用户定义的相等运算符或不相等运算符意味着也要重写 GetHashCode 函数。

下面的示例生成 CS0661：

复制代码
// CS0661.cs
// compile with: /W:3
class Test   // CS0661
{
   public static bool operator == (object o, Test t)
   {
      return true;
   }

   public static bool operator != (object o, Test t)
   {
      return true;
   }

   public override bool Equals(object o)
   {
      return true;
   }

   // uncomment the GetHashCode function to resolve
   // public override int GetHashCode()
   // {
   //    return 0;
   // }

   public static void Main()
   {
   }
}


编译器警告（等级 3）CS0665

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0665
发送反馈意见

更新：2007 年 11 月

错误消息

条件表达式中的赋值总是常数；您是要使用 == 而非 = 吗？

条件表达式使用的是 = 运算符而不是 == 运算符。

下面的示例生成 CS0665：

复制代码
// CS0665.cs
// compile with: /W:3
class Test
{
   public static void Main()
   {
      bool i = false;

      if (i = true)   // CS0665
      // try the following line instead
      // if (i == true)
      {
      }

      System.Console.WriteLine(i);
   }
}


编译器警告（等级 3）CS0675

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0675
发送反馈意见

更新：2007 年 11 月

错误消息

按位“或”运算符在带符号扩展操作数上使用；请考虑首先强制转换为较小的无符号类型

编译器隐式地拓宽了并带符号扩展了变量，然后在按位“或”运算中使用了结果值。这可能导致意外的行为。

下面的示例生成 CS0675：

复制代码
// CS0675.cs
// compile with: /W:3
using System;

public class sign
{
   public static void Main()
   {
      int hi = 1;
      int lo = 1;
      long value = (((long)hi) << 32) | lo;              // CS0675
      // try the following line instead
      // long value = (((long)hi) << 32) | ((uint)lo);   // correct
   }
}


编译器警告（等级 3）CS0693

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS0693
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型参数“type parameter”与外部类型“type”中的类型参数同名

当具有泛型成员（例如泛型类中的方法）时将发生此错误。方法的类型参数不一定与类的类型参数相同，因此不能为它们指定相同的名称。有关更多信息，请参见泛型方法（C# 编程指南）。

若要避免这种情况，请对其中一个类型参数使用其他名称。

示例

下面的示例生成 CS0693。

复制代码
// CS0693.cs
// compile with: /W:3 /target:library
class Outer<T>
{
   class Inner<T> {}   // CS0693
   // try the following line instead
   // class Inner<U> {}
}


编译器警告（等级 3）CS1700

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS1700
示例 发送反馈意见

更新：2007 年 11 月

错误消息

程序集引用 Assembly Name 无效，无法解析

此警告指示未正确指定属性（如 InternalsVisibleToAttribute）。

有关更多信息，请参见友元程序集（C# 编程指南）。

示例

下面的示例生成 CS1700。

复制代码
// CS1700.cs
// compile with: /target:library
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("app2, Retargetable=f")]   // CS1700
[assembly:InternalsVisibleTo("app2")]   // OK


编译器警告（等级 3）CS1702

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS1702
发送反馈意见

更新：2007 年 11 月

错误消息

假定程序集引用“Assembly Name #1”和“Assembly Name #2”匹配，您可能需要提供运行时策略

两个程序集引用有不同的内部版本号和/或修订号，所以它们不会自动统一。您可能需要在应用程序的 .config 文件中使用指令，以提供强制统一的运行时策略。


编译器警告（等级 3）CS1717

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS1717
示例 发送反馈意见

更新：2007 年 11 月

错误消息

对同一变量进行赋值；您是要给其他变量赋值吗？

当您将一个变量分配给它自身时，例如 a = a，会出现此警告。

几个常见的错误可以生成此警告：

  • 写入 a = a 作为 if 语句的条件，例如 if (a = a)。您的意思可能是 if (a == a)（这总为 true），所以您可以将其简要地写为 if (true)。

  • 键入错误。您的意思可能是 a = b。

  • 在一个参数和字段有相同名称的构造函数中，不要使用 this 关键字；您的意思可能是 this.a = a。

示例

下面的示例生成 CS1717。

复制代码
// CS1717.cs
// compile with: /W:3
public class Test
{
   public static void Main()
   {
      int x = 0;
      x = x;   // CS1717
   }
}


编译器警告（等级 3）CS1718

[image]

Visual C# 参考：错误和警告
编译器警告（等级 3）CS1718
示例 发送反馈意见

更新：2007 年 11 月

错误消息

对同一变量进行比较；您是要比较其他变量吗?

如果您是要比较其他变量，则您只需更正语句。

但另一种可能是您要测试真假值，并通过类似 if (a == a) (true) 或 if (a < a) (false) 这样的语句来完成此操作。最好简单地说成 if (true) 或 if (false)。这有两个原因：

  • 它更简单；更清楚地表达您的意思。

  • 它可以避免混淆：C# 2.0 的一个新特性就是可为 null 的值类型，这类似于 T-SQL（SQL Server 使用的编程语言）中的值 null。由于 T-SQL 中使用了三重逻辑，熟悉 T-SQL 的开发人员可能关心可为 null 的类型在类似 if (a == a) 这样的表达式上的效果。如果您使用 true 或 false，您就可以避免这类可能出现的混淆。

示例

下面的代码生成警告 CS1718。

复制代码
// CS1718.cs
using System;
public class Tester
{
    public static void Main()
    {
        int i = 0;
        if (i == i) { // CS1718.cs
        //if (true) {
            i++;
        }
    }
}


编译器警告（等级 4）CS0028

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS0028
发送反馈意见

更新：2007 年 11 月

错误消息

“function declaration”的签名错误，不能作为入口点

Main 的方法声明无效：声明所使用的签名无效。Main 必须声明为静态的并且它必须返回 int 或 void。有关更多信息，请参见 Main() 和命令行参数（C# 编程指南）。

下面的示例生成 CS0028：

复制代码
// CS0028.cs
// compile with: /W:4 /warnaserror
public class a
{
    public static double Main(int i)   // CS0028
    // Try the following line instead:
    // public static void Main()
    {
    }
}


编译器警告（等级 4）CS0078

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS0078
发送反馈意见

更新：2007 年 11 月

错误消息

“l”后缀很容易与数字“1”混淆 -- 为清楚起见，请使用“L”

编译器在检测到转换到的 long 所使用的是小写 l 而不是大写 L 时发出警告。

下面的示例生成 CS0078：

复制代码
// CS0078.cs
// compile with: /W:4
class test {
   public static void TestL(long i)
   {
   }

   public static void TestL(int i)
   {
   }

   public static void Main()
   {
      TestL(25l);   // CS0078
      // try the following line instead
      // TestL(25L);
   }
}


编译器警告（等级 4）CS0109

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS0109
发送反馈意见

更新：2007 年 11 月

错误消息

成员“member”未隐藏继承成员。不需要关键字 new

类声明包括了 new 关键字，即使声明不重写基类中的现有声明。可以删除 new 关键字。

下面的示例生成 CS0109：

复制代码
// CS0109.cs
// compile with: /W:4
namespace x
{
   public class a
   {
      public int i;
   }

   public class b : a
   {
      public new int i;
      public new int j;   // CS0109
      public static void Main()
      {
      }
   }
}


编译器警告（等级 4）CS0402

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS0402
发送反馈意见

更新：2007 年 11 月

错误消息

“identifier”：入口点不能是泛型的或属于泛型类型

在泛型类型中找到入口点。若要移除此警告，请在非泛型类或结构中实现 Main。

复制代码
// CS0402.cs
// compile with: /W:4
class C<T>
{
   public static void Main()  // CS0402
   {

   }
}

class CMain
{
   public static void Main() {}
}


编译器警告（等级 4）CS0422

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS0422
发送反馈意见

更新：2007 年 11 月

错误消息

不再支持 /incremental 选项

Visual C# 2005 中不支持增量编译（/incr 或 /incremental）。


编译器警告（等级 4）CS0429

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS0429
示例 发送反馈意见

更新：2007 年 11 月

错误消息

检测到无法访问的表达式代码

每当无法访问代码中某个表达式的一部分时，便会发生此错误。在下面的示例中，条件 false && myTest() 满足此条件，因为 myTest() 方法因 && 运算符左边始终为 false 这一事实而永远不会被计算。只要 && 运算符将 false 语句计算为 false，它便会停止计算，并且永远不会计算右边。

示例

下面的代码生成 CS0429。

复制代码
// CS0429.cs
public class cs0429
{
    public static void Main()
    {
        if (false && myTest())  // CS0429
        // Try the following line instead:
        // if (true && myTest())
        {
        }
        else
        {
            int i = 0;
            i++;
        }
    }

    static bool myTest() { return true; }
}


编译器警告（等级 4）CS0628

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS0628
发送反馈意见

更新：2007 年 11 月

错误消息

“member”：在密封类中声明了新的保护成员

sealed 类无法引入 protected 成员，因为没有其他类能够从 sealed 类继承并使用 protected 成员。

下面的示例生成 CS0628：

复制代码
// CS0628.cs
// compile with: /W:4
sealed class C
{
   protected int i;   // CS0628
}

class MyClass
{
   public static void Main()
   {
   }
}


编译器警告（等级 4）CS0649

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS0649
发送反馈意见

更新：2007 年 11 月

错误消息

从未对字段“field”赋值，字段将一直保持其默认值“value”

编译器检测到未初始化且从未被赋值的私有字段声明或内部字段声明。

下面的示例生成 CS0649：

复制代码
// CS0649.cs
// compile with: /W:4
using System.Collections;

class MyClass
{
   Hashtable table;  // CS0649
   // You may have intended to initialize the variable to null
   // Hashtable table = null;

   // Or you may have meant to create an object here
   // Hashtable table = new Hashtable();

   public void Func(object o, string p)
   {
      // Or here
      // table = new Hashtable();
      table[p] = o;
   }

   public static void Main()
   {
   }
}


编译器警告（等级 4）CS1573

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS1573
发送反馈意见

更新：2007 年 11 月

错误消息

参数“parameter”在“parameter”的 XML 注释中没有匹配的 param 标记（但其他参数有）

当使用 /doc 编译器选项时，为方法中的某些而不是全部参数指定了注释。您可能忘记了输入这些参数的注释。

下面的示例生成 CS1573：

复制代码
// CS1573.cs
// compile with: /W:4
public class MyClass
{
   /// <param name='Int1'>Used to indicate status.</param>
   // enter a comment for Char1?
   public static void MyMethod(int Int1, char Char1)
   {
   }

   public static void Main ()
   {
   }
}


编译器警告（等级 4）CS1591

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS1591
发送反馈意见

更新：2007 年 11 月

错误消息

缺少对公共可见类型或成员“Type_or_Member”的 XML 注释

指定了 /doc 编译器选项，但有一个或多个构造没有注释。

下面的示例生成 CS1591：

复制代码
// CS1591.cs
// compile with: /W:4 /doc:x.xml

/// text
public class Test
{
   // /// text
   public static void Main()   // CS1591, remove "//" from previous line
   {
   }
}


编译器警告（等级 4）CS1610

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS1610
发送反馈意见

更新：2007 年 11 月

错误消息

无法删除用于默认 Win32 资源的临时文件“file”-- resource

在使用 /win32res 编译器选项时以及当 %TEMP% 目录没有 DELETE 权限时，该警告指示编译器未能删除它创建的临时文件。

确保对 %TEMP% 目录有读/写/删除权限。

如有必要，可以手动删除这些文件，这对 C# 或您的任何程序没有损害。


编译器警告（等级 4）CS1712

[image]

Visual C# 参考：错误和警告
编译器警告（等级 4）CS1712
示例 发送反馈意见

更新：2007 年 11 月

错误消息

类型参数“type parameter”在“type”的 XML 注释中没有匹配的 typeparam 标记（但其他类型参数有）

泛型类型的文档缺少 typeparam 标记。有关更多信息，请参见<typeparam>（C# 编程指南）。

示例

下面的代码生成警告 CS1712。若要解决此错误，请为类型参数 S 添加一个 typeparam 标记。

复制代码
// CS1712.cs
// compile with: /doc:cs1712.xml
using System;
class Test
{
    public static void Main() {}
    /// <param name="j"> This is the j parameter.</param>
    /// <typeparam name="T"> This is the T type parameter.</typeparam>
    public void bar<T,S>(int j) {}  // CS1712
}


编译器错误 CS1959

[image]

Visual C# 参考：错误和警告
编译器错误 CS1959
示例 另请参见 发送反馈意见

更新：2007 年 11 月

错误消息

“name”的类型为“type”。在常量声明中指定的类型必须为 sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string、枚举类型或引用类型。

常量声明中允许的类型局限于在此消息中描述的那些类型。

更正此错误

  • 使用允许的类型声明常量。

示例

下面的代码将产生 CS1959，因为 null 不是类型。

复制代码
// cs1959.cs
class Program
    {
        static void Test<T>() where T : class
        {
            const T x = null; // CS1959
        }
    }

请参见

参考

常量（C# 编程指南）
null（C# 参考）


编译器错误 CS0845

[image]

Visual C# 参考：错误和警告
编译器错误 CS0845
示例 发送反馈意见

更新：2007 年 11 月

错误消息

表达式目录树 lambda 不能在合并运算符左侧包含 null 文本。

由于 null 本身不具有类型，因此 null 合并运算符无法对其进行运算。

更正此错误

  • 将 null 文本强制转换为对象。

示例

下面的代码将生成 CS0845：

复制代码
// cs0845.cs
using System;
using System.Linq;
using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<object>> e = () => null ?? null; // CS0845
            // Try the following line instead.
            // Expression<Func<object>> e = () => (object)null ?? null;
        }
    }
}


C# 术语

[image]

C# 语言参考
C# 术语
另请参见 发送反馈意见

更新：2007 年 11 月

访问修饰符

用于限制对类型或类型成员的访问的关键字，如 private、protected、internal 或 public。有关更多信息，请参见访问修饰符。

可访问成员

一种可由给定类型访问的成员。一种类型的可访问成员不一定可由另一种类型访问。有关更多信息，请参见访问修饰符和友元程序集。

访问器

设置或检索与属性关联的私有数据成员的值的方法。读写属性具有 get 和 set 访问器。只读属性只有 get 访问器。有关更多信息，请参见属性。

匿名方法

匿名方法是作为参数传递给委托的代码块。有关更多信息，请参见匿名方法。

基类

一种由其他“派生”类继承的类。有关更多信息，请参见继承。

调用堆栈

运行时从程序的开始位置到当前正在执行的语句的一系列方法调用。

类

一种描述对象的数据类型。类不仅包含数据，还包含作用于数据的方法。有关更多信息，请参见类。

构造函数

类或结构中用于初始化该类型的对象的一种特殊方法。有关更多信息，请参见构造函数。

委托

委托是一种引用方法的类型。一旦为委托分配了方法，委托将与该方法具有完全相同的行为。有关更多信息，请参见委托。

派生类

一种使用继承获取、扩展或修改其他“基”类的行为和数据的类。有关更多信息，请参见继承。

析构函数

类或结构中用于准备要由系统销毁的实例的一种特殊方法。有关更多信息，请参见析构函数。

事件

类或结构的一个成员，用于发送更改通知。有关更多信息，请参见事件。

字段

类或结构的一个可直接访问的数据成员。

泛型

使用泛型，可以定义使用类型参数定义的类和/或方法。当客户端代码实例化类型时，会指定一个特定的类型作为参数。有关更多信息，请参见泛型。

IDE

集成开发环境。为各种开发工具（包括编译器、调试器、代码编辑器和设计器）提供统一用户界面的应用程序。

不可变类型

一种类型，其实例数据（字段和属性）在创建后不会更改。大多数值类型都属于不可变类型。

不可访问的成员

无法由给定类型访问的成员。一种类型的不可访问的成员并不一定对另一种类型是不可访问的。有关更多信息，请参见访问修饰符。

继承

C# 支持继承，因此从其他类（也称为基类）派生的类将继承相同的方法和属性。继承涉及基类和派生类。有关更多信息，请参见继承。

接口

一种只包含公共方法、事件和委托的签名的类型。继承接口的对象必须实现该接口中定义的所有方法和事件。类或结构可以继承任意数目的接口。有关更多信息，请参见接口。

迭代器

迭代器是这样一种方法，它允许包含集合或数组的类的使用者使用 foreach，in（C# 参考） 循环访问该集合或数组。

成员

在类或结构上声明的字段、属性、方法或事件。

方法

一个提供类或结构的行为的命名代码块。

可变类型

一种类型，其实例数据（字段和属性）在创建后可以更改。大多数引用类型都属于可变类型。

嵌套类型

在另一个类型的声明内声明的类型。

对象

类的实例。对象存在于内存中，具有数据和作用于这些数据的方法。有关更多信息，请参见对象、类和结构。

属性

通过访问器访问的数据成员。有关更多信息，请参见属性。

重构

重用以前输入的代码。例如，Visual C# 速成版代码编辑器可以智能地重新设置代码的格式，将突出显示的代码块转变成方法。有关更多信息，请参见重构。

引用类型

一种数据类型。声明为引用类型的变量指向存储数据的位置。有关更多信息，请参见引用类型。

静态

声明为静态的类或方法不必首先使用关键字 new 进行实例化就可存在。Main() 就属于静态方法。有关更多信息，请参见静态类和静态类成员。

结构

一种复合数据类型，通常用于包含具有某种逻辑关系的多个变量。结构还可以包含方法和事件。结构不支持继承，但支持接口。结构是值类型，而类是引用类型。有关更多信息，请参见结构。

值类型

值类型是在堆栈上分配的数据类型，与在堆上分配的引用类型不同。内置类型（包括数值类型以及结构类型和可以为 null 的类型）都属于值类型。类类型和字符串类型属于引用类型。有关更多信息，请参见 值类型（C# 参考）。

请参见

概念

C# 编程指南

其他资源

C# 参考


C# 语言规范

[image]

C# 语言参考
C# 语言规范
另请参见 发送反馈意见

更新：2007 年 11 月

C# 语言规范是 C# 语法的权威资料来源。它包含有关该语言各个方面的详细信息，以及 Visual C# 产品文档未涉及的许多要点。C# 3.0 规范已经更新，并且与早期版本合并为单个文档。

可以从下列位置获得 Microsoft Word 格式的 C# 语言规范：

  • “Visual C# Developer Center”（Visual C# 开发人员中心） 上的 MSDN online。

  • 在 Visual Studio 中，位于 Visual Studio 2008 安装目录下的 VC#\Specifications\1033\ 目录中。

如果您的计算机没有安装 Microsoft Word，则可以使用免费的 Word Viewer 2003 来查看、复制和打印该规范的 Word 版本。

请参见

概念

C# 编程指南

其他资源

C# 参考


C#（针对 C++ 开发人员）

[image]

Visual C# 语言概念
C#（针对 C++ 开发人员）
另请参见 发送反馈意见

更新：2007 年 11 月

下表包含 C# 和本机 C++（它不使用 /clr）之间的重要比较信息。如果您是一位 C++ 程序员，此表将为您提供这两种语言的最重要差异。

说明：

C++ 和 C# 项目是从不同的项目模型派生的。有关 C++ 和 C# 项目之间差异的更多信息，请参见项目中的项管理和使用解决方案资源管理器。

功能 | 参考主题
继承：在 C++ 中，类和结构实际上是相同的，而在 C# 中，它们很不一样。C# 类可以实现任意数量的接口，但只能从一个基类继承。而且，C# 结构不支持继承，也不支持显式默认构造函数（默认情况下提供一个）。 | 类 接口 struct（C# 参考）
数组：在 C++ 中，数组只是一个指针。在 C# 中，数组是包含方法和属性的对象。例如，可通过 Length 属性查询数组的大小。C# 数组还使用索引器（验证用于访问数组的各个索引）。声明 C# 数组的语法不同于声明 C++ 数组的语法：在 C# 中，“[]”标记出现在数组类型之后，而非变量之后。 | 数组（C# 编程指南） 索引器（C# 编程指南）
布尔值：在 C++ 中，bool 类型实质上是一个整数。在 C# 中，不存在 bool 类型与其他类型之间的相互转换。 | bool
long 类型：long 类型在 C# 中为 64 位，而在 C++ 中为 32 位。 | long
传递参数：在 C++ 中，除非显式通过指针或引用传递，否则所有变量都通过值传递。在 C# 中，除非显式通过具有 ref 或 out 参数修饰符的引用传递，否则类通过引用传递，而结构通过值传递。 | 结构 类 ref（C# 参考） out（C# 参考）
switch 语句：与 C++ 中的 switch 语句不同，C# 不支持从一个 case 标签贯穿到另一个 case 标签。 | switch
委托：C# 委托大致类似于 C++ 中的函数指针，是类型安全和可靠的。 | 委托
基类方法：C# 支持用于调用派生类中重写基类成员的 base 关键字。而且，在 C# 中，使用 override 关键字重写虚拟或抽象方法是显式的。 | base 请参见 override 的示例
方法隐藏：C++ 通过继承支持方法的隐式“隐藏”。在 C# 中，必须使用 new 修饰符来显式隐藏继承的成员。 | new
预处理器指令用于条件编译。C# 中不使用头文件。 | C# 预处理器指令
异常处理：C# 为无论是否引发异常都应执行的代码提供 finally 关键字。 | try-finally try-catch-finally
C# 运算符：C# 支持其他运算符，如 is 和 typeof。它还引入了某些逻辑运算符的不同功能。 | & 运算符 | 运算符 ^ 运算符 is typeof
typedef 关键字。在 C++ 中，typedef 用于为已声明的类型创建更短或更方便的名称。在 C# 中，using 指令提供此功能。 | using 指令（C# 参考）
extern 关键字：在 C++ 中，extern 用于导入类型。在 C# 中，extern 用于为使用同一程序集的不同版本创建别名。 | extern
static 关键字：在 C++ 中，static 既可用于声明类级实体，也可用于声明特定于某模块的类型。在 C# 中，static 仅用于声明类级实体。 | static
C# 中的 Main 方法和 C++ 中的 main 函数的声明方式不同。在 C# 中，它是大写的，并且始终是 static 的。此外，在 C# 中，对处理命令行参数的支持要可靠得多。 | Main() 和命令行参数（C# 编程指南）
在 C# 中，只有在 unsafe 模式下才允许使用指针。 | unsafe
在 C# 中以不同的方式执行重载运算符。 | C# 运算符
字符串：在 C++ 中，字符串只是字符的数组。在 C# 中，字符串是支持可靠搜索方法的对象。 | 字符串 String
foreach 关键字使您可以循环访问数组和集合。 | foreach，in
全局：在 C# 中，不支持全局方法和全局变量。方法和变量必须包含在 class 或 struct 之内。 | C# 程序的常规结构
#define 预处理指令：在 C++ 中， #define 指令通常用于声明常量值。在 C# 中，#define 指令不可用于此目的。在 C# 中，最好将常量定义为枚举类型（仅限整数值）或者定义为类或结构的静态成员。如果具有多个像这样的常量，可以考虑创建一个单独的“Constants”类来保存这些常量。 | static（C# 参考） const（C# 参考） enum（C# 参考）
导入类型：在 C++ 中，多个模块公用的类型放置在头文件中。在 C# 中，可通过元数据获取此信息。 | using 元数据概述
C# 中的局部变量在初始化前不能使用。 | 方法（C# 编程指南）
内存管理：C++ 语言不提供垃圾回收功能；在进程终止前，未显式释放的内存仍保持已分配的状态。C# 语言提供垃圾回收功能。 | 垃圾回收
析构函数：C# 具有用于确定地释放非托管资源的不同语法。 | 析构函数 using 语句（C# 参考）
构造函数：与 C++ 类似，如果在 C# 中不提供类构造函数，则为您自动生成一个默认构造函数。该默认构造函数将所有字段初始化为它们的默认值。 | 实例构造函数 默认值表
C# 不支持位域。 | C++ 位字段
C# 的输入/输出服务和格式设置依赖于 .NET Framework 的运行库。 | C# 语言教程 格式化数值结果表
在 C# 中，方法参数不能有默认值。如果要获得同样的效果，请使用方法重载。 | 编译器错误 CS0241
在 C# 中，以类似于 C++ 模板的方式提供用于类型参数化的泛型类型和泛型方法。不过，二者有明显区别。例如，在 C# 中，泛型类型信息在运行时保留。 | C# 中的泛型
as 关键字与标准强制转换类似，不同之处在于：如果转换失败，则返回值为 null，而不是引发异常。这与使用 C++ 中的 static_cast（与 dynamic_cast 不同，它不执行运行时检查，因此失败时不引发异常）相似。 | as（C# 参考）

有关 C# 和其他编程语言中的关键字之间的比较的更多信息，请参见对等语言。有关 C# 应用程序的常规结构的信息，请参见 C# 程序的通用结构（C# 编程指南）。

请参见

概念

C# 编程指南
项目中的项管理
使用解决方案资源管理器


摘要（C# 与 Java）

[image]

Visual C# 语言概念
摘要（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

尽管 Microsoft 和其他厂商已经推出了许多用于 .NET 平台的语言，但与 Java 非常相似的 C# 非常适合想要从 J2EE 迁移到 .NET 平台的开发人员使用。

本文对这两种语言进行了比较和对比。在许多方面，C# 同时具有 C++ 的强大、Java 的精致和 Visual Basic 的易开发性，希望本文能够成功地证实这一点。

请参见

概念

C# 编程指南

其他资源

Visual C#
C# 编程语言（针对 Java 开发人员）


高级 C# 技术（C# 与 Java）

[image]

Visual C# 语言概念
高级 C# 技术（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 提供了一些有用的语言功能（如索引器、属性和委托），这些功能支持高级编程技术。

索引器

索引器提供了一种像访问数组一样访问类或结构的方法。例如，可以有一个表示公司中的单个部门的类。这个类可能包含部门中所有雇员的姓名，而且索引器允许您访问这些姓名，如下所示：

C# 复制代码
sales[0] = "Nikki";
sales[1] = "Becky";

例如，在类定义中，通过用以下签名定义属性来启用索引器：

C# 复制代码
public string this [int index]  //indexer

然后，像对普通属性一样，为它提供 get 和 set 方法，这些访问器指定当使用该索引器时将引用到什么内部成员。

在下面的简单示例中，创建一个名为 Department 的类，这个类使用索引器访问该部门中的雇员，这些雇员在内部表示为一个字符串数组：

C# 复制代码
public class Department
{
    private string name;
    private const int MAX_EMPLOYEES = 10;
    private string[] employees = new string[MAX_EMPLOYEES];  //employee array

    public Department(string departmentName)  //constructor
    {
        name = departmentName;
    }

    public string this [int index]  //indexer
    {
        get
        {
            if (index >= 0 && index < MAX_EMPLOYEES)
            {
                return employees[index];
            }
            else
            {
                throw new System.IndexOutOfRangeException();
            }
        }
        set
        {
            if (index >= 0 && index < MAX_EMPLOYEES)
            {
                employees[index] = value;
            }
            else
            {
                throw new System.IndexOutOfRangeException();
            }
        }
    }

    // code for the rest of the class...
}

然后，可以创建这个类的一个实例并访问它，如下面的代码示例所示：

C# 复制代码
class TestDepartment
{
    static void Main()
    {
        Department sales = new Department("Sales");

        sales[0] = "Nikki";
        sales[1] = "Becky";

        System.Console.WriteLine("The sales team is {0} and {1}", sales[0], sales[1]);
    }
}

输出为：

The sales team is Nikki and Becky

有关更多信息，请参见 索引器（C# 编程指南）。

属性

C# 提供了一种称为“属性”的机制，用于添加有关类型的声明性信息。属性有些类似于 Java 中的批注概念。关于类型的额外信息位于类型定义前面的声明标记中。下面的示例演示如何使用 .NET Framework 属性来修饰类或方法。

在下面的示例中，通过添加 WebMethodAttribute 属性将 GetTime 方法标记为 XML Web services。

C# 复制代码
public class Utilities : System.Web.Services.WebService
{
    [System.Web.Services.WebMethod]  // Attribute
    public string GetTime()
    {
        return System.DateTime.Now.ToShortTimeString();
    }
}

添加 WebMethod 属性使 .NET Framework 自动处理调用此函数所需的 XML/SOAP 交换。调用此 Web 服务将检索下面的值：

<?xml version="1.0" encoding="utf-8" ?>

<string xmlns="http://tempuri.org/">7:26 PM</string>

在下面的示例中，通过添加 SerializableAttribute 属性将 Employee 类标记为 serializable。虽然 Salary 字段被标记为 public，但是它不会被序列化，因为它标记了 NonSerializedAttribute 属性。

C# 复制代码
[System.Serializable()]
public class Employee
{
    public int ID;
    public string Name;
    [System.NonSerialized()] public int Salary;
}

有关更多信息，请参见 创建自定义属性（C# 编程指南）。

委托

C++、Pascal 和其他语言支持函数指针的概念，允许您在运行时选择要调用哪些函数。

Java 不提供任何具有函数指针功能的结构，但 C# 提供这种构造。通过使用 Delegate 类，委托实例可以封装属于可调用实体的方法。

对于实例方法，委托由一个包含类的实例和该实例上的方法组成。对于静态方法，可调用实体由一个类和该类上的静态方法组成。因此，委托可用于调用任何对象的函数，而且委托是面向对象的、类型安全和安全的。

定义和使用委托有三个步骤：

  • 声明

  • 实例化

  • 调用

使用以下语法声明委托：

C# 复制代码
delegate void Del1();

然后可以使用此委托引用返回 void 而且不带任何参数的函数。

类似地，若要为任何带字符串参数且返回 long 型结果的函数创建委托，则使用以下语法：

C# 复制代码
delegate long Del2(string s);

然后，可以将此委托分配到带有此签名的任何方法，如下所示：

C# 复制代码
Del2 d;                // declare the delegate variable
d = DoWork;  // set the delegate to refer to the DoWork method

其中 DoWork 的签名为：

C# 复制代码
public static long DoWork(string name)

重新分配委托

Delegate 对象是不可变的，即设置与它们匹配的签名后就不能再更改签名了。但是，如果其他方法具有同一签名，您也可以指向该方法。在本例中，将 d 重新分配给一个新的委托对象，因此 d 将调用 DoMoreWork 方法。只有 DoWork 和 DoMoreWork 具有相同签名时，您才可以执行此操作。

C# 复制代码
Del2 d;                    // declare the delegate variable
d = DoWork;      // set the delegate to refer to the DoWork method
d = DoMoreWork;  // reassign the delegate to refer to the DoMoreWork method

调用委托

调用委托相当简单。只需用委托变量的名称替代方法名称。这将使用值 11 和 22 调用 Add 方法，并返回一个 long 型结果，该结果被赋给变量 sum：

C# 复制代码
Del operation;                 // declare the delegate variable
operation = Add;      // set the delegate to refer to the Add method
long sum = operation(11, 22);  // invoke the delegate

以下代码演示了委托的创建、实例化和调用：

C# 复制代码
public class MathClass
{
    public static long Add(int i, int j)       // static
    {
        return (i + j);
    }

    public static long Multiply (int i, int j)  // static
    {
        return (i * j);
    }
}

class TestMathClass
{
    delegate long Del(int i, int j);  // declare the delegate type

    static void Main()
    {
        Del operation;  // declare the delegate variable

        operation = MathClass.Add;       // set the delegate to refer to the Add method
        long sum = operation(11, 22);             // use the delegate to call the Add method

        operation = MathClass.Multiply;  // change the delegate to refer to the Multiply method
        long product = operation(30, 40);         // use the delegate to call the Multiply method

        System.Console.WriteLine("11 + 22 = " + sum);
        System.Console.WriteLine("30 * 40 = " + product);
    }
}

输出

11 + 22 = 33

30 * 40 = 1200

委托实例必须包含一个对象引用。前面的示例通过将方法声明为静态（意味着无需指定对象引用）避开了这一要求。但如果委托指实例方法，则必须按如下方式给定对象引用：

C# 复制代码
Del operation;                   // declare the delegate variable
MathClass m1 = new MathClass();  // declare the MathClass instance
operation = m1.Add;     // set the delegate to refer to the Add method

在本例中，Add 和 Multiply 是 MathClass 的实例方法。如果 MathClass 的方法未声明为静态方法，那么您可以使用 MathClass 的实例通过委托来调用这些方法，如下所示：

C# 复制代码
public class MathClass
{
    public long Add(int i, int j)       // not static
    {
        return (i + j);
    }

    public long Multiply (int i, int j)  // not static
    {
        return (i * j);
    }
}

class TestMathClass
{
    delegate long Del(int i, int j);  // declare the delegate type

    static void Main()
    {
        Del operation;                   // declare the delegate variable
        MathClass m1 = new MathClass();  // declare the MathClass instance

        operation = m1.Add;       // set the delegate to refer to the Add method
        long sum = operation(11, 22);      // use the delegate to call the Add method

        operation = m1.Multiply;  // change the delegate to refer to the Multiply method
        long product = operation(30, 40);  // use the delegate to call the Multiply method

        System.Console.WriteLine("11 + 22 = " + sum);
        System.Console.WriteLine("30 * 40 = " + product);
    }
}

输出

本示例提供的输出与之前方法声明为静态的示例相同。

11 + 22 = 33

30 * 40 = 1200

委托和事件

.NET Framework 还可以广泛地将委托用于事件处理任务，如 Windows 或 Web 应用程序中的按钮 Click 事件。Java 中的事件处理通常通过实现自定义侦听器类完成，而 C# 开发人员则可以利用委托处理事件。事件的声明类似于具有委托类型的字段，区别在于事件声明前面有 event 关键字。事件通常被声明为 public，但允许使用任何可访问性修饰符。下面的示例演示了 delegate 和 event 的声明。

C# 复制代码
// Declare the delegate type:
public delegate void CustomEventHandler(object sender, System.EventArgs e);

// Declare the event variable using the delegate type:
public event CustomEventHandler CustomEvent;

事件委托是多路广播的，这意味着它们可以对多个事件处理方法进行引用。通过维护事件的已注册事件处理程序列表，委托为引发事件的类担当事件发送器的角色。下面的示例演示如何为多个函数订阅事件。EventClass 类包含委托、事件和调用事件的方法。请注意调用事件只能从声明该事件的类内部进行。然后，TestEvents 类使用 += 运算符订阅事件，并使用 -= 运算符取消订阅。调用 InvokeEvent 方法时，它将激发事件，所有订阅了该事件的函数也同步激发，如下面的示例所示。

C# 复制代码
public class EventClass
{
    // Declare the delegate type:
    public delegate void CustomEventHandler(object sender, System.EventArgs e);

    // Declare the event variable using the delegate type:
    public event CustomEventHandler CustomEvent;

    public void InvokeEvent()
    {
        // Invoke the event from within the class that declared the event:
        CustomEvent(this, System.EventArgs.Empty);
    }
}

class TestEvents
{
    private static void CodeToRun(object sender, System.EventArgs e)
    {
        System.Console.WriteLine("CodeToRun is executing");
    }

    private static void MoreCodeToRun(object sender, System.EventArgs e)
    {
        System.Console.WriteLine("MoreCodeToRun is executing");
    }

    static void Main()
    {
        EventClass ec = new EventClass();

        ec.CustomEvent += new EventClass.CustomEventHandler(CodeToRun);
        ec.CustomEvent += new EventClass.CustomEventHandler(MoreCodeToRun);

        System.Console.WriteLine("First Invocation:");
        ec.InvokeEvent();

        ec.CustomEvent -= new EventClass.CustomEventHandler(MoreCodeToRun);

        System.Console.WriteLine("\nSecond Invocation:");
        ec.InvokeEvent();
    }
}

输出

First Invocation:

CodeToRun is executing

MoreCodeToRun is executing

Second Invocation:

CodeToRun is executing

请参见

任务

“委托”示例

概念

C# 编程指南

参考

委托（C# 编程指南）
事件（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


数组（C# 与 Java）

[image]

Visual C# 语言概念
数组（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

数组是具有相同数据类型的项的有序集合。要访问数组中的某个项，需要同时使用数组名称及该项与数组起点之间的偏移量。在 C# 中，声明和使用数组的方法与 Java 有一些重要区别。

一维数组

一维数组以线性方式存储固定数目的项，只需一个索引值即可标识任意一个项。在 C# 中，数组声明中的方括号必须跟在数据类型后面，且不能放在变量名称之后，而这在 Java 中是允许的。因此，类型为 integers 的数组应使用以下语法声明：

C# 复制代码
int[] arr1;

下面的声明在 C# 中无效：

C# 复制代码
//int arr2[];  //compile error

声明数组后，可以使用 new 关键字设置其大小，这一点与 Java 相同。下面的代码声明数组引用：

C# 复制代码
int[] arr;
arr = new int[5];  // create a 5 element integer array

然后，可以使用与 Java 相同的语法访问一维数组中的元素。C# 数组索引也是从零开始的。下面的代码访问上面数组中的最后一个元素：

C# 复制代码
System.Console.WriteLine(arr[4]);  // access the 5th element

初始化

C# 数组元素可以在创建时使用与 Java 相同的语法进行初始化：

C# 复制代码
int[] arr2Lines;
arr2Lines = new int[5] {1, 2, 3, 4, 5};

但 C# 初始值设定项的数目必须与数组大小完全匹配，这与 Java 不同。可以使用此功能在同一行中声明并初始化 C# 数组：

C# 复制代码
int[] arr1Line = {1, 2, 3, 4, 5};

此语法创建一个数组，其大小等于初始值设定项的数目。

在程序循环中初始化

在 C# 中初始化数组的另一个方法是使用 for 循环。下面的循环将数组的每个元素都设置为零：

C# 复制代码
int[] TaxRates = new int[5];

for (int i=0; i<TaxRates.Length; i++)
{
    TaxRates[i] = 0;
}

交错数组

C# 和 Java 都支持创建交错（非矩形）数组，即每一行包含的列数不同的数组。例如，在下面的交错数组中，第一行有四项，而第二行有三项：

C# 复制代码
int[][] jaggedArray = new int[2][];
jaggedArray[0] = new int[4];
jaggedArray[1] = new int[3];

多维数组

可以使用 C# 创建规则的多维数组，多维数组类似于同类型值的矩阵。虽然 Java 和 C# 都支持交错数组，但 C# 还支持多维数组（数组的数组）。

使用以下语法声明多维矩形数组：

C# 复制代码
int[,] arr2D;      // declare the array reference
float[,,,] arr4D;  // declare the array reference

声明之后，可以按如下方式为数组分配内存：

C# 复制代码
arr2D = new int[5,4];  // allocate space for 5 x 4 integers

然后，可以使用以下语法访问数组的元素：

C# 复制代码
arr2D[4,3] = 906;

由于数组是从零开始的，因此此行将第四行第五列中的元素设置为 906。

初始化

可以使用以下一种方法，在同一个语句中创建、设置并初始化多维数组：

C# 复制代码
int[,] arr4 = new int [2,3] { {1,2,3}, {4,5,6} };
int[,] arr5 = new int [,]   { {1,2,3}, {4,5,6} };
int[,] arr6 =               { {1,2,3}, {4,5,6} };

在程序循环中初始化

可以使用此处所示的嵌套循环初始化数组中的所有元素：

C# 复制代码
int[,] arr7 = new int[5,4];

for(int i=0; i<5; i++)
{
    for(int j=0; i<4; j++)
    {
        arr7[i,j] = 0;  // initialize each element to zero
    }
}

System.Array 类

在 .NET Framework 中，数组是作为 Array 类的实例实现的。此类提供了许多有用的方法，如 Sort 和 Reverse。

下面的示例演示了使用这些方法是多么的简单。首先，使用 Reverse 方法将数组元素反转，然后使用 Sort 方法对它们进行排序：

C# 复制代码
class ArrayMethods
{
    static void Main()
    {
        // Create a string array of size 5:
        string[] employeeNames = new string[5];

        // Read 5 employee names from user:
        System.Console.WriteLine("Enter five employee names:");
        for(int i=0; i<employeeNames.Length; i++)
        {
            employeeNames[i]= System.Console.ReadLine();
        }

        // Print the array in original order:
        System.Console.WriteLine("\nArray in Original Order:");
        foreach(string employeeName in employeeNames)
        {
            System.Console.Write("{0}  ", employeeName);
        }

        // Reverse the array:
        System.Array.Reverse(employeeNames);

        // Print the array in reverse order:
        System.Console.WriteLine("\n\nArray in Reverse Order:");
        foreach(string employeeName in employeeNames)
        {
            System.Console.Write("{0}  ", employeeName);
        }

        // Sort the array:
        System.Array.Sort(employeeNames);

        //  Print the array in sorted order:
        System.Console.WriteLine("\n\nArray in Sorted Order:");
        foreach(string employeeName in employeeNames)
        {
            System.Console.Write("{0}  ", employeeName);
        }
    }
}

输出

Enter five employee names:

Luca

Angie

Brian

Kent

Beatriz

Array in Original Order:

Luca Angie Brian Kent Beatriz

Array in Reverse Order:

Beatriz Kent Brian Angie Luca

Array in Sorted Order:

Angie Beatriz Brian Kent Luca

请参见

概念

C# 编程指南

参考

数组（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


使用不确定数目的参数（C# 与 Java）

[image]

Visual C# 语言概念
使用不确定数目的参数（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 允许您在声明某个方法时通过指定 params 关键字来向此方法发送数目不确定的参数。变量列表也可包含常规参数，但请注意：使用 params 关键字声明的参数必须放在最后。它采取长度可变的数组形式，并且每个方法只能有一个 params 参数。

当编译器试图解析某个方法调用时，它会查找参数列表与所调用的方法相匹配的那个方法。如果无法找到与参数 (Argument) 列表相匹配的方法重载，但有一个具有相应类型的 params 参数 (Parameter) 的匹配版本，则将调用此方法，将其他参数 (Argument) 放入一个数组中。

下面的示例说明了这一点：

C# 复制代码
class TestParams
{
    private static void Average(string title, params int[] values)
    {
        int sum = 0;
        System.Console.Write("Average of {0} (", title);

        for (int i = 0; i < values.Length; i++)
        {
            sum += values[i];
            System.Console.Write(values[i] + ", ");
        }
        System.Console.WriteLine("): {0}", (float)sum/values.Length);
    }
    static void Main()
    {
        Average ("List One", 5, 10, 15);
        Average ("List Two", 5, 10, 15, 20, 25, 30);
    }
}

以上示例中，方法 Average 是使用类型为整型数组的 params 参数声明的，这样您便可以用任意数目的变量来调用此方法。输出结果如下：

Average of List One (5, 10, 15, ): 10

Average of List Two (5, 10, 15, 20, 25, 30, ): 17.5

如果要允许使用不确定的不同类型的参数，可以指定 Object 类型的 params 参数。

请参见

概念

C# 编程指南

参考

将数组作为参数传递（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


Main () 方法和其他方法（C# 与 Java）

[image]

Visual C# 语言概念
Main () 方法和其他方法（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

本节讨论方法和如何通过引用和通过值来传递方法参数。

Main () 方法

每个 C# 应用程序都必须包含一个 Main 方法，以指定从哪里开始执行程序。在 C# 中，Main 的首字母大写，而 Java 使用小写的 main。

Main 只能返回 int 或 void，并有一个代表命令行参数的可选字符串数组参数：

C# 复制代码
static int Main(string[] args)
{
    //...
    return 0;
}

对于包含任何传入的命令行参数的字符串数组参数，其工作方式与在 Java 中的工作方式相同。因此，args[0] 指定第一个命令行参数，args[1] 表示第二个参数，依此类推。与 C++ 不同，args 数组不包含 EXE 文件名称。

其他方法

向某个方法传递参数时，可以通过值或通过引用传递参数。值参数简单地采用任意变量的值，在方法中使用。因此，调用代码中的变量值不受对方法内的参数所执行的操作的影响。

而引用参数指向在调用代码中声明的变量，因此，通过引用传递时方法将修改此变量的内容。

通过引用传递

在 Java 和 C# 中，引用对象的方法参数始终都是通过引用传递的，而基元数据类型参数（C# 中的值类型）是通过值传递的。

在 C# 中，若要通过引用传递值类型，需要指定关键字 ref 或 out。这两个关键字的区别在于参数初始化的不同。ref 参数必须在使用前进行初始化，而 out 参数不必在传递前进行显式初始化，并且它将忽略以前的任何值。

ref 关键字

如果希望被调用方法永久更改用作参数的变量的值，请在值类型参数中指定 ref 关键字。这种做法传递的不是调用中所使用的变量的值，而是对此变量本身的引用。方法随后会处理此引用，因此在方法执行期间对此参数所做的更改将保存到用作此方法的参数的原始变量中。

下面的代码在 Add 方法中演示了此功能，其中的第二个 int 参数就是使用 ref 关键字通过引用传递的：

C# 复制代码
class TestRef
{
    private static void Add(int i, ref int result)
    {
        result += i;
        return;
    }

    static void Main()
    {
        int total = 20;
        System.Console.WriteLine("Original value of 'total': {0}", total);

        Add(10, ref total);
        System.Console.WriteLine("Value after calling Add(): {0}", total);
    }
}

此示例很简单，其输出表明对结果参数所做的更改已在 Add 方法调用所使用的 total 变量中体现出来：

Original value of 'total': 20

Value after calling Add(): 30

这是因为结果参数引用了调用代码中 total 变量所占用的实际内存位置。类的属性不是变量，因此不能直接用作 ref 参数。

调用方法时，ref 关键字必须位于参数前面，并且必须位于方法声明之中。

out 关键字

out 关键字的作用与 ref 关键字非常相似，并且对使用 out 关键字声明的参数所做的更改将在方法之外体现出来。out 关键字与 ref 关键字有两点不同：(1) 方法中将忽略 out 参数的任何初始值；(2) 必须在方法调用过程中为 out 参数赋值：

C# 复制代码
class TestOut
{
    private static void Add(int i, int j, out int result)
    {
        // The following line would cause a compile error:
        // System.Console.WriteLine("Initial value inside method: {0}", result);

        result = i + j;
        return;
    }

    static void Main()
    {
        int total = 20;
        System.Console.WriteLine("Original value of 'total': {0}", total);

        Add(33, 77, out total);
        System.Console.WriteLine("Value after calling Add(): {0}", total);
    }
}

在此示例中，Add 方法的第三个参数就是使用 out 关键字进行声明的，并且在调用此方法时还需要对该参数使用 out 关键字。输出将为：

Original value of 'total': 20

Value after calling Add(): 110

总而言之，如果想让方法修改现有变量，请使用 ref 关键字；若要返回在方法内生成的值，请使用 out 关键字。当方法为调用代码生成多个结果值时，通常将 out 关键字与方法的返回值结合起来使用。

请参见

概念

C# 编程指南
Main() 和命令行参数（C# 编程指南）

参考

传递参数（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


移动设备和数据（C# 与 Java）

[image]

Visual C# 语言概念
移动设备和数据（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

有了 C# 和 .NET Compact Framework，就可以使用桌面数据库编程所用的相同概念和类似 API 来访问和管理移动设备上的数据库数据。ADO.NET 在移动设备上提供一个面向 Windows CE 设备（包括 Pocket PC 和 Smartphone）的桌面 API 子集。有关更多信息，请参见数据库访问（C# 与 Java）。

Java

在 Java 中，可以使用 J2ME 和 JDBC 从移动设备访问数据库。有关更多信息，请参见数据库访问（C# 与 Java）。J2ME 不会在所有设备上都表示为单一 API，也没有单一的开发环境。另外，J2ME 必须在虚拟机（具体是 KVM 还是 JVM 取决于配置）中运行。

C#

在 C# 中，可以在桌面或移动设备上使用熟悉的连接、命令和数据表等概念来执行数据库读取操作。您可以使用 System.Data.SqlServerCe 命名空间和类。例如，可以执行下列操作：

  • 将 System.Data.SqlServerCe.SqlCeConnection 用于数据库连接。

  • 将 System.Data.SqlServerCe.SqlCeCommand 用于 SQL 命令对象。

  • 将结果集对象（如 DataTable）用于数据表对象。

.NET Framework 提供了 DataAdapter 对象，使您能够轻松地将上述各类结合使用。可以使用该对象的 System.Data.SqlServerCe.SqlCeDataAdapter 连接属性来设置 System.Data.SqlServerCe.SqlCeConnection 对象。

可以使用 DataAdapter 的 System.Data.SqlServerCe.SqlCeDataAdapter.SelectCommand 属性指定要执行的查询，也可以将该查询与连接对象一起传递给 DataAdapter 的构造函数。

C# 复制代码
da = new SqlCeDataAdapter("SELECT * FROM Users", cn);

可以使用 DataAdapter 对象的 Fill 方法创建 DataTable 对象。DataAdapter 对象包含由查询返回的结果集数据。可以循环访问 DataAdapter 对象，以便使用 Rows 集合访问数据行。

下面的示例演示如何访问移动设备上的 SQL Server CE (SQLCE) 数据库中的表行。

C# 复制代码
namespace DataAccessCE
{
    using System.Data;
    using System.Data.SqlServerCe;

    class DataAccessCE
    {
        public static string connectionString = "";
        public static SqlCeConnection cn = null;
        public static SqlCeDataAdapter da = null;
        public static DataTable dt = new DataTable();

        static void Main()
        {
            connectionString = "Data Source=\\My Documents\\Database.sdf" ;
            cn = new SqlCeConnection(connectionString);

            da = new SqlCeDataAdapter("SELECT * FROM Users", cn);
            da.Fill(dt);

            foreach (DataRow dr in dt.Rows)
            {
                System.Console.WriteLine(dr[0]);
            }
        }
    }
}

有关更多信息，请参见下列主题：

  • ADO.NET 概述

  • 访问数据 (Visual Studio)

  • 示例代码 (ADO.NET)

  • 复制

编译代码

在应用程序中使用 SQLCE 数据库之前，必须将对 System.Data.SqlServerCe 的引用添加到项目中。在开发环境中，通过单击“项目”菜单中的“添加引用”可以完成此操作。然后从“添加引用”对话框中选择 System.Data.SqlServerCe 组件。

说明：

对于在以下说明中使用的某些 Visual Studio 用户界面元素，您的计算机可能会显示不同的名称或位置。这些元素取决于您使用的 Visual Studio 版本及设置。有关更多信息，请参见Visual Studio 设置。

可靠编程

若要编译和运行代码，需要满足以下条件；否则，行 da.Fill(dt); 会失败并引发异常。

  • 设备上安装了 SQL Server CE。

  • 在一个名为 Database.sdf 的 SQLCE 数据库上有一个包含一些待测试数据的数据库表。可以在设备上使用 SQL CE 工具生成此表，也可以从 SQL Server 桌面复制该表以生成 .sdf 文件。可以将 .sdf 文件添加到项目，或将它手动复制到连接字符串中指定的目录。

请参见

概念

C# 编程指南
数据集 (ADO.NET)

参考

智能设备（如何实现 - C#）
SqlConnection
SqlCommand

其他资源

C#（针对 Java 开发人员）
智能设备开发
使用数据集 (ADO.NET)


数据类型（C# 与 Java）

[image]

Visual C# 语言概念
数据类型（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

本主题讨论 Java 和 C# 在数据的表示、分配和垃圾回收等方面的一些主要相同点和不同点。

复合数据类型

在 Java 和 C# 中，类作为有字段、方法和事件的复合数据类型这一概念是相似的。（有关类继承的概念在名为 继承与派生类（C# 与 Java） 的主题中单独讨论。）C# 引入结构的概念，结构是一种堆栈分配的复合数据类型，它不支持继承。在其他许多方面，结构与类非常相似。结构提供一种将相关字段和方法组合在一起的轻量方法，以便在紧凑循环和其他性能关键的方案中使用。

C# 使您能够创建一个在对类的实例进行垃圾回收前调用的析构函数方法。在 Java 中，可以使用 finalize 方法来包含代码，用于在将对象作为垃圾回收前清理资源。在 C# 中，此功能由类析构函数执行。析构函数就像是没有参数和前面不带颚化符 (~) 的构造函数。

内置数据类型

C# 提供 Java 中可用的所有数据类型，并增加了对无符号数字和新的 128 位高精度浮点类型的支持。

核心类库为 Java 中的每个基元数据类型提供了一个包装类，此包装类将基元数据类型表示为 Java 对象。例如，Int32 类包装 int 数据类型，Double 类包装 double 数据类型。

另一方面，C# 中的所有基元数据类型都是 System 命名空间中的对象。对于每个数据类型，提供了一个简称（或别名）。例如，int 是 System.Int32 的简称，而 double 是 System.Double 的简写。

下表提供了 C# 数据类型列表及其别名。如表所示，前八个数据类型对应于 Java 中可用的基元类型。但请注意，Java 的 boolean 在 C# 中称为 bool。

简称 | .NET 类 | 类型 | 宽度 | 范围（位）
byte | Byte | 无符号整数 | 8 | 0 到 255
sbyte | SByte | 有符号整数 | 8 | -128 到 127
int | Int32 | 有符号整数 | 32 | -2,147,483,648 到 2,147,483,647
uint | UInt32 | 无符号整数 | 32 | 0 到 4294967295
short | Int16 | 有符号整数 | 16 | -32,768 到 32,767
ushort | UInt16 | 无符号整数 | 16 | 0 到 65535
long | Int64 | 有符号整数 | 64 | -922337203685477508 到 922337203685477507
ulong | UInt64 | 无符号整数 | 64 | 0 到 18446744073709551615
float | Single | 单精度浮点型 | 32 | -3.402823e38 至 3.402823e38
double | Double | 双精度浮点型 | 64 | -1.79769313486232e308 至 1.79769313486232e308
char | Char | 单 Unicode 字符 | 16 | 文本中使用的 Unicode 符号
bool | Boolean | 逻辑布尔值类型 | 8 | True 或 False
object | Object | 所有其他类型的基类型 |  |
string | String | 字符序列 |  |
decimal | Decimal | 精确小数类型或整型，可以表示带有 29 个有效位的十进制数 | 128 | ±1.0 × 10e−28 至 ±7.9 × 10e28

因为 C# 将所有基元数据类型当作对象表示，所以可以在基元数据类型上调用对象方法。例如：

C# 复制代码
static void Main()
{
    int i = 10;
    object o = i;
    System.Console.WriteLine(o.ToString());
}

借助自动装箱和取消装箱完成此操作。有关更多信息，请参见 装箱和取消装箱（C# 编程指南）。

常量

Java 和 C# 均能够声明这样一个变量：它的值在编译时指定，在运行时不能更改。Java 使用 final 字段修饰符声明此类变量，而 C# 则使用 const 关键字。除了 const 以外，C# 还提供 readonly 关键字以声明可以在运行时进行一次赋值（在声明语句中或在构造函数中）的变量。初始化以后，readonly 变量的值不能更改。当已单独编译的模块需要共享版本号等数据时，可以使用 readonly 变量。如果模块 A 更新了，并使用一个新的版本号进行了重新编译，则模块 B 可以用这个新的常数值进行初始化，而无需重新编译。

枚举

枚举用于对已命名常数进行分组，与在 C 和 C++ 中的用法相似，但不可用于 Java。下面的示例定义一个简单的 Color 枚举。

C# 复制代码
public enum Color
{
    Green,   //defaults to 0
    Orange,  //defaults to 1
    Red,     //defaults to 2
    Blue     //defaults to 3
}

也可以将整数值分配给枚举，如下面的枚举声明所示：

C# 复制代码
public enum Color2
{
    Green = 10,
    Orange = 20,
    Red = 30,
    Blue = 40
}

下面的代码示例调用 Enum 类型的 GetNames 方法来显示枚举的可用常数。然后，将值分配给枚举，并显示此值。

C# 复制代码
class TestEnums
{
    static void Main()
    {
        System.Console.WriteLine("Possible color choices: ");

        //Enum.GetNames returns a string array of named constants for the enum.
        foreach(string s in System.Enum.GetNames(typeof(Color)))
        {
            System.Console.WriteLine(s);
        }

        Color favorite = Color.Blue;

        System.Console.WriteLine("Favorite Color is {0}", favorite);
        System.Console.WriteLine("Favorite Color value is {0}", (int) favorite);
    }
}

输出

Possible color choices:

Green

Orange

Red

Blue

Favorite Color is Blue

Favorite Color value is 3

字符串

Java 和 C# 中的字符串类型的行为相似，只有细微的差异。两种字符串类型都是不可变的，意味着一旦创建了字符串，字符串的值就无法更改。两个实例中的方法看上去修改了字符串的实际内容，实际上创建并返回了一个新字符串，而原始字符串保持不变。C# 和 Java 中比较字符串值的过程有所不同。若要在 Java 中比较字符串值，则开发人员需要在字符串类型上调用 equals 方法，原因是默认情况下 == 运算符会比较引用类型。在 C# 中，开发人员可以直接使用 == 或 != 运算符来比较字符串值。尽管在 C# 中字符串是引用类型，但在默认情况下 == 和 != 运算符将比较字符串值而不是引用。

和在 Java 中一样，C# 开发人员不应使用字符串类型来串连字符串，以避免在每次串连字符串时创建新字符串类所产生的开销。相反，开发人员可以使用 StringBuilder 类，它与 Java 的 StringBuffer 类在功能上等效。

字符串文本

C# 能够避免在字符串常数中使用转义序列，如用于制表符的 "\t" 或用于反斜杠字符的 "\"。若要达到此目的，只需在分配字符串值之前使用 @ 符号声明原义字符串。下面的示例演示如何使用转义字符以及如何分配字符串文本：

C# 复制代码
static void Main()
{
    //Using escaped characters:
    string path1 = "\\\\FileShare\\Directory\\file.txt";
    System.Console.WriteLine(path1);

    //Using String Literals:
    string path2 = @"\\FileShare\Directory\file.txt";
    System.Console.WriteLine(path2);
}

转换和强制转换

Java 和 C# 对数据类型的自动转换和强制转换遵循相似的规则。

和 Java 一样，C# 支持隐式和显式类型转换。如果是扩大转换，则为隐式转换。例如，下面从 int 到 long 的转换为隐式转换，与在 Java 中相同：

C# 复制代码
int int1 = 5;
long long1 = int1;  //implicit conversion

下面是 .NET Framework 数据类型之间的隐式转换列表：

源类型 | 目标类型
Byte | short、ushort、int、uint、long、ulong、float、double 或 decimal
Sbyte | short、int、long、float、double 或 decimal
Int | long、float、double 或 decimal
Uint | long、ulong、float、double 或 decimal
Short | int、long、float、double 或 decimal
Ushort | int、uint、long、ulong、float、double 或 decimal
Long | float、double 或 decimal
Ulong | float、double 或 decimal
Float | double
Char | ushort、int、uint、long、ulong、float、double 或 decimal

使用与 Java 相同的语法强制转换要显式转换的表达式：

C# 复制代码
long long2 = 5483;
int int2 = (int)long2;  //explicit conversion

下表列出了显式转换。

源类型 | 目标类型
Byte | sbyte 或 char
Sbyte | byte、ushort、uint、ulong 或 char
Int | sbyte、byte、short、ushort、uint、ulong 或 char
Uint | sbyte、byte、short、ushort、int 或 char
Short | sbyte、byte、ushort、uint、ulong 或 char
Ushort | sbyte、byte、short 或 char
Long | sbyte、byte、short、ushort、int、uint、ulong 或 char
Ulong | sbyte、byte、short、ushort、int、uint、long 或 char
Float | sbyte、byte、short、ushort、int、uint、long、ulong、char 或 decimal
Double | sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 decimal
Char | sbyte、byte 或 short
Decimal | sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 double

值与引用类型

C# 支持两种变量类型：

  • 值类型

    这些是内置基元数据类型（如 char、int 和 float）以及用 struct 声明的用户定义类型。

  • 引用类型

    从基元类型构造的类和其他复杂的数据类型。这种类型的变量不包含类型的实例而仅包含对实例的引用。

如果创建两个值类型变量 i 和 j（如下所示），则 i 和 j 完全相互独立：

C# 复制代码
int i = 10;
int j = 20;

为它们指定了独立的内存位置：

如果更改这两个变量中其中一个的值，另一个变量当然不受影响。例如，如果具有如下形式的表达式，则这两个变量之间仍然没有关联：

C# 复制代码
int k = i;

也就是说，如果更改 i 的值，则 k 将保留赋值时 i 具有的值。

C# 复制代码
i = 30;

System.Console.WriteLine(i.ToString());  // 30
System.Console.WriteLine(k.ToString());  // 10

但是，引用类型的行为则不同。例如，可以声明如下所示的两个变量：

C# 复制代码
Employee ee1 = new Employee();
Employee ee2 = ee1;

现在，因为类在 C# 中为引用类型，所以 ee1 被视为对 Employee 的引用。在前面的两行中，第一行在内存中创建 Employee 的一个实例，并设置 ee1 以引用该实例。因此，将 ee2 设置为等于 ee1 时，它包含了对内存中的类的重复引用。如果现在更改 ee2 上的属性，则 ee1 上的属性将反映这些更改，因为两者都指向内存中的相同对象，如下所示：

装箱和取消装箱

将值类型转换为引用类型的过程称为装箱。反之，将引用类型转换为值类型的过程则称为取消装箱。下面的代码说明了这一点：

C# 复制代码
int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;  // unboxing

Java 要求手动执行这种转换。可以通过构造这种对象或装箱，将基元数据类型转换为包装类的对象。同样地，可以通过在这种对象上调用合适的方法或取消装箱，从包装类对象中提取基元数据类型的值。有关装箱和取消装箱的更多信息，请参见装箱和取消装箱（C# 编程指南）。

请参见

概念

C# 编程指南

参考

类型（C# 编程指南）

其他资源

Visual C#
C# 编程语言（针对 Java 开发人员）


流控制（C# 与 Java）

[image]

Visual C# 语言概念
流控制（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

Java 和 C# 中的流控制语句（如 if else 和 switch 语句）非常相似。

分支语句

分支语句根据特定条件更改运行时的程序执行流。

if、else 和 else if

在 Java 和 C# 这两种语言中，这些语句完全相同。

switch 语句

在两种语言中，switch 语句均提供条件性多重分支操作。但是，它们之间也有不同。在 Java 中，您可以“跳过”一个 case 而执行下一个 case，除非此 case 的末尾使用了 break 语句。而 C# 要求在每个 case 的末尾都使用 break 或 goto 语句。否则，编译器将产生以下错误：

控制不能从一个 case 标签贯穿到另一个 case 标签。

注意：如果 case 没有指定任何要在匹配此 case 时执行的代码，控制将贯穿到随后的 case。如果 switch 语句中使用了 goto，那么您只能跳到同一个 switch 中的另一个 case 块。如果要跳到默认 case，应使用 goto default.；否则，应使用 goto case cond，此处的 cond 就是您希望跳到的那个 case 的匹配条件。与 Java switch 的另一点区别是：在 Java 中，switch 语句只能使用整型变量，而在 C# 中可以使用字符串变量。

例如，以下代码在 C# 中为有效代码，而在 Java 中为无效代码：

C# 复制代码
static void Main(string[] args)
{
    switch (args[0])
    {
        case "copy":
            //...
            break;

        case "move":
            //...
            goto case "delete";

        case "del":
        case "remove":
        case "delete":
            //...
            break;

        default:
            //...
            break;
    }
}

goto 的返回

在 Java 中，goto 是不能实现的保留关键字。但是，您可以使用具有 break 或 continue 的标签语句来实现与 goto 类似的用途。

C# 允许 goto 语句跳到标签语句处。但是请注意，要跳到某个特定的标签处，goto 语句必须在此标签的范围内。换言之，尽管 goto 可以从一个类中跳出，从而跳到此类之外或退出 try...catch 语句中的 finally 块，但不能使用它跳到某个语句块内。大多数情况下，建议不要使用 goto，因为它不是一种面向对象编程的好做法。

请参见

概念

C# 编程指南

其他资源

Visual C#
C# 编程语言（针对 Java 开发人员）


事件（C# 与 Java）

[image]

Visual C# 语言概念
事件（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

事件是当对象发生用户关心的情况（如单击图形用户界面上的控件）时，类将此对象通知用户的方法。这种通知即称为“引发事件”。引发事件的对象称为事件的源或发送方。

与 Java 中的事件处理（通过实现自定义侦听器类执行）不同，C# 开发人员可以使用委托进行事件处理。委托是一种封装方法的类型。一旦使用方法初始化委托后，委托的行为方式就与该方法完全相同，且可以使用 () 运算符调用。它与 C++ 函数指针类似，但它是类型安全的。

委托与其他任何方法的使用方法类似，也有参数（可能有多个）和返回值（仅一个），如下面的示例所示：

复制代码
public delegate int ReturnResult(int x, int y);

有关委托的更多信息，请参见 委托（C# 编程指南）。

事件和方法一样具有签名，签名包括名称和参数列表。事件的签名通过委托类型来定义，例如：

复制代码
public delegate void MyEventHandler(object sender, System.EventArgs e);

在 Windows 用户界面编程中，通常使第一个参数作为引用事件源的对象，使第二个参数作为携带与事件相关的数据的对象。但是，在 C# 语言中并不强制使用这种形式；只要事件签名返回 void，其他方面可以与任何有效的委托签名一样。

事件可以使用 event 关键字进行声明，如下例所示：

复制代码
public event MyEventHandler TriggerIt;

若要触发该事件，请定义引发该事件时要调用的方法，如下例所示：

复制代码
public void Trigger()
{
    TriggerIt();
}

若要引发事件，可以调用委托，并传递与该事件相关的参数。然后，该委托将调用已添加到该事件中的所有处理程序。每个事件都可以分配多个处理程序来接收该事件。在这种情况下，该事件将自动调用每个接收方。无论有多少个接收方，引发一个事件都只需调用一次该事件。

如果希望某个类接收事件，请通过使用 += 运算符向该事件添加委托来订阅该事件，例如：

复制代码
myEvent.TriggerIt += myEvent.MyMethod;

若要取消预订事件，请使用 -= 运算符将委托从事件中移除，例如：

复制代码
myEvent.TriggerIt -= new MyEventHandler(myEvent.MyMethod);

有关事件的更多信息，请参见 事件（C# 编程指南）。

说明：

在 C# 2.0 中，委托既可以封装命名方法，也可以封装匿名方法。有关匿名方法的更多信息，请参见 匿名方法（C# 编程指南）。

示例

说明

下面的示例定义了一个具有三个关联方法的事件。触发此事件时，将执行这些方法。接着，从此事件移除一个方法，再次触发此事件。

代码

复制代码
// Declare the delegate handler for the event:
public delegate void MyEventHandler();

class TestEvent
{
    // Declare the event implemented by MyEventHandler.
    public event MyEventHandler TriggerIt;

    // Declare a method that triggers the event:
    public void Trigger()
    {
        TriggerIt();
    }
    // Declare the methods that will be associated with the TriggerIt event.
    public void MyMethod1()
    {
        System.Console.WriteLine("Hello!");
    }
    public void MyMethod2()
    {
        System.Console.WriteLine("Hello again!");
    }
    public void MyMethod3()
    {
        System.Console.WriteLine("Good-bye!");
    }

    static void Main()
    {
        // Create an instance of the TestEvent class.
        TestEvent myEvent = new TestEvent();

        // Subscribe to the event by associating the handlers with the events:
        myEvent.TriggerIt += new MyEventHandler(myEvent.MyMethod1);
        myEvent.TriggerIt += new MyEventHandler(myEvent.MyMethod2);
        myEvent.TriggerIt += new MyEventHandler(myEvent.MyMethod3);
        // Trigger the event:
        myEvent.Trigger();

        // Unsuscribe from the the event by removing the handler from the event:
        myEvent.TriggerIt -= new MyEventHandler(myEvent.MyMethod2);
        System.Console.WriteLine("\"Hello again!\" unsubscribed from the event.");

        // Trigger the new event:
        myEvent.Trigger();
    }
}

输出

复制代码
Hello!
Hello again!
Good-bye!
"Hello again!" unsubscribed from the event.
Hello!
Good-bye!

请参见

概念

C# 编程指南

参考

event（C# 参考）
委托（C# 参考）

其他资源

C#（针对 Java 开发人员）


运算符重载（C# 与 Java）

[image]

Visual C# 语言概念
运算符重载（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

与 C++ 一样，C# 也允许您重载运算符，以供您自己的类使用。这样做，可以使使用用户定义的数据类型就像使用基本数据类型一样自然、合理。例如，您可以创建一个名为 ComplexNumber 的新数据类型来表示一个复杂的数字，并提供使用标准算术运算符对此类数字执行数学运算的方法，如使用 + 运算符将两个复杂数字相加。

若要重载某个运算符，可以编写一个函数，在其命名运算符之后加上要重载的运算符的符号。例如，可按以下方法重载 + 运算符：

C# 复制代码
public static ComplexNumber operator+(ComplexNumber a, ComplexNumber b)

所有运算符重载均为类的静态方法。此外还应注意，重载相等运算符 (==) 时，还必须重载不相等运算符 (!=)。< 和 > 运算符以及 <= 和 >= 运算符也必须成对重载。

以下是可重载的运算符的完整列表：

  • 一元运算符：+、-、!、~、++、--、true、false

  • 二元运算符：+、+、+、+、+、+、+、+、+、+、+、+、+、+、+、+

下面的代码示例创建一个重载 + 和 - 运算符的 ComplexNumber 类：

C# 复制代码
public class ComplexNumber
{
    private int real;
    private int imaginary;

    public ComplexNumber() : this(0, 0)  // constructor
    {
    }

    public ComplexNumber(int r, int i)  // constructor
    {
        real = r;
        imaginary = i;
    }

    // Override ToString() to display a complex number in the traditional format:
    public override string ToString()
    {
        return(System.String.Format("{0} + {1}i", real, imaginary));
    }

    // Overloading '+' operator:
    public static ComplexNumber operator+(ComplexNumber a, ComplexNumber b)
    {
        return new ComplexNumber(a.real + b.real, a.imaginary + b.imaginary);
    }

    // Overloading '-' operator:
    public static ComplexNumber operator-(ComplexNumber a, ComplexNumber b)
    {
        return new ComplexNumber(a.real - b.real, a.imaginary - b.imaginary);
    }
}

使用这个类，您就可以用以下代码创建和操作两个复杂的数字：

C# 复制代码
class TestComplexNumber
{
    static void Main()
    {
        ComplexNumber a = new ComplexNumber(10, 12);
        ComplexNumber b = new ComplexNumber(8, 9);

        System.Console.WriteLine("Complex Number a = {0}", a.ToString());
        System.Console.WriteLine("Complex Number b = {0}", b.ToString());

        ComplexNumber sum = a + b;
        System.Console.WriteLine("Complex Number sum = {0}", sum.ToString());

        ComplexNumber difference = a - b;
        System.Console.WriteLine("Complex Number difference = {0}", difference.ToString());
    }
}

如程序所示，您现在可以用非常直观的方式对属于 ComplexNumber 类的对象使用加减运算符。结果如下：

Complex Number a = 10 + 12i

Complex Number b = 8 + 9i

Complex Number sum = 18 + 21i

Complex Number difference = 2 + 3i

尽管 Java 会在内部重载 + 运算符以将字符串串联起来，但它并不支持运算符重载。

请参见

任务

“运算符重载”示例

概念

C# 编程指南

参考

可重载运算符（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


迁移到 Visual C#

[image]

Visual C# 语言概念
迁移到 Visual C#
另请参见 发送反馈意见

更新：2007 年 11 月

本节向从其他编程语言进行迁移的开发人员介绍 C# 语法和概念。它还包含可用于将 Java 语言源代码转换为 C# 源代码的 Java Language Conversion Assistant 的参考文档。

本节内容

C#（针对 Java 开发人员）

将 C# 语言的语法和构造与 Java 语言进行比较。

将 Java 应用程序转换为 Visual C#

描述 Java Language Conversion Assistant，它是一个用于将 Java 项目移植到 Visual C# 的工具。

C#（针对 C++ 开发人员）

比较 C# 语言与 C++ 语言的功能。

请参见

概念

C# 编程指南

其他资源

Visual C#
Visual C# 入门


继承与派生类（C# 与 Java）

[image]

Visual C# 语言概念
继承与派生类（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

您可以通过创建一个从现有类派生的新类来扩展现有类的功能。此派生类继承基类的属性，而且您可以根据需要添加或重写方法及属性。

在 C# 中，继承及接口实现均由 : 运算符定义，此运算符与 Java 中的 extends 和 implements 等效。在类声明中，基类应始终位于最左侧。

与 Java 一样，C# 也不支持多重继承，这意味着这些类不能从多个类中继承。不过，您可以使用接口实现此目的，具体操作方法与 Java 中的操作方法相同。

下面的代码使用两个私有成员变量 x 和 y 定义一个名为 CoOrds 的类，x 和 y 表示该点的位置。这两个变量分别通过名为 X 和 Y 属性访问：

C# 复制代码
public class CoOrds
{
    private int x, y;

    public CoOrds()  // constructor
    {
        x = 0;
        y = 0;
    }

    public int X
    {
        get { return x; }
        set { x = value; }
    }

    public int Y
    {
        get { return y; }
        set { y = value; }
    }
}

按如下方式从 CoOrds 类派生一个名为 ColorCoOrds 的新类：

C# 复制代码
public class ColorCoOrds : CoOrds

这样，ColorCoOrds 继承了基类的所有字段和方法，您可以根据需要向基类添加新的字段和方法，以在此派生类中提供其他功能。在此示例中，您将添加一个私有成员和多个访问器，以便向该类中添加颜色：

C# 复制代码
public class ColorCoOrds : CoOrds
{
    private System.Drawing.Color screenColor;

    public ColorCoOrds()  // constructor
    {
        screenColor = System.Drawing.Color.Red;
    }

    public System.Drawing.Color ScreenColor
    {
        get { return screenColor; }
        set { screenColor = value; }
    }
}

派生类的构造函数隐式调用基类的构造函数（在 Java 术语中，称为超类）。在继承中，所有基类构造函数先于派生类构造函数获得调用，并按照这些类在类层次结构中出现的先后顺序调用。

在类型上强制转换为基类

与在 Java 中一样，不能使用对基类的引用访问派生类的成员和方法，即使此基类引用可能包含对此派生类型的某个对象的有效引用。

您可以使用对此派生类型的引用来隐式引用某个派生类：

C# 复制代码
ColorCoOrds color1 = new ColorCoOrds();
CoOrds coords1 = color1;

在此代码中，基类引用 coords1 包含了 color1 引用的副本。

基关键字

您可以在子类中使用 base 关键字访问基类成员，即使当这些基成员在超类中被重写时也可执行此操作。例如，您可以创建一个派生类，该派生类中包含一个签名与基类中的签名相同的方法。如果该方法以 new 关键字开头，则表示此方法是属于该派生类的全新方法。您还可以使用基关键字再提供一个方法，用于访问基类中的原始方法。

例如，假设 CoOrds 基类有一个名为 Invert() 的方法，它用于交换 x 和 y 坐标。您可以使用下列代码在 ColorCoOrds 派生类中提供此方法的替换方法：

C# 复制代码
public new void Invert()
{
    int temp = X;
    X = Y;
    Y = temp;
    screenColor = System.Drawing.Color.Gray;
}

正如您所看到的，此方法先交换 x 和 y，然后将坐标的颜色设置为灰色。您可以为此方法提供对基实现的访问，方法是在 ColorCoOrds 中再创建一个方法（如以下方法）：

C# 复制代码
public void BaseInvert()
{
    base.Invert();
}

接着通过调用 BaseInvert() 方法在 ColorCoOrds 对象中调用基方法。

C# 复制代码
ColorCoOrds color1 = new ColorCoOrds();
color1.BaseInvert();

请记住，如果先将对基类的引用分配给 ColorCoOrds 的一个实例，然后再访问其方法，也可以得到同样的效果：

C# 复制代码
CoOrds coords1 = color1;
coords1.Invert();

选择构造函数

基类对象总是在任何派生类之前构建，因此，基类构造函数先于派生类构造函数获得执行。如果基类有多个构造函数，则派生类可以决定要调用哪个构造函数。例如，若要添加第二个构造函数，您可以按如下方式修改 CoOrds 类：

C# 复制代码
public class CoOrds
{
    private int x, y;

    public CoOrds()
    {
        x = 0;
        y = 0;
    }

    public CoOrds(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

接着可以使用基关键字更改 ColorCoOrds 类以使用某个特定的可用构造函数：

C# 复制代码
public class ColorCoOrds : CoOrds
{
    public System.Drawing.Color color;

    public ColorCoOrds() : base ()
    {
        color = System.Drawing.Color.Red;
    }

    public ColorCoOrds(int x, int y) : base (x, y)
    {
        color = System.Drawing.Color.Red;
    }
}

在 Java 中，此功能是通过 super 关键字实现的。

方法重写

通过为已声明方法提供一个新实现，可以让派生类重写基类的方法。Java 和 C# 的一个重要区别是：默认情况下，Java 方法被标记为 virtual，而在 C# 中，必须使用 virtual 修饰符将方法显式标记为 virtual。属性访问器和方法均可以重写，它们的重写方法非常相似。

虚方法

派生类中要重写的方法使用 virtual 修饰符进行声明。在派生类中，已重写的方法是使用 override 修饰符声明的。

override 修饰符表示派生类的一个方法或属性，它代替基类中具有相同名称和签名的方法或属性。要重写的基方法必须声明为 virtual、abstract 或 override：不能按此方式重写非虚方法或非静态方法。已重写和正在重写的方法或属性必须具有同样的访问级别修饰符。

下面的示例演示派生类中使用重写修饰符进行重写的 StepUp 虚方法：

C# 复制代码
public class CountClass
{
    public int count;

    public CountClass(int startValue)  // constructor
    {
        count = startValue;
    }

    public virtual int StepUp()
    {
        return ++count;
    }
}

class Count100Class : CountClass
{
    public Count100Class(int x) : base(x)  // constructor
    {
    }

    public override int StepUp()
    {
        return ((base.count) + 100);
    }
}

class TestCounters
{
    static void Main()
    {
        CountClass counter1 = new CountClass(1);
        CountClass counter100 = new Count100Class(1);

        System.Console.WriteLine("Count in base class = {0}", counter1.StepUp());
        System.Console.WriteLine("Count in derived class = {0}", counter100.StepUp());
    }
}

运行此代码时，可以看到派生类的构造函数使用的是基类中给定的方法体，因此您可以初始化计数成员，而无需复制此代码。输出如下：

Count in base class = 2

Count in derived class = 101

抽象类

抽象类将一个或多个方法或属性声明为抽象。此类方法不具有声明它们的类中所提供的实现，尽管抽象类也可以包含非抽象方法（即已为其提供了实现的方法）。抽象类不能直接实例化，它只能作为一个派生类。此类派生类必须使用 override 关键字为所有抽象方法和属性提供实现，除非派生成员本身被声明为抽象。

下面的示例声明 Employee 抽象类。您还将创建一个名为 Manager 的派生类，它提供在 Employee 类中定义的 Show() 抽象方法的实现：

C# 复制代码
public abstract class Employee
{
    protected string name;

    public Employee(string name)  // constructor
    {
        this.name = name;
    }

    public abstract void Show();  // abstract show method
}

public class Manager: Employee
{
    public Manager(string name) : base(name) {}  // constructor

    public override void Show()  //override the abstract show method
    {
        System.Console.WriteLine("Name : " + name);
    }
}

class TestEmployeeAndManager
{
    static void Main()
    {
        // Create an instance of Manager and assign it to a Manager reference:
        Manager m1 = new Manager("H. Ackerman");
        m1.Show();

        // Create an instance of Manager and assign it to an Employee reference:
        Employee ee1 = new Manager("M. Knott");
        ee1.Show();  //call the show method of the Manager class
    }
}

此代码调用 Manager 类提供的 Show() 的实现，并在屏幕上显示员工姓名。输出如下：

Name : H. Ackerman

Name : M. Knott

接口

接口是一种主干类，它包含方法签名但不包含方法实现。因此，接口类似于只包含抽象方法的抽象类。C# 接口与 Java 接口极其相似，而且工作方式也几乎相同。

根据定义，所有接口成员均为公共成员，并且接口不能包含常量、字段（私有数据成员）、构造函数、析构函数或任何类型的静态成员。如果为某个接口成员指定了任何修饰符，编译器将产生一个错误。

您可以从接口派生类，以实现此接口。此类派生类必须为接口的所有方法提供实现，除非派生类被声明为抽象。

接口声明方式与 Java 中的声明方式相同。在接口定义中，属性仅指示它的类型，并仅通过 get 和 set 关键字指示它为只读、只写还是读/写。以下接口声明一个只读属性：

C# 复制代码
public interface ICDPlayer
{
    void Play();  // method signature
    void Stop();  // method signature

    int FastForward(float numberOfSeconds);

    int CurrentTrack  // read-only property
    {
        get;
    }
}

如果使用冒号代替 Java 中的 implements 关键字，则类可以从此接口中继承。实现类必须按如下方式为所有方法及任何所需的属性访问器提供定义：

C# 复制代码
public class CDPlayer : ICDPlayer
{
    private int currentTrack = 0;

    // implement methods defined in the interface
    public void Play()
    {
        // code to start CD...
    }

    public void Stop()
    {
        // code to stop CD...
    }

    public int FastForward(float numberOfSeconds)
    {
        // code to fast forward CD using numberOfSeconds...

        return 0;  //return success code
    }

    public int CurrentTrack  // read-only property
    {
        get
        {
            return currentTrack;
        }
    }

    // Add additional methods if required...
}

实现多重接口

使用以下语法，可以让类实现多重接口：

C# 复制代码
public class CDAndDVDComboPlayer : ICDPlayer, IDVDPlayer

如果一个类在成员名称存在多义性的情况下实现多个接口，则使用属性或方法名称的完全限定符来解析此类。换言之，派生类可以使用方法的完全限定名称表明此派生类属于哪个接口，从而解决冲突，这与 ICDPlayer.Play() 中一样。

请参见

概念

C# 编程指南

参考

继承（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


C#（针对 Java 开发人员）

[image]

Visual C# 语言概念
C#（针对 Java 开发人员）
另请参见 发送反馈意见

更新：2007 年 11 月

本节的主题提供对 C# 语言和 .NET Framework 的介绍。

本节内容

  • C# 编程语言（针对 Java 开发人员）

  • C# 代码示例（针对 Java 开发人员）

  • C# 应用程序类型（针对 Java 开发人员）

请参见

其他资源

Visual C# 入门


数据库访问（C# 与 Java）

[image]

Visual C# 语言概念
数据库访问（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 和 Java 使用相似的方式访问数据库数据。C# 和 Java 都需要数据库驱动程序来执行实际的数据库操作。另外，两者都需要一个数据库连接、一个要针对数据库连接执行的 SQL 查询以及一个执行查询后产生的结果集。

有关 ADO.NET 的更多信息，请参见：

  • ADO.NET

  • 访问数据 (Visual Studio)

  • 示例代码 (ADO.NET)

有关 .NET Framework 数据库访问类的更多信息，请参见：

  • System.Data.OracleClient

  • System.Data.Odbc

  • System.Data.OleDb

  • 数据集 (ADO.NET)

  • 使用数据集 (ADO.NET)

请参见

概念

C# 编程指南

其他资源

C#（针对 Java 开发人员）


源文件约定（C# 与 Java）

[image]

Visual C# 语言概念
源文件约定（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

包含 C# 类的文件的命名约定与 Java 文件的命名约定有所不同。在 Java 中，所有源文件的扩展名均为 .java。每个源文件均包含一个顶级公共类声明，并且该类的名称必须与文件名匹配。换句话说，使用公共范围声明的类 Customer 必须在名为 Customer.java 的源文件中定义。

C# 源代码用 .cs 扩展名表示。与 Java 不同，C# 源文件可以包含多个顶级公共类声明，并且文件名不必与任何类名称匹配。

顶级声明

在 Java 和 C# 中，源代码的开头为几个按一定顺序排列的顶级声明。Java 和 C# 程序中的声明仅有少许不同。

Java 中的顶级声明

在 Java 中，可以使用 package 关键字将类组合在一起。组合后的打包类必须在源文件第一行可执行代码中使用 package 关键字。如下所示，之后是访问其他包中的类所需的任何导入语句，然后是类声明：

复制代码
package Acme;
import java.io.*;
class Customer
{
    ...
}

C# 中的顶级声明

C# 使用命名空间的概念，通过 namespace 关键字将相关类按逻辑方式组合在一起。这些组合类的作用与 Java 包的作用相似，并且同一个类可以出现在两个不同的命名空间内。若要访问在当前命名空间之外的某个命名空间中定义的类，请使用 using 指令，并在该指令后面加上命名空间名称，如下所示：

C# 复制代码
using System.IO;

namespace Acme
{
    class Customer
    {
        // ...
    }
}

注意，可以在命名空间声明内放入 using 指令，在这种情况下，这些导入的命名空间将成为包含命名空间的一部分。

Java 不允许在同一个源文件中出现多个包。而 C# 允许在一个 .cs 文件中出现多个命名空间，如下所示：

C# 复制代码
namespace AcmeAccounting
{
    public class GetDetails
    {
        // ...
    }
}

namespace AcmeFinance
{
    public class ShowDetails
    {
        // ...
    }
}

完全限定名称和命名空间别名

与 Java 一样，不对命名空间使用 using 引用仍可以访问 .NET Framework 命名空间或用户定义的命名空间中的类，方法是提供类的完全限定名称，例如，上例中的 DataSet 或 AcmeAccounting.GetDetails。

但完全限定名称可能因名称太长而难以处理，在这种情况下，可以使用 using 关键字指定一个简称或别名，以提高代码的可读性。

在下面的代码中，将创建一个别名，以引用由某个虚构公司编写的代码：

C# 复制代码
using DataTier = Acme.SQLCode.Client;

class OutputSales
{
    static void Main()
    {
        int sales = DataTier.GetSales("January");
        System.Console.WriteLine("January's Sales: {0}", sales);
    }
}

注意，在 WriteLine 的语法中，格式字符串中含有 {x}，其中的 x 表示要插入到该位置的值在参数列表中的位置。假设 GetSales 方法返回 500，则应用程序的输出应当为：

January's Sales: 500

预处理指令

与 C 和 C++ 类似，C# 包含预处理指令，这些指令提供以下能力：有条件地跳过源文件中的节、报告错误和警告条件以及描绘源代码的不同区域。使用“预处理指令”一词仅仅是为了与 C 和 C++ 编程语言保持一致，因为 C# 并不包括单独的预处理步骤。有关更多信息，请参见 C# 预处理器指令。

请参见

概念

C# 编程指南

其他资源

C# 编程语言（针对 Java 开发人员）


用户界面开发（C# 与 Java）

[image]

Visual C# 语言概念
用户界面开发（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

可以在 C# 中使用 .NET Framework 的丰富 Windows 窗体组件进行客户端窗体应用程序编程。

Java

大部分 Java 应用程序使用 Abstract Windowing ToolKit (AWT) 或 Swing（包括 AWT 事件模型）来进行窗体编程，而 Swing 使用 AWT 的基础结构。AWT 提供所有基本的 GUI 功能和类。

Java 示例

框架是带有标题和边框的窗口，通常用于添加组件。

复制代码
JFrame aframe = new JFrame();

Component 类是具有图形表示形式的对象，它经常会被扩展；使用继承的方法也经常重写这些方法，如演示代码中的 Shape 组件的 paint 方法。

复制代码
    import java.awt.*;
    import javax.swing.*;

    class aShape extends JComponent {
    public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D)g;

    // Draw the shape.
    }

    public static void main(String[] args) {
    JFrame aframe = new JFrame();
    frame.getContentPane().add(new aShape ());
    int frameWidth = 300;
    int frameHeight = 300;
    frame.setSize(frameWidth, frameHeight);
    frame.setVisible(true);
    }
}

可以注册侦听器来侦听组件的操作事件，从而处理事件。例如，按下并释放某个按钮时，AWT 通过调用此按钮上的 processEvent 将一个 ActionEvent 实例发送到该按钮。此按钮的 processEvent 方法接收此按钮的所有事件，它通过调用自身的 processActionEvent 方法来继续传递操作事件。后一个方法将继续传递操作事件，将它传递给那些已注册为侦听此按钮生成的操作事件的操作侦听器。

C#

在 C# 中，.NET Framework 的 System.Windows.Forms 命名空间和类为 Windows 窗体开发提供一套全面的组件。例如，下面的代码使用 Label、Button 和 MenuStrip。

C# 示例

直接从 Form 类派生，如下所示：

C# 复制代码
public partial class Form1 : System.Windows.Forms.Form

添加您的组件：

C# 复制代码
this.button1 = new System.Windows.Forms.Button();
this.Controls.Add(this.button1);

下面的代码演示如何将标签、按钮和菜单添加到窗体。

C# 复制代码
namespace WindowsFormApp
{
    public partial class Form1 : System.Windows.Forms.Form
    {
        private System.ComponentModel.Container components = null;

        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.MenuStrip menu1;

        public Form1()
        {
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();

            this.label1 = new System.Windows.Forms.Label();
            this.Controls.Add(this.label1);

            this.button1 = new System.Windows.Forms.Button();
            this.Controls.Add(this.button1);

            this.menu1 = new System.Windows.Forms.MenuStrip();
            this.Controls.Add(this.menu1);
        }

        static void Main()
        {
            System.Windows.Forms.Application.Run(new Form1());
        }
    }
}

与 Java 类似，在 C# 中可以注册侦听器来侦听组件的事件。例如，按下并释放按钮时，运行时会将一个 Click 事件传递给所有已注册为侦听该按钮的 Click 事件的侦听器。

C# 复制代码
private void button1_Click(object sender, System.EventArgs e)
{
}

例如，可以使用下面的代码来注册 button1_Click，以处理 Button 的实例（称为 button1）的 Click 事件。

C# 复制代码
// this code can go in InitializeComponent()
button1.Click += button1_Click;

有关更多信息，请参见创建 ASP.NET Web 应用程序 (Visual C#)。

有关 Forms 类的更多信息，请参见根据功能列出的 Windows 窗体控件和 System.Windows.Forms。

请参见

概念

C# 编程指南
设计用户界面 (Visual C#)

其他资源

C#（针对 Java 开发人员）


C# 代码示例（针对 Java 开发人员）

[image]

Visual C# 语言概念
C# 代码示例（针对 Java 开发人员）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 是一种精确、简单、类型安全、面向对象的语言，它使程序员可以构建各种各样的应用程序。与 .NET Framework 组合后，Visual C# 使用户可以创建 Windows 应用程序、Web 服务、数据库工具、组件、控件以及其他内容。

本节内容

控制台应用程序开发（C# 与 Java）

描述 C# 中的控制台应用程序（针对 Java 开发人员）。

文件 I/O（C# 与 Java）

描述 C# 中针对 Java 开发人员的文件 I/O 操作，并链接到 XML 类以供 XML 文件进行加载和保存。

数据库访问（C# 与 Java）

比较两种语言中的数据访问。

用户界面开发（C# 与 Java）

描述用 C# 创建 Windows 窗体应用程序（针对 Java 开发人员）。

资源管理（C# 与 Java）

提供关于用 C# 编写的 Windows 资源文件的主题（针对 Java 开发人员）。

Web 服务应用程序（C# 与 Java）

描述用 C# 进行 XML Web 服务应用程序开发（针对 Java 开发人员）。

移动设备和数据（C# 与 Java）

描述使用 C#、.NET Framework Windows 窗体和 SQL Server 与数据库交互（针对 Java 开发人员）。

请参见

概念

C# 编程指南

其他资源

迁移到 Visual C#
用 Visual C# 编写应用程序


控制台应用程序开发（C# 与 Java）

[image]

Visual C# 语言概念
控制台应用程序开发（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

控制台应用程序无需使用任何图形用户界面即可读取标准输入和输出 (I/O) 中的数据并向其写入数据。Java 和 C# 的控制台应用程序结构十分相似，而且控制台 I/O 所使用的类也十分相似。

尽管类和方法签名的详细信息可能不同，但 C# 和 Java 都使用类似的概念来执行控制台 I/O 操作。对于控制台应用程序及相关的控制台读取和写入方法，C# 和 Java 均使用主入口点这一概念。在 C# 中，主入口点为 Main，而在 Java 中，主入口点为 main。

Java“Hello World”示例

在下面的 Java 示例代码中，static void main() 例程接受一个对应用程序参数的 String 引用，然后 main 例程将一行输出到控制台。

复制代码
/*  A Java Hello World Console Application */
public class Hello {
    public static void main (String args[]) {
        System.out.println ("Hello World");
    }
}

C#“Hello World”示例

在下面的 C# 示例代码中，static void Main() 例程接受一个对应用程序参数的 string 引用，然后 Main 例程将一行代码写入控制台。

C# 复制代码
// A C# Hello World Console Application.
public class Hello
{
    static void Main()
    {
        System.Console.WriteLine("Hello World");
    }
}

编译代码

如果您使用的是 Visual C#，按 F5 可以一步编译并运行代码。如果您使用的是命令行并且文件名为“Hello.cs”，则可以如下所示调用 C# 编译器：

csc Hello.cs

更多信息

有关创建控制台应用程序的更多信息，请参见 创建控制台应用程序 (Visual C#)。

有关 .NET Framework 控制台类的更多信息，请参见：

  • Console 类、WriteLine 方法和 ReadLine 方法。

  • 按类别列出的 C# 编译器选项.

请参见

概念

C# 编程指南
Main() 和命令行参数（C# 编程指南）

其他资源

C#（针对 Java 开发人员）


属性（C# 与 Java）

[image]

Visual C# 语言概念
属性（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 中，属性是类、结构或接口的命名成员，它提供了一种通过 get 和 set 访问器方法访问私有字段的巧妙方法。

下面的代码示例为 Animal 类声明 Species 属性，该属性将对私有变量 name 的访问抽象化：

C# 复制代码
public class Animal
{
    private string name;

    public string Species
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}

通常，属性的名称与它所访问的内部成员的名称相同，但是第一个字母要大写，如以上示例中的 Name，否则内部成员将带一个 _ 前缀。此外，请注意 set 访问器中所使用的隐式参数 value；该参数具有基础成员变量的类型。

实际上，访问器在内部表示为 get_X() 和 set_X() 方法，以保持与基于 .NET Framework 的语言（它们不支持访问器）的兼容性。定义属性后，可以非常容易地获取或设置其值：

C# 复制代码
class TestAnimal
{
    static void Main()
    {
        Animal animal = new Animal();
        animal.Species = "Lion";                   // set accessor
        System.Console.WriteLine(animal.Species);  // get accessor

    }
}

如果一个属性仅有 get 访问器，则它为只读属性。如果它仅有 set 访问器，则为只写属性。如果它同时具有这两种访问器，则为读写属性。

请参见

概念

C# 编程指南

参考

属性（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


C# 应用程序类型（针对 Java 开发人员）

[image]

Visual C# 语言概念
C# 应用程序类型（针对 Java 开发人员）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 应用程序类型包括 Windows 控制台应用程序、Windows 窗体应用程序、ASP.NET Web 应用程序、ASP.NET Web 服务应用程序、智能设备应用程序、ActiveX 应用程序以及安装和部署应用程序。

控制台应用程序

控制台应用程序使用标准命令行输入和输出而不是窗体进行输入和输出。控制台应用程序使用 System.IO 类处理输入和输出。您可以在方法之前加上类名（如 System.IO.Console.WriteLine()），也可以在程序的起始处添加 using 语句。使用 Visual Studio 及其他开发环境（包括任何文本编辑器，如“记事本”）可以很容易地创建控制台应用程序。有关更多信息，请参见 Visual Studio 简介、创建控制台应用程序 (Visual C#)、Hello World -- 您的第一个程序（C# 编程指南）或 Main() 和命令行参数（C# 编程指南）。

窗体应用程序

窗体应用程序采用的是用户熟悉的 Windows 图形用户界面，其中包含各种输入控件（如按钮和列表框）。窗体应用程序使用 System.Windows.Forms 命名空间中的类。使用 Visual Studio 及其他开发环境（包括任何文本编辑器，如“记事本”）可以很容易地创建窗体应用程序。有关如何创建 Windows 应用程序的更多信息，请参见如何：创建 Windows 应用程序项目、创建 ASP.NET Web 应用程序 (Visual C#)或创建 ASP.NET Web 应用程序 (Visual C#)。

ASP.NET Web 应用程序

ASP.NET 应用程序是显示在 Web 浏览器中而不是控制台上或窗体应用程序中的 Web 应用程序。ASP.NET 应用程序使用 System.Web 命名空间和诸如 System.Web.UI 这样的类来处理浏览器的输入和输出。您可以在方法之前加上类名（如 using System.Web.UI.HtmlControls; ），也可以在程序的起始处添加 using 语句。使用 Visual Studio 及其他开发环境（包括任何文本编辑器，如“记事本”）可以很容易地创建 ASP.NET 应用程序。有关如何创建 ASP.NET 应用程序的更多信息，请参见 Visual Web Developer。有关如何使用 Visual Studio 创建 ASP.NET 应用程序的更多信息，请参见应用程序关系图上的 ASP.NET 应用程序概述。有关 ASP.NET 的更多信息，请参见 .NET Framework 中的 ASP.NET Web 应用程序。有关如何调试 ASP.NET 应用程序的更多信息，请参见调试 ASP.NET 和 AJAX 应用程序和调试准备：ASP.NET Web 应用程序。

ASP.NET Web 服务应用程序

可使用 URL、HTTP 和 XML 访问 ASP.NET Web 服务，以便在任何平台上运行的、使用任何语言编写的程序都可以访问 ASP.NET Web 服务。ASP.NET Web 服务应用程序可以显示在控制台上和窗体中，或者显示在 Web 浏览器或智能设备中。ASP.NET Web 服务应用程序使用 System.Web 和 System.Web.Services 命名空间和类。使用 Visual Studio 及其他开发环境（包括任何文本编辑器，如“记事本”）可以很容易地创建 ASP.NET 应用程序 Web 服务。有关如何创建 Web 服务应用程序的更多信息，请参见访问和显示数据 (Visual C#) 和如何：创建 ASP.NET Web 服务项目。有关如何向现有项目添加 ASP.NET Web 服务的更多信息，请参见如何：在托管代码中向现有 Web 项目添加 Web 服务。有关 ASP.NET Web 服务的更多信息，请参见演练：在 Visual Web Developer 中创建和使用 ASP.NET Web 服务和演练：使用 Visual Basic 或 Visual C# 创建 Web 服务。有关如何调试 ASP.NET Web 服务应用程序的更多信息，请参见调试准备：XML Web services 项目。

其他与 ASP.NET Web 服务相关的主题包括：

  • 生成 XML Web services 客户端

  • XML Web services 说明

  • 如何：创建 Web 服务方法

  • 演练：使用 Visual Basic 或 Visual C# 创建 Web 服务

  • 演练：在 Visual Web Developer 中创建和使用 ASP.NET Web 服务

智能设备应用程序

智能设备应用程序运行在移动设备（如 PDA 和 Smartphone）上。智能设备应用程序可以是控制台应用程序、Windows 窗体应用程序或 ASP.NET 和 Web 客户端，并显示在控制台上、窗体中或 Web 浏览器中。智能设备应用程序使用与桌面应用程序相同的命名空间和类。不过，智能设备应用程序使用 Compact Framework 而不是 .NET Framework。有些版本的开发环境可能支持在移动设备上开发某些或所有类型的 C# 应用程序。

其他与 ASP.NET Web 服务相关的主题包括：

  • 智能设备开发

  • ASP.NET 移动控件快速入门

  • ASP.NET 移动控件示例

ActiveX 控件

与 Java Bean 类似，ActiveX 控件也是一个组件，并且与“OLE 对象”和组件对象模型 (COM) 对象等效。简而言之，ActiveX 控件就是一个支持 IUnknown 接口的 COM 对象。ActiveX 控件是用于开发在从 Internet Explorer 到软件开发工具及最终用户生产工具等多种不同容器内重用的可编程软件组件的主要体系结构。有关 ActiveX 控件的更多信息，请参见：

  • ActiveX 简介

  • 将 ActiveX 控件打包

  • 使用 ActiveX 控件使 Web 自动执行操作

  • 设计安全的 ActiveX 控件

  • 如何：调试 ActiveX 控件

安装和部署应用程序

Visual Studio 为部署桌面、Web 及智能设备安装和部署项目提供模板。不同版本的开发环境可能支持在桌面、Web 及移动设备上安装和部署某些或所有类型的 C# 应用程序。有关更多信息，请参见：

  • 部署 .NET Framework 应用程序.

  • 打包设备解决方案以便进行部署

  • 演练：打包智能设备解决方案以便进行部署

  • “Cabinet Packaging”（压缩打包）

相关主题

  • .NET Framework 网络操作基础

  • Windows 窗体应用程序基础知识

  • .NET Framework 示例

请参见

概念

C# 编程指南

其他资源

迁移到 Visual C#
C# 代码示例（针对 Java 开发人员）
C# 编程语言（针对 Java 开发人员）
Visual C# 入门
使用 Visual C# IDE


异常（C# 与 Java）

[image]

Visual C# 语言概念
异常（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 中的异常处理与 Java 中的异常处理非常相似。

只要在程序执行过程中出现错误，.NET Framework 公共语言运行时 (CLR) 就会创建一个 Exception 对象详细描述此错误。在 .NET Framework 中，Exception 为所有异常类的基类。从 Exception 类派生的异常分为两种类别：SystemException 和 ApplicationException。System 命名空间中的所有类型从 SystemException 派生，而用户定义的异常应从 ApplicationException 派生，以便区分运算时错误和应用程序错误。一些常见的 System 异常包括：

  • IndexOutOfRangeException：使用了大于数组或集合大小的索引。

  • NullReferenceException：在将引用设置为有效实例之前使用了引用的属性或方法。

  • ArithmeticException：操作将导致上溢或下溢。

  • FormatException：参数或操作数格式不正确。

与 Java 一样，如果代码可能导致异常，则将此代码置于 try 块内。紧跟在该块之后的一个或多个 catch 块提供错误处理。此外还可以对不管是否会引发异常都要执行的任何代码使用 finally 块。有关更多信息，请参见 try-catch（C# 参考） 和 try-catch-finally（C# 参考）。

使用多个 catch 块时，捕捉到的异常必须按照普遍性递增的顺序放置，因为只有与引发的异常相匹配的第一个 catch 块将被执行。C# 编译器对此作了强制规定，而 Java 编译器则未作强制规定。

此外，C# 不像 Java 那样需要对 catch 块使用参数；在没有参数的情况下，catch 块也适用于 Exception 类。

例如，读取文件时可能遇到 FileNotFoundException 或 IOException 异常，并且您希望将含义更明确的 FileNotFoundException 处理程序放置在第一位，如以下代码所示：

C# 复制代码
try
{
    // code to open and read a file
}
catch (System.IO.FileNotFoundException e)
{
    // handle the file not found exception first
}
catch (System.IO.IOException e)
{
    // handle any other IO exceptions second
}
catch
{
    // a catch block without a parameter
    // handle all other exceptions last
}
finally
{
    // this is executed whether or not an exception occurs
    // use to release any external resources
}

通过从 Exception 派生，可以创建您自己的异常类。例如，下面的代码创建一个可能引发的 InvalidDepartmentException 类（例如，如果为新的 Employee 提供的部门无效）。用户定义的异常的类构造函数使用 base 关键字来调用基类构造函数，并发送相应的消息：

C# 复制代码
public class InvalidDepartmentException : System.Exception
{
    public InvalidDepartmentException(string department) : base("Invalid Department: " + department)
    {
    }
}

接着可以使用以下代码引发一个异常：

C# 复制代码
class Employee
{
    private string department;

    public Employee(string department)
    {
        if (department == "Sales" || department == "Marketing")
        {
            this.department = department;
        }
        else
        {
            throw new InvalidDepartmentException(department);
        }
    }
}

C# 不支持选中的异常。在 Java 中，这些选中的异常使用 throws 关键字进行声明，从而指定方法可以引发必须由调用代码处理的特定类型异常。

请参见

概念

C# 编程指南

参考

异常和异常处理（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


循环语句（C# 与 Java）

[image]

Visual C# 语言概念
循环语句（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

循环语句重复指定的代码块，直到满足指定的条件。

for 循环

在 C# 和 Java 中，for 循环的语法和操作相同：

C# 复制代码
for (int i = 0; i<=9; i++)
{
    System.Console.WriteLine(i);
}

foreach 循环

C# 引入了一种称为 foreach 循环的新循环类型，它与 Visual Basic 的 For Each 相似。对于支持 IEnumerable 接口的容器类（如数组），可以使用 foreach 循环来循环访问其中的每一项。下面的代码演示如何使用 foreach 语句输出数组的内容：

C# 复制代码
static void Main()
{
    string[] arr= new string[] {"Jan", "Feb", "Mar"};

    foreach (string s in arr)
    {
        System.Console.WriteLine(s);
    }
}

有关更多信息，请参见 数组（C# 与 Java）。

while 和 do...while 循环

在这两种语言中，while 和 do...while 语句的语法和操作是相同的：

C# 复制代码
while (condition)
{
    // statements
}
C# 复制代码
do
{
    // statements
}
while(condition);  // Don't forget the trailing ; in do...while loops

请参见

概念

C# 编程指南

其他资源

C# 编程语言（针对 Java 开发人员）


文件 I/O（C# 与 Java）

[image]

Visual C# 语言概念
文件 I/O（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

尽管类和方法签名的详细信息可能不同，但 C# 和 Java 在执行 I/O 操作时使用的概念类似。C# 和 Java 均有 file 类的概念和关联的文件读写方法。存在相似的文档对象模型 (DOM)，用于处理 XML 内容。

Java 文件操作示例

在 Java 中，可以使用 File 对象执行基本的文件 I/O 操作，例如创建、打开、关闭、读取和写入文件。例如，可以使用 File 类的方法执行文件 I/O 操作，例如使用 File 类的 createNewFile 或 delete 方法创建或删除文件。可以使用 BufferedReader 和 BufferedWriter 类读写文件的内容。

下面的代码示例演示如何创建新文件、删除文件、从文件中读取文本以及写入文件。

复制代码
// Java example code to create a new file
    try
    {
        File file = new File("path and file_name");
        boolean success = file.createNewFile();
    }
    catch (IOException e)    {   }

// Java example code to delete a file.
    try
    {
        File file = new File("path and file_name");
        boolean success = file.delete();
    }
    catch (IOException e)    {    }

// Java example code to read text from a file.
    try
    {
        BufferedReader infile = new BufferedReader(new FileReader("path and file_name "));
        String str;
        while ((str = in.readLine()) != null)
        {
            process(str);
        }
        infile.close();
    }
    catch (IOException e)
    {
        // Exceptions ignored.
    }

// Java example code to writing to a file.
    try
    {
        BufferedWriter outfile =
          new BufferedWriter(new FileWriter("path and file_name "));
        outfile.write("a string");
        outfile.close();
    }
    catch (IOException e)    {    }

C# 文件操作示例

要在 C# 中执行一个文件 I/O 操作，可以使用同样熟悉的基本步骤，使用 .NET Framework 等效类和方法执行创建、打开、关闭、读取和写入操作。例如，可以使用 .NET Framework 的 File 类的方法执行文件 I/O 操作。例如，可以使用 Exists 方法检查文件是否存在。可以使用 Create 方法来创建文件，在此过程中可以选择覆盖现有文件（如下面的代码示例所示），并且可以使用 FileStream 类和 BufferedStream 对象来执行读写操作。

下面的代码示例演示如何删除文件、创建文件、写入文件和读取文件。

C# 复制代码
// sample C# code for basic file I/O operations
// exceptions ignored for code simplicity

class TestFileIO
{
    static void Main()
    {
        string fileName = "test.txt";  // a sample file name

        // Delete the file if it exists.
        if (System.IO.File.Exists(fileName))
        {
            System.IO.File.Delete(fileName);
        }

        // Create the file.
        using (System.IO.FileStream fs = System.IO.File.Create(fileName, 1024))
        {
            // Add some information to the file.
            byte[] info = new System.Text.UTF8Encoding(true).GetBytes("This is some text in the file.");
            fs.Write(info, 0, info.Length);
        }

        // Open the file and read it back.
        using (System.IO.StreamReader sr = System.IO.File.OpenText(fileName))
        {
            string s = "";
            while ((s = sr.ReadLine()) != null)
            {
                System.Console.WriteLine(s);
            }
        }
    }
}

相关章节

可用于创建、读取和写入流的 .NET Framework 类包括 StreamReader 和 StreamWriter 类。可用于处理文件的其他 .NET Framework 类包括：

  • FileAccess 和 FileAttribute 类。

  • Directory 和 DirectoryInfo、Path、FileInfo 和 DriveInfo 类。

  • BinaryReader 和 BinaryWriter 类。

  • StringReader 和 StringWriter 类。

  • TextReader 和 TextWriter 类

  • XmlReader 和 XmlWriter 类。

  • ToBase64Transform 类。

  • FileStream、BufferedStream 和 MemoryStream 类。

请参见

概念

C# 编程指南
异步文件 I/O

其他资源

C#（针对 Java 开发人员）


C# 编程语言（针对 Java 开发人员）

[image]

Visual C# 语言概念
C# 编程语言（针对 Java 开发人员）
发送反馈意见

更新：2007 年 11 月

本节讨论 C# 和 Java 编程语言之间的相似点和差异。

本节内容

源文件约定（C# 与 Java）

数据类型（C# 与 Java）

运算符（C# 与 Java）

流控制（C# 与 Java）

循环语句（C# 与 Java）

类基础（C# 与 Java）

Main () 方法和其他方法（C# 与 Java）

使用不确定数目的参数（C# 与 Java）

属性（C# 与 Java）

Struct（C# 与 Java）

数组

继承与派生类（C# 与 Java）

事件

运算符重载（C# 与 Java）

异常（C# 与 Java）

高级 C# 技术（C# 与 Java）

垃圾回收（C# 与 Java）

安全代码和不安全的代码（C# 与 Java）

摘要（C# 与 Java）

相关章节

C# 编程指南

Visual C#

迁移到 Visual C#


Web 服务应用程序（C# 与 Java）

[image]

Visual C# 语言概念
Web 服务应用程序（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

.NET Framework 为通过 Web 服务进行互操作提供了广泛的支持。在 C# 中使用 .NET Framework、Visual Studio 和 ASP.NET 创建一个 Web 服务就像创建一个 Web 服务项目并将属性 WebMethod 添加到您要公开的任何公用方法一样简单。

Java

在 Java 中您可以使用 Web 服务包来实现应用程序，如 Java Web Services Developer Pack 或 Apache SOAP。例如，在 Java 中可以使用下面的步骤创建一个 Web 服务和 Apache SOAP。

在 Java 中使用 Apache SOAP 创建 Web 服务

  1. 如下所示编写一个 Web 服务方法：

    复制代码
    public class HelloWorld
{

    public String sayHelloWorld()
    {
        return "HelloWorld ";
    }
}

  2. 创建 Apache SOAP 部署说明符。该说明符可能如下所示：

    复制代码
    <dd:service xmlns:dd="http://xml.apache.org/xml-soap/deployment"
           id="urn:HelloWorld">

   <dd:provider type="java"
               scope="Application"
               methods="sayHelloWorld">

    <dd:java class="HelloWorld" static="false" />

  </dd:provider>

<dd:faultListener>org.apache.soap.server.DOMFaultListener</dd:faultListener>

  <dd:mappings />

</dd:service>

  3. 编译 HelloWorld 类并将它移到 Web 服务器的类路径。

  4. 使用命令行工具部署 Web 服务。

C#

在 C# 中使用 .NET Framework 类和 Visual Studio IDE 创建 Web 服务更为简单。

在 C# 中使用 .NET Framework 和 Visual Studio 创建 Web 服务

  1. 在 Visual Studio 中创建 Web 服务应用程序。有关更多信息，请参见 C# 应用程序类型（针对 Java 开发人员）。下面是生成的代码。

    C# 复制代码
    using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
    public Service () {

    }

    [WebMethod]
    public string HelloWorld() {
        return "Hello World";
    }

}

  2. 查找 [WebService(Namespace = "http://tempuri.org/")] 行，将 "http://tempuri.org/" 更改为 "http://tempuri.org/"。

运行 C# Web 服务

  1. 编译和运行服务。在 Web 浏览器中键入“http://localhost/WebSite1/Service.asmx”，其中 localhost 是 IIS Web 服务器的名称，而 Service 是服务的名称（在本例中为 Service）。

  2. 输出为：

    复制代码
    The following operations are supported. For a formal definition, please review the Service Description.
HelloWorld

  3. 单击 HelloWorld 链接以调用 Service1 的 HelloWorld 方法。输出为：

    复制代码
    Click here for a complete list of operations.
HelloWorld
Test
To test the operation using the HTTP POST protocol, click the 'Invoke' button.

SOAP 1.1
...
SOAP 1.2
...
HTTP POST
...

  4. 单击“调用”按钮以调用 Service1 的 HelloWorld 方法。输出为：

    复制代码
    <?xml version="1.0" encoding="utf-8" ?>
  <string xmlns="http://HowToDevelopWebServicesTest/">Hello World</string>

有关 Web 服务的更多信息，请参见：

  • 生成 XML Web services 客户端

  • XML Web services 说明

  • 如何：创建 Web 服务方法

  • 演练：使用 Visual Basic 或 Visual C# 创建 Web 服务

  • 演练：在 Visual Web Developer 中创建和使用 ASP.NET Web 服务

请参见

概念

C# 编程指南

其他资源

C#（针对 Java 开发人员）


Struct（C# 与 Java）

[image]

Visual C# 语言概念
Struct（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 支持 struct 关键字，它也是一个起源于 C 但在 Java 中不可用的项。可以将 struct 想像为一个轻量类。虽然 structs 可包含构造函数、常量、字段、方法、属性、索引器、运算符和嵌套类型，但是多数情况下，它们仅用于封装由相关字段组成的组。因为结构是值类型，所以它们的分配效率要比类略高些。structs 与类的不同之处在于，它们既不能是抽象的，也不支持实现继承。

在下面的示例中，用 new 关键字初始化 struct（调用默认的无参数构造函数），然后设置实例的成员。

C# 复制代码
public struct Customer
{
    public int ID;
    public string Name;

    public Customer(int customerID, string customerName)
    {
        ID = customerID;
        Name = customerName;
    }
}

class TestCustomer
{
    static void Main()
    {
        Customer c1 = new Customer();  //using the default constructor

        System.Console.WriteLine("Struct values before initialization:");
        System.Console.WriteLine("ID = {0}, Name = {1}", c1.ID, c1.Name);
        System.Console.WriteLine();

        c1.ID = 100;
        c1.Name = "Robert";

        System.Console.WriteLine("Struct values after initialization:");
        System.Console.WriteLine("ID = {0}, Name = {1}", c1.ID, c1.Name);
    }
}

输出

编译和运行上述代码时，其输出结果显示 struct 变量已按默认值初始化。int 变量初始化为 0，string 变量初始化为空字符串：

Struct values before initialization:

ID = 0, Name =

Struct values after initialization:

ID = 100, Name = Robert

请参见

任务

“结构”示例

概念

C# 编程指南

参考

结构（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


C# 中的新增功能

[image]

Visual C# 开发环境
C# 中的新增功能
另请参见 发送反馈意见

更新： 2008 年 7 月

Visual C# 2008 Service Pack 1 中的新增功能

C# 语言

功能 | 说明
多种编译器改进 | 已对 C# 编译器进行了多处改进，以便消除不符合语言规范的地方。其中一些改进是重大更改，而其他改进则只是软件更新或增强。有关重大更改的更多信息，请参见 Visual C# 2008 重大更改。有关在 Service Pack 1 中修复的其他 Bug 的更多信息，请参见 Visual Studio 2008 Service Pack 1 Beta for Visual C# 更改及已修复问题列表。

Visual C# IDE

功能

说明

实时语义错误

Visual C# 2008 Service Pack 1 引入了一个新功能 - 实时语义错误，该功能可提供更为完整的一组关于代码的错误信息。此功能检查以前仅在生成后报告的表达式级错误。在您编写代码时，该功能用红色波浪下划线突出显示错误。有关波浪下划线的更多信息，请参见编辑代码 (Visual C#)。

LINQ 查询调试

LINQ 查询的调试在 Service Pack 1 中得到了极大的增强。

  • 现在，当您逐句通过查询表达式中的各行时，可以在“监视”窗口和数据提示中查看范围变量。

  • 调试器不再隐藏匿名类型的名称，并允许它们在强制转换和实例化中显示。这样，便可以在调试会话过程中创建匿名类型的临时实例。强制转换为匿名类型及从匿名类型强制转换则可以正确计算。

  • 为“添加至监视”生成的字符串已经改进，而且可以生成表达式，与在 Visual Studio 2008 的最初发行版中比较，这些表达式会更加频繁地计算。

有关更多信息，请参见位于 Microsoft 网站上的 Sree's ventures in code space（Sree 在代码空间的冒险行为）

为解决方案中的所有文件显示任务列表注释

在 Visual Studio 的早期版本中，仅用打开文件内的任务注释填充任务列表。在 Visual Studio 2008 Service Pack 1 中，C# 集成开发环境 (IDE) 显示解决方案中所有打开的及关闭的文件中的任务注释。有关更多信息，请参见如何：创建任务列表注释。

重命名 Windows Presentation Foundation 项目中的重构

Visual Studio 2008 Service Pack 1 允许您使用重命名重构功能对 XAML 中定义的引用进行重命名。有关重命名重构的更多信息，请参见重命名。

LINQ

功能 | 说明
对 [M:Enumerable.Cast`1] 方法的改进 | Visual C# 2008 Service Pack 1 通过禁用基元值转换和显式定义的用户转换来提高 Enumerable.Cast<T> 方法的性能。从 int 数据类型到 long 数据类型的转换便是一个基元值转换的示例。
[M:Enumerable.Where`1] 和 [M:Enumerable.Select`1] 方法的性能提高 | Visual C# 2008 Service Pack 1 提高了 Where 标准查询运算符和 Select 标准查询运算符的性能。

Visual C# 2008 最初发行版中的新增功能

C# 3.0 语言

C# 3.0 语言和编译器引入了多种新的语言功能。这些新的语言构造可以分别用在各种上下文中，并且可以共同完成语言集成查询 (LINQ)。有关 LINQ 的更多信息，请参见 The LINQ Project（LINQ 项目）。

下表列出了新增的 C# 3.0 语言功能：

功能

说明

隐式类型的本地变量和数组

在与本地变量一起使用时，var 关键字指示编译器根据初始化语句右侧的表达式推断变量或数组元素的类型。

对象初始值设定项

支持无需显式调用构造函数即可进行对象初始化。

集合初始值设定项

支持使用初始化列表而不是对 Add 或其他方法的特定调用来初始化集合。

扩展方法

使用静态方法扩展现有类，这些静态方法可以通过实例方法语法进行调用。

匿名类型

允许动态创建可以添加到集合中并且可以使用 var 进行访问的未命名结构化类型。

Lambda 表达式

支持带有可绑定到委托或表达式树的输入参数的内联表达式。另请参见匿名函数（C# 编程指南）。

查询关键字

用来指定查询表达式中子句的关键字：

  • from 子句

  • where 子句（可选）

  • 排序子句（可选）

  • join 子句（可选）

  • select 或 group 子句

  • into 子句（可选）

自动实现的属性

支持使用简化的语法声明属性。

分部方法定义

分部类型现在可以包含分部方法。

C# 3.0 编译器

/win32Manifest 和 /noWin32Manifest 编译器开关。

这些新的编译器开关用于为在 Windows Vista 上运行的程序指定请求的执行级别。

Visual C# IDE

功能

说明

多目标

Visual Studio 2008 允许您为项目指定 .NET Framework 的版本（即 .NET Framework 2.0、3.0 或 3.5）。应用程序的 .NET Framework 目标是在计算机上运行此应用程序时所需的 .NET Framework 的版本。有关更多信息，请参见以特定的 .NET Framework 为目标。

新的项目类型和模板

为 Windows Presentation Foundation、Windows Communication Foundation 和 Web 项目提供了多个新的项目模板。有关更多信息，请参见 Visual C# 版本中的项目模板和 Visual Studio 中的默认项目模板。

对于 C# 3.0 的 IntelliSense 支持

Visual C# 代码编辑器提供了语句结束和快速信息功能，以支持 C# 3.0 中的下列新语言构造：

  • 隐式类型的局部变量

  • 查询表达式

  • 扩展方法

  • 对象/集合初始值设定项

  • 匿名类型

  • Lambda 表达式

  • 分部方法

有关更多信息，请参见 Visual C# IntelliSense。

对 C# 3.0 的重构支持

为了支持下列新语言构造，已经更新了重构功能“重命名”、“签名更改”、“提取方法”和“将局部变量提升为参数”：

  • 查询表达式

  • 扩展方法

  • Lambda 表达式

此外，重构功能还提供了新的选项和警告，以便帮助防止由于重构操作而造成意外的后果。有关更多信息，请参见重构。

设置代码格式

代码编辑器为包括查询表达式在内的多个新 C# 3.0 语言构造提供了格式设置选项。有关更多信息，请参见“选项”对话框 ->“文本编辑器”->“C#”->“格式设置”。

组织 Using 语句

Visual C# 代码编辑器提供了简便方法用来对 using 和 extern 声明进行排序和移除没有使用的声明。

请参见

其他资源

语言集成查询 (LINQ)
Is C# becoming a functional language?（C# 正在成为一种功能丰富的语言吗？）
.NET Framework 3.5 中新增的库类

修订记录

日期 | 历史记录 | 原因
2008 年 7 月 | 增加了有关 Visual C# 2008 Service Pack 1 的一节。 | SP1 功能更改。


运算符（C# 与 Java）

[image]

Visual C# 语言概念
运算符（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 提供 Java 支持的所有适用的运算符，如下表所示。在表的最后，您会看到在 C# 中可用但 Java 中没有的一些新运算符：

类别 | 符号
一元 | ++--+-!~()
乘法 | */%
加法 | + -
移位 | << >>
关系 | < > <= >= instanceof
相等 | == !=
逻辑“与” | &
逻辑 XOR | ^
逻辑“或” | |
条件 AND | &&
条件 OR | ||
条件运算 | ? :
赋值 | = *= /= %= += -= <<= >>= &= ^= |=
操作数类型 | typeof
操作数大小 | sizeof
执行溢出检查 | checked
取消溢出检查 | unchecked

唯一不能在 C# 中使用的 Java 运算符是移位运算符 (>>>)。因为 Java 中缺少无符号的变量，所以需要这一运算符处理需要向右移位以在最高有效比特位中插入 1 这种情况。

C# 支持无符号变量，因此，C# 只需要标准的 >> 运算符。此运算符根据操作数是否有符号会产生不同的结果。将一个无符号数向右移位会在最高有效比特位中插入 0，而将一个有符号数向右移位会复制前面的最高有效比特位。

Checked 和 Unchecked 运算符

如果结果大小比分配给所用数据类型的位数大很多，算术运算将导致溢出。对给定的整数算术运算使用 checked 和 unchecked 关键字可以检查或忽略此类溢出。如果表达式是一个使用 checked 的常数表达式，就会在编译时产生错误。

下面这个简单的示例演示了这些运算符：

C# 复制代码
class TestCheckedAndUnchecked
{
    static void Main()
    {
        short a = 10000;
        short b = 10000;

        short c = (short)(a * b);                     // unchecked by default
        short d = unchecked((short)(10000 * 10000));  // unchecked
        short e = checked((short)(a * b));            // checked - run-time error

        System.Console.WriteLine(10000 * 10000);  // 100000000
        System.Console.WriteLine(c);              // -7936
        System.Console.WriteLine(d);              // -7936
        System.Console.WriteLine(e);              // no result
    }
}

在这段代码中，unchecked 运算符绕过编译时错误，否则下列语句可能会导致编译时错误：

C# 复制代码
short d = unchecked((short)(10000 * 10000));  // unchecked

下面的表达式在默认情况下 unchecked，因此值溢出后无提示：

C# 复制代码
short c = (short)(a * b);                     // unchecked by default

我们可以在运行时使用 checked 运算符强制检查表达式是否发生溢出：

C# 复制代码
short e = checked((short)(a * b));            // checked - run-time error

将前两个值赋给 d 和 c，会在程序运行时无提示地溢出值 -7936，但是在尝试相乘以获取使用 checked() 的 e 的值时，此程序将引发 OverflowException。

说明：

也可以通过使用命令行编译器开关 (/checked) 控制是否检查代码块的算术溢出，或直接在 Visual Studio 中基于每个项目进行控制。

请参见

概念

C# 编程指南

参考

C# 运算符

其他资源

C# 编程语言（针对 Java 开发人员）


资源管理（C# 与 Java）

[image]

Visual C# 语言概念
资源管理（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C# 中，使用 Visual Studio 管理资源变得更加简单。

Java

Java 应用程序通常与应用程序的各种资源（例如类文件、声音文件和图像文件）一起绑定在 JAR 文件中。您很可能使用 JBuilder 或 Eclipse 来管理 JAR 文件，其管理方式与 Visual Studio 管理解决方案和项目的方式几乎相同。

C#

在 C# 项目中，可以直接从 Visual Studio 的解决方案资源管理器中打开资源。

也可以使用 图像编辑器 和 二进制编辑器 处理托管项目中的资源文件。

有关如何向托管项目添加资源的更多信息，请参见：

  • 添加和编辑资源 (Visual C#)

  • 演练：本地化 Windows 窗体

  • 演练：将本地化资源用于 ASP.NET

可以像读取外部内容或嵌入资源一样读取应用程序中的这些资源。例如，下面的代码行使用 System.Reflection 命名空间中的类以及 Assembly 这样的类从程序集读取嵌入的资源文件。此例中的文件为 assemblyname.file.ext。

C# 复制代码
static void Main()
{
    System.Reflection.Assembly asm =
        System.Reflection.Assembly.GetExecutingAssembly();

    System.Drawing.Bitmap tiles = new System.Drawing.Bitmap
        (asm.GetManifestResourceStream("assemblyname.file.ext"));
}

  • 有关更多信息，请参见反射（C# 编程指南）。

  • 有关应用程序资源的更多信息，请参见管理应用程序资源。

  • 有关常规资源编辑器的工作原理信息，请参见资源编辑器。

  • 有关如何编辑 .Resx 格式的资源文件的更多信息，请参见应用程序中的资源。

  • 有关如何处理 XML 和 Simplified API for XML (SAX2) 的更多信息，请参见 XML 开发中心。

请参见

概念

C# 编程指南
添加和编辑资源 (Visual C#)

其他资源

C#（针对 Java 开发人员）


安全代码和不安全的代码（C# 与 Java）

[image]

Visual C# 语言概念
安全代码和不安全的代码（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

C# 的一项特别有趣的功能是支持不安全类型的代码。通常，公共语言运行时 (CLR) 负责检查 Microsoft 中间语言 (MSIL) 代码的行为，防止任何有问题的操作。但是，有时您希望直接访问低级功能（如 Win32 API 调用），只要您负责确保此类代码能够正确运行，就允许您这样做。此类代码必须放在源代码中的不安全块内。

Unsafe 关键字

进行低级 API 调用、使用指针算法或执行其他一些棘手操作的 C# 代码必须放在以 unsafe 关键字标记的块内。下面的任何代码均可标记为 unsafe：

  • 整个方法。

  • 大括号中的代码块。

  • 单个语句。

下面的示例演示如何在上述三种情况下使用 unsafe：

C# 复制代码
class TestUnsafe
{
    unsafe static void PointyMethod()
    {
        int i=10;

        int *p = &i;
        System.Console.WriteLine("*p = " + *p);
        System.Console.WriteLine("Address of p = {0:X2}\n", (int)p);
    }

    static void StillPointy()
    {
        int i=10;

        unsafe
        {
            int *p = &i;
            System.Console.WriteLine("*p = " + *p);
            System.Console.WriteLine("Address of p = {0:X2}\n", (int)p);
        }
    }

    static void Main()
    {
        PointyMethod();
        StillPointy();
    }
}

在这段代码中，整个 PointyMethod() 方法被标记为 unsafe，因为该方法声明和使用了指针。StillPointy() 方法将一个代码块标记为 unsafe，因为这个代码块再次使用了指针。

fixed 关键字

在安全代码中，垃圾回收器在对象的生存期内可以自由地移动对象，以组织和压缩可用资源。但是，如果代码使用了指针，则此行为可能很容易造成意外的结果，因此您可以使用 fixed 语句来指示垃圾回收器不要移动某些对象。

下面的代码演示了使用 fixed 关键字以确保在执行 PointyMethod() 方法中的代码块时系统不会移动数组。注意：fixed 只用于不安全的代码中：

C# 复制代码
class TestFixed
{
    public static void PointyMethod(char[] array)
    {
        unsafe
        {
            fixed (char *p = array)
            {
                for (int i=0; i<array.Length; i++)
                {
                    System.Console.Write(*(p+i));
                }
            }
        }
    }

    static void Main()
    {
        char[] array = { 'H', 'e', 'l', 'l', 'o' };
        PointyMethod(array);
    }
}

请参见

任务

“不安全代码”示例

概念

C# 编程指南

参考

不安全代码和指针（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


类基础（C# 与 Java）

[image]

Visual C# 语言概念
类基础（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

以下各节对 C# 和 Java 的修饰符进行了比较。

访问修饰符

C# 修饰符与 Java 修饰符十分相似，只有几个小小的差异。可以使用访问修　符声明类的每个成员或类本身，从而定义允许访问的范围。不是在其他类内部声明的类只能指定公共修饰符或内部修饰符。嵌套的类与其他类成员一样，可以指定下面五个访问修饰符中的任何一个：

  • public

    对所有类都可见。

  • protected

    只对派生类可见。

  • private

    只在给定的类中可见。

  • internal

    只在相同的程序集内可见。

  • protected internal

    只对当前程序集或从包含类派生的类型可见。

Public、Protected 和 Private 修饰符

public 修饰符表示成员可在任何地方（类的内部和外部）使用。protected 修饰符指示只能从包含类或从包含类派生的类内部进行访问。private 修饰符表示只能从包含类型的内部进行访问。在 C# 中，默认的访问修饰符为 Private，而在 Java 中，默认情况下，可在包含包内的任何位置进行访问。

Internal 修饰符

只能在当前程序集内部访问 internal 项。.NET Framework 中的程序集差不多相当于 Java 的 JAR 文件，它表示可从其中构造其他程序的构造块。

Protected Internal 修饰符

protected internal 项只对当前程序集或从包含类派生的类型可见。

Sealed 修饰符

类声明中带有 sealed 修饰符的类与抽象类相反：它不能被继承。您可以将一个类标记为 Sealed，以防止其他类重写其功能。自然，密封类不可能是抽象类。还要注意，struct 是隐式密封的；因此，它不能被继承。sealed 修饰符相当于在 Java 中用 final 关键字标记一个类。

Readonly 修饰符

若要在 C# 中定义常数，请使用 const 或 readonly 修饰符来代替 Java 的 final 关键字。C# 中这两个修饰符的主要不同在于，const 项在编译时处理，而 readonly 字段的值则是在运行时指定。这意味着可以在类构造函数以及声明中对 readonly 字段赋值。例如，下面的类声明了一个名为 IntegerVariable 的 readonly 变量，它在类构造函数中初始化：

C# 复制代码
public class SampleClass
{
    private readonly int intConstant;

    public SampleClass ()  //constructor
    {
        // You are allowed to set the value of the readonly variable
        // inside the constructor
        intConstant = 5;
    }

    public int IntegerConstant
    {
        set
        {
            // You are not allowed to set the value of the readonly variable
            // anywhere else but inside the constructor

            // intConstant = value;  // compile-time error
        }
        get
        {
            return intConstant;
        }
    }
}
class TestSampleClass
{
    static void Main()
    {
        SampleClass obj= new SampleClass();

        // You cannot perform this operation on a readonly field.
        obj.IntegerConstant = 100;

        System.Console.WriteLine("intConstant is {0}", obj.IntegerConstant);  // 5
    }
}

如果 readonly 修饰符应用于静态字段，它应该在类的静态构造函数中被初始化。

请参见

概念

C# 编程指南

参考

访问修饰符（C# 编程指南）
常量（C# 编程指南）

其他资源

C# 编程语言（针对 Java 开发人员）


垃圾回收（C# 与 Java）

[image]

Visual C# 语言概念
垃圾回收（C# 与 Java）
另请参见 发送反馈意见

更新：2007 年 11 月

在 C 和 C++ 中，许多对象要求程序员在声明它们后为其分配资源，然后才可以安全地使用对象。使用完对象后将这些资源释放回自由内存池，也是程序员的责任。如果资源得不到释放，则认为代码泄漏内存，因为越来越多的资源会被不必要地消耗掉。另一方面，如果资源被过早释放，可能会发生数据丢失、其他内存区域损坏和 Null 指针异常。

Java 和 C# 都单独管理应用程序使用的所有对象的生存期，通过这种方法防范这些危险。

在 Java 中，JVM 通过跟踪对已分配资源的引用来释放不再使用的内存。只要 JVM 检测到不再由有效引用引用的资源，该资源就被作为垃圾回收。

在 C# 中，垃圾回收由与 JVM 的功能类似的公共语言运行时 (CLR) 处理。CLR 垃圾回收器定期检查内存堆中是否有任何未引用的对象，并释放这些对象占用的资源。

请参见

概念

C# 编程指南
自动内存管理

其他资源

C# 代码示例（针对 Java 开发人员）


dv_csrefhxs {2DDA7CF9-E8CF-4df1-98E8-521EDE1C5FC7}

{2DDA7CF9-E8CF-4df1-98E8-521EDE1C5FC7}

This topic is for verification of full-text search and indexes.