语法结构
.NET读作 dot net,C# 的程序必须依附在.net平台,和C/C++的区别是:
- C/C++代码 → 编译器 → 机器指令
- C# 代码 → 编译器 → IL中间语言 → .NET中的CLR → 机器指令
好处就是可以提高开发效率,代码直接转换为机器指令比较耗时,C# 则只有在运行的时候才会转换,但是代价就是性能下降,
- .net framework平台,微软开发,仅支持Windows平台
- 后来社区开发了MONO,可以跨平台
- 最后微软开发了.ner core,支持跨平台
第一个程序:
using System;
// 命名空间
namespace HelloWorldApplication
{
// 类型
class HelloWorld
{
// 函数或者方法
static void Main(string[] args)
{
Console.WriteLine("Hello World");
Console.ReadKey();
}
}
}
- 程序的第一行
using System
。using
关键字用于在程序中包含System
命名空间。 一个程序一般有多个using
语句。 - 下一行是
namespace
声明。一个namespace
里包含了一系列的类。HelloWorldApplication
命名空间包含了类HelloWorld
。 - 下一行是
class
声明。类HelloWorld
包含了程序使用的数据和方法声明。类一般包含多个方法。方法定义了类的行为。在这里,HelloWorld
类只有一个Main
方法。 - 下一行定义了
Main
方法,是所有 C# 程序的 入口点。Main
方法说明当执行时 类将做什么动作。 - 下一行
/*...*/
将会被编译器忽略,且它会在程序中添加额外的 注释。 Main
方法通过语句Console.WriteLine("Hello World")
, 指定了它的行为。WriteLine
是一个定义在System
命名空间中的Console
类的一个方法。该语句会在屏幕上显示消息 “Hello World”。- 最后一行
Console.ReadKey()
是针对 VS .NET 用户的。这使得程序会等待一个按键的动作,防止程序从 Visual Studio .NET 启动时屏幕会快速运行并关闭。
如果不写那一句 using System;
,那么打印输出的那个语句就是:
System.Console.WriteLine("Hello World");
这个结构一般是 命名空间.类.方法
,且命名空间、类名、方法名一般以大写开头。
注:
- C# 是大小写敏感的。
- 所有的语句和表达式必须以分号
;
结尾。 - 程序的执行从
Main
方法开始。 - 与 Java 不同的是,文件名可以不同于类的名称。
主函数中 string[] args
的含义:
-
string[]
:表示一个字符串数组。 -
args
:是参数的名称(可以自定义,但通常使用args
)。 - 作用:
args
用于接收从命令行传递的参数。
示例
假设你编译了一个名为 MyProgram.exe
的程序,并在命令行中运行:
MyProgram.exe arg1 arg2 arg3
在 Main
方法中,args
数组将包含以下内容:
args[0] = "arg1"
args[1] = "arg2"
args[2] = "arg3"
顶级语句
在VS2022中创建 C# 项目的时候,会有一个提示就是是否使用顶级语句,那么什么是顶级语句呢?
在 C# 9.0 版本中,引入了顶级语句(Top-Level Statements)的概念,这是一种新的编程范式,允许开发者在文件的顶层直接编写语句,而不需要将它们封装在方法或类中。
特点:
- 无需类或方法:顶级语句允许你直接在文件的顶层编写代码,无需定义类或方法。
- 文件作为入口点:包含顶级语句的文件被视为程序的入口点,类似于 C# 之前的
Main
方法。 - 自动
Main
方法:编译器会自动生成一个Main
方法,并将顶级语句作为Main
方法的主体。 - 支持局部函数:尽管不需要定义类,但顶级语句的文件中仍然可以定义局部函数。
- 更好的可读性:对于简单的脚本或工具,顶级语句提供了更好的可读性和简洁性。
- 适用于小型项目:顶级语句非常适合小型项目或脚本,可以快速编写和运行代码。
- 与现有代码兼容:顶级语句可以与现有的 C# 代码库一起使用,不会影响现有代码。
传统 C# 代码 - 在使用顶级语句之前,你必须像这样编写一个 C# 程序:
using System;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
使用顶级语句的 C# 代码 - 使用顶级语句,可以简化为:
using System;
Console.WriteLine("Hello, World!");
顶级语句支持所有常见的 C# 语法,包括声明变量、定义方法、处理异常等。例如:
using System;
using System.Linq;
// 顶级语句中的变量声明
int number = 42;
string message = "The answer to life, the universe, and everything is";
// 输出变量
Console.WriteLine($"{message} {number}.");
// 定义和调用方法
int Add(int a, int b) => a + b;
Console.WriteLine($"Sum of 1 and 2 is {Add(1, 2)}.");
// 使用 LINQ
var numbers = new[] { 1, 2, 3, 4, 5 };
var evens = numbers.Where(n => n % 2 == 0).ToArray();
Console.WriteLine("Even numbers: " + string.Join(", ", evens));
// 异常处理
try
{
int zero = 0;
int result = number / zero;
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Error: " + ex.Message);
}
注意事项
- 文件限制: 顶级语句只能在一个源文件中使用。如果在一个项目中有多个使用顶级语句的文件,会导致编译错误。
- 程序入口: 如果使用顶级语句,则该文件会隐式地包含
Main
方法,并且该文件将成为程序的入口点。 - 作用域限制: 顶级语句中的代码共享一个全局作用域,这意味着可以在顶级语句中定义的变量和方法可以在整个文件中访问。
顶级语句在简化代码结构、降低学习难度和加快开发速度方面具有显著优势,特别适合于编写简单程序和脚本。
优缺点对比
顶级语句
优点:
- 简洁:适合编写简单的脚本或小型程序。
- 快速原型开发:在测试或快速验证想法时非常方便。
- 减少样板代码:不需要显式定义
class
和Main
方法。
缺点:
- 可维护性差:随着代码规模增大,顶级语句会变得难以维护。
- 不适合大型项目:缺乏结构化和模块化,难以组织复杂的逻辑。
- 工具支持有限:某些工具(如单元测试框架)可能不完全支持顶级语句。
适用场景:
- 小型工具或脚本。
- 快速原型开发。
- 简单的控制台应用程序。
传统封装类:
优点:
- 结构化:适合组织复杂的逻辑和大型项目。
- 可维护性强:代码更易于阅读、扩展和维护。
- 工具支持完善:所有 C# 工具和框架都支持传统方式。
缺点:
- 样板代码多:需要显式定义
class
和Main
方法。 - 略显繁琐:对于简单的脚本或小型程序,可能显得冗余。
适用场景:
- 大型项目。
- 需要模块化和结构化的代码。
- 需要与框架(如 ASP .NET Core、单元测试框架)集成的项目。
标识符与关键字
标识符是用来识别类、变量、函数或任何其它用户定义的项目。在 C# 中,类的命名必须遵循如下基本规则:
- 标识符必须以字母、下划线或
@
开头,后面可以跟一系列的字母、数字(0-9
)、下划线(_
)、@
。 - 标识符中的第一个字符不能是数字。
- 标识符必须不包含任何嵌入的空格或符号,比如
? - +! # % \^ & \* ( ) [ ] { } . ; : " ' / \\
。 - 标识符不能是 C# 关键字。除非它们有一个
@
前缀。 例如,@if
是有效的标识符,但if
不是,因为if
是关键字。 - 标识符必须区分大小写。大写字母和小写字母被认为是不同的字母。
- 不能与 C# 的类库名称相同。
关键字是 C# 编译器预定义的保留字。这些关键字不能用作标识符,但是,如果您想使用这些关键字作为标识符,可以在关键字前面加上 @
字符作为前缀。在 C# 中,有些关键字在代码的上下文中有特殊的意义,如 get
和 set
,这些被称为上下文关键字(contextual keywords) 。
图源自菜鸟教程。
图源自菜鸟教程。
数据类型
值类型(Value types)
值类型变量可以直接分配给一个值。它们是从类 System.ValueType
中派生的。值类型 直接包含数据 。比如 int
、char
、float
,它们分别存储 整数 、字符串 、浮点数 。例如当声明一个 int
类型时,系统 分配内存 来 存储值 。
常见的值类型:
图源自菜鸟教程。
获取类型的存储尺寸示例:
using System;
namespace DataTypeApplication
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Size of int: {0}", sizeof(int));
Console.ReadLine();
}
}
}
引用类型(Reference types)
引用类型 不包含 存储在变量中的 实际数据,但它们包含 对变量的引用。
换句话说,它们指的是一个 内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。
内置的引用类型有:object
、dynamic
和 string
。
类似于 C++ 中的指针,详见 C#的引用类型和C++的指针异同之处
对象(Object)类型
对象(Object)类型 是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。
当一个值类型转换为对象类型时,则被称为 装箱;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱。
关于装箱与拆箱,详见 C#中类型转换的“装箱”与“拆箱”
object boxed = 42; // 装箱:将值赋给对象类型
int unboxed = (int)boxed; // 拆箱:将 object 转换回 int
动态(Dynamic)类型
可以存储 任何类型的值 在 动态类型变量 中,动态变量的 类型检查 是在代码运行时发生的。
声明动态类型的关键字为 dynamic
,例如:
var x = 10; // x 的类型是 int(编译时确定)
dynamic y = 10; // y 的类型在运行时解析
y = "World"; // 允许赋值不同类型
动态类型与对象类型相似,但是对象类型变量的类型检查是 在编译时 发生的,而动态类型变量的类型检查是 在运行时 发生的。
字符串(String)类型
字符串(String)类型允许给变量分配任何字符串值,其是 System.String
类的别名,是从对象(Object)类型派生的。
字符串类型的值可以通过两种形式进行分配:引号 和 @引号,例如:
string str = "runoob.com";
string str = @"runoob.com";
字符串的前面加 @
,称作"逐字字符串",可以将转义字符 \
当作普通字符对待,比如:
string str = @"C:\Windows";
// 等同于:
string str = "C:\\Windows"; // 第一个为转义,第二个为字符串
且 @
字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内:
string str = @"<script type="text/javascript">
<!--
-->
</script>";
指针类型(Pointer types)
指针类型变量存储另一种类型的 内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。
type* identifier;
正如 C#的引用类型和C++的指针 中所说,指针在 C# 中主要用于与原生代码交互或性能优化,但 不推荐常规使用。