C#主要语言区域
本文内容
数组、集合和LINQ
字符串内插
模式匹配
委托和Lambda表达式
async/await
属性
本文介绍C#语言的主要功能
数组、集合和LINQ
C#和.NET提供了许多不同的集合类型。
数组包含由语言定义的语法。
泛型集合类型列在System.Collections.Generic命名空间中。
专用集合包括System.Span<T>(用于访问堆栈帧上的连续内存),以及System.Memory<T>(用于访问托管堆上的连续内存)。
所有集合(包括数组、Span<T>和Memory<T>)都遵循一种统一的迭代原则。
使用System.Collections.Geberic.IEnumerable<T>接口。
这种统一的原则意味着任何集合类型都可以与LINQ查询或其他算法一起使用。
你可以使用IEnumerable<T>编写方法,这些算法适用于任何集合。
数组
数组是一种数据结构,其中包含许多通过计算索引访问的变量。数组中的变量(亦称为数组的“元素”)均为同一类型。
我们将这种类型称为数组的“元素类型”。
数组类型是引用类型,声明数组变量只是为引用数组实例预留空间。
实际的数组实例是在运行时使用new运算符动态创建而成。
new运算符指定了新数组实例的长度,然后在此实例的生存期间内固定使用这个长度。
数组元素的索引介于0到length-1之间。
new运算符自动将数组元素初始化为其默认值(例如,所有数值类型的默认值为0,所有引用类型的默认值为null)。
以下示例创建int元素数组,初始化此数组,然后打印此数组的内容。
int[] a = new int[10];
for (int i = 0; i < a.Length; i++)
{
a[i] = i * i;
}
for (int i = 0; i < a.Length; i++)
{
Console.WriteLine($"a[{i}] = {a[i]}");
}
a1数组包含10个元素,a2数组包含50个元素(10 x 5),a3数组包含100个元素(10 x 5 x 2)。
数组的元素类型可以是任意类型(包括数组类型)。
包含数组类型元素的数组有时称为“交错数组”,因为元素数组的长度不必全部一样。
以下示例分配由int数组构成的数组:
int[][] a = new int[3][];
a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];
第一行创建包含三个元素的数组,每个元素都是int[ ]类型,并且初始值均为null。
接下来的代码行将这三个元素初始化为引用长度不同的各个数组实例。
通过new运算符,可以使用“数组初始值设定项”(在分隔符 { 和 } 内编写的表达式列表)指定数组元素的初始值。
以下示例分配int[ ],并将其初始化为包含三个元素。
int[] a = new int[] { 1, 2, 3 };
可从 { 和 } 内的表达式数量推断出数组的长度。
数组初始化可以进一步缩短,这样就不用重新声明数组类型了。
int[] a = { 1, 2, 3 };
以上两个示例等同于以下代码:
int[] t = new int[3];
t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;
foreach语句可用于枚举任何集合的元素。
以下代码从前一个示例中枚举数组:
foreach (int item in a)
{
Console.WriteLine(item);
}
foreach语句使用IEnumrable<T>接口,因此适用于任何集合。
字符串内插
C#字符串内插使你能够通过定义表达式(其结果放置在格式字符串中)来设置字符串格式。
例如,以下示例从一组天气数据显示给定日期的温度:
Console.WriteLine($"The low and high temperature on {weatherData.Date:MM-dd-yyyy}");
Console.WriteLine($" was {weatherData.LowTemp} and {weatherData.HighTemp}.");
// Output (similar to):
// The low and high temperature on 08-11-2020
// was 5 and 30.
内插字符串通过$标记来声明。
字符串插内插计算 { 和 } 之间的表达式,然后将结果转换为string,并将括号内的文本替换为表达式的字符串结果。
第一个表达式({weatherData.Date:MM-dd-yyyy})中的:指定格式字符串。
在前一个示例中,这指定日期应以"MM-dd-yyyy"格式显示。
模式匹配
C#语言提供模式匹配表达式来查询对象的状态并基于该状态执行代码。
你可以检查属性和字段的类型和值,以确定要执行的操作。
还可以检查列表或数组的元素。
swith表达式是模式匹配的主要表达式。
委托和Lambda表达式
委托类型表示对具有特定参数列表和返回类型的方法的引用。
通过委托,可以将方法视为可分配给变量并可作为参数传递的实体。
委托还类似于其他一些语言中存在的“函数指针”概念。
与函数指针不同,委托是面向对象且类型安全的。
下面的示例声明并使用Function委托类型。
delegate double Function(double x);
class Multiplier
{
double _factor;
public Multiplier(double factor) => _factor = factor;
public double Multiply(double x) => x * _factor;
}
class DelegateExample
{
static double[] Apply(double[] a, Function f)
{
var result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
public static void Main()
{
double[] a = { 0.0, 0.5, 1.0 };
double[] squares = Apply(a, (x) => x * x);
double[] sines = Apply(a, Math.Sin);
Multiplier m = new(2.0);
double[] doubles = Apply(a, m.Multiply);
}
}
Function委托类型实例可以引用需要使用double自变量并返回double值的方法。
Apply方法将给定的Function应用于double[ ]的元素,从而返回包含结果的double[ ]的元素,从而返回包含结果的double[ ]
async/await
C#支持含两个关键字的异步程序:async和await。
将async修饰符添加到方法声明中,以声明这是异步方法。
await运算符通知编译器异步等待结果完成。
控件返回给调用方,该方法返回一个管理异步工作状态的结构。
结构通常是System.Threading.Tasks.Task<TResult>,但可以是任何支持awaiter模式的类型。
这些功能使你能够编写这样的代码:以其同步对应项的形式读取,但以异步方式执行。
例如,以下代码会下载Microsoft Docs主页:
public async Task<int> RetrieveDocsHomePage()
{
var client = new HttpClient();
byte[] content = await client.GetByteArrayAsync("https://docs.microsoft.com/");
Console.WriteLine($"{nameof(RetrieveDocsHomePage)}: Finished downloading.");
return content.Length;
}
这一小型示例显示了异步编程的主要功能:
方法声明包含async修饰符。
方法await的主体是GetByteArrayAsync方法的返回。
return语句中指定的类型与方法的Task<T>声明中的类型参数匹配。
(返回Task的方法将使用不带任何参数的return语句)
属性
C#程序中的类型、成员和其他实体支持使用修饰符来控制其行为的某些方面。
例如,方法的可访问性是由public、protected、internal和private修饰符控制。
C#整合了这种能力,以便可以将用户定义类型的声明性信息附加到程序实体,并在运行时检索此类信息。
以下示例声明了HelpAttribute特性,可将其附加到程序实体,以提供指向关联文档的链接。
public class HelpAttribute : Attribute
{
string _url;
string _topic;
public HelpAttribute(string url) => _url = url;
public string Url => _url;
public string Topic
{
get => _topic;
set => _topic = value;
}
}
所有特性类都派生自.NET库提供的Attribute基类。
特性的应用方式为,在相关声明前的方括号内指定特性的名称以及任意自变量。
如果特性的名称以Attribute结尾,那么可以在引用特性时省略这部分名称。
例如,可按如下方法使用HelpAttribute。
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/features")]
public class Widget
{
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/features",
Topic = "Display")]
public void Display(string text) { }
}
此示例将HelpAttribute附加到Widget类。
还向此类中的Dispaly方法附加了另一个HelpAttribute。
特性类的公共构造函数控制了将特性附加到程序实体时必须提供的信息。
可以通过引用特性类的公共读写属性(如上面示例对Topic属性的引用),提供其他信息。
可以在运行时使用反射来读取和操作特性定义的元数据。
如果使用这种方法请求获取特定特性,便会调用特性类的构造函数(在程序源中提供信息)。
返回生成的特性实例。
如果是通过属性提供其他信息,那么在特性实例返回前,这些属性会设置为给定值。
下面的代码示例展示了如何获取与Widget类及其Display方法相关联的HelpAttribute实例。
Type widgetType = typeof(Widget);
object[] widgetClassAttributes = widgetType.GetCustomAttributes(typeof(HelpAttribute), false);
if (widgetClassAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
}
System.Reflection.MethodInfo displayMethod = widgetType.GetMethod(nameof(Widget.Display));
object[] displayMethodAttributes = displayMethod.GetCustomAttributes(typeof(HelpAttribute), false);
if (displayMethodAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
Console.WriteLine($"Display method help URL : {attr.Url} - Related topic : {attr.Topic}");
}
了解详细信息
可以通过试用其中一个教程来了解更多关于C#的内容。