C Sharp语法英语Syntax (programming languages)是编写该语言程序的一套规则。也适用于.NET Framework and Mono

C# 2.0的特性

编辑

针对于.NET SDK 2.0(相对应于ECMA-334标准第三版),C# 的新特性有:

分部类别

编辑

分部类别将类别的实现分在多个文件中。该概念于C# 中首次出现,除了能将一个类别的成员分开存放,还使ASP.NET中的代码后置得以实现。代码后置实现了HTML代码和后台交互代码的分离。

file1.cs:

public partial class MyClass1
{
    public void MyMethod1()
    {
        // implementation
    }
}

file2.cs:

public partial class MyClass1
{
    public void MyMethod2()
    {
        // implementation
    }
}

分部类别这个特性允许将一个类别的编写工作分配给多个人,一人写一个文件,便于版本控制。它又可以隔离自动生成的代码和人工书写的代码,例如设计窗体应用程序时。

泛型

编辑

泛型,或参数化类型,是被C#支持的.NET 2.0特性。不同于C++模版,.NET参数化类型是在运行时被实例化,而不是编译时,因此它可以跨语言,而C++模版却不行。C#泛型类在编译时,先生成中间代码IL,通用类型符号T只是一个占位符;在实例化类时,根据实际数据类型代替T并由即时编译器(JIT)生成本地代码,其中使用了实际的数据类型,等同于用实际类型写的普通的类。

它支持的一些特性并不被C++模版直接支持,比如约束泛型参数实现一个接口。另一方面,C# 不支持无类型的泛型参数。不像Java中的泛型,在CLI虚拟机中,.NET generics使用具化生成泛型参数,它允许优化和保存类型信息。[1]

泛型类中,可以用where关键字对参数类型实现约束。例如:

 public class Node<T, V> where T : Stack, IComparable, new(),class where V: Stack,struct
 {...}

上述表示T和V必须是Stack类或其派生类,T必须继承了IComparable接口、有无参构造函数、是引用类型;V必须是值类型。

泛型不仅能作用在类上,也可单独用在类的方法上,称为“泛型方法”。

泛型类的静态成员变量在相同封闭类间共享,不同的封闭类间不共享。

泛型类中的方法重载,参数类型T和V在运行时确定,不影响这个类通过编译。C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。特别地,当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。

静态类别

编辑

静态类别它不能被实例化,并且只能有静态成员。这同很多过程语言中的模块概念相类似。

迭代器

编辑

一种新形式的迭代器它提供了函数式编程中的generator,使用yield return

类似于Python中使用的yield

// Method that takes an iterable input (possibly an array)
// and returns all even numbers.
public static IEnumerable<int> GetEven(IEnumerable<int> numbers)
{
    foreach(int i in numbers)
    {
        if (i % 2 == 0) yield return i;
    }
}

注意事项:

  • foreach循环时考虑线程安全性,不要试图对被遍历的集合进行remove和add等操作
  • IEnumerable接口是LINQ特性的核心接口。只有实现了IEnumerable接口的集合,才能执行相关的LINQ操作,比如select,where等

匿名方法

编辑

匿名方法类似于函数式编程中的闭包[2]匿名方法是通过使用 delegate 关键字创建委托实例来声明的。例如:


delegate void NumberChanger(int n);
 
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: {0}", x);
};



public void Foo(object parameter)
{
    // ...

    ThreadPool.QueueUserWorkItem(delegate
    {
        // anonymous delegates have full access to local variables of the enclosing method
        if(parameter == ...)
        { 
            // ... 
        }

        // ...
    });
}

委托的协变和逆变

编辑

委托签名的协变和逆变,[3]

属性访问器可以被单独设置访问级别

编辑

例子:

string status = string.Empty;

public string Status
{
    get { return status; }             // anyone can get value of this property,
    protected set { status = value; }  // but only derived classes can change it
}

可空类型

编辑

可空类型(跟个问号,如int? i = null;)允许设置null给任何类类型。

int? i = null;
object o = i;
if(o == null)
    Console.WriteLine("Correct behaviour - runtime version from September 2005 or later");
else
    Console.WriteLine("Incorrect behaviour - pre-release runtime (from before September 2005)");

??运算子

编辑

??):如果左运算数表达式的值不为空值时回传该值,如果为空值则返回右运算数表达式的值。

object nullObj = null; 
object obj = new Object(); 
return nullObj ?? obj; // returns obj

主要用作将一个可空类型赋值给不可空类型的简便语法

int? i = null;
int j = i ?? 0; // Unless i is null, initialize j to i. Else (if i is null), initialize j to 0.

C# 3.0的特性

编辑

C# 3.0发布于2007年10月17日,是.NET Framework 3.5的一部分,它的新特性灵感来自于函数式编程语言,如:HaskellML,并广泛地引入了Language Integrated Query(LINQ)模式到通用语言运行库中e.[4]

语言集成查询(英语:Language Integrated Query,缩写:LINQ):[5] 上下文相关关键字"from, where, select"可用于查询SQL、XML、集合等。这些标识符在LINQ上下文中被作为关键字,但是它们的增加不会破坏原有的名为fromwhereselect的变量。

类型初始化器

编辑
Customer c = new Customer();
c.Name = "James";

可写作:

Customer c = new Customer() { Name="James" };

集合初始化器

编辑
MyList list = new MyList();
list.Add(1);
list.Add(2);

可写作

MyList list = new MyList { 1, 2 };

假设MyList实现了System.Collections.IEnumerable且有一个Add方法method[6]

匿名类型

编辑
var x = new { Name="James" };

局部变量类型推断

编辑

局部变量类型推断

var x = new Dictionary<string, List<float>>();

等同于

Dictionary<string, List<float>> x = new Dictionary<string, List<float>>();

它只是一个语法糖,这个特性被匿名类型声明时所需要

Lambda表达式

编辑

Lambda表达式(无函式名称的物件方法在程式语言中的表达语法):

listOfFoo.Where(
    delegate(Foo x)
    {
        return x.Size > 10; 
    }
)
可写作
listOfFoo.Where(x => x.Size > 10);

编译器翻译Lambda表达式为强类型委托或强类型表达式树

注意事项:

  • 如果只有一个参数,可以省略括号(),例如 item=>{Console.WriteLine("只有一个参数{0}的Lambda表达式",item); };
  • 如果只有一个返回值的语句,可以省略花括号{}、return关键字、分号,例如 item => {return item % 2 == 0;};改写成:item =>item %2 == 0;
  • Lambda表达式可以分配给Func,Action或Predicate委托。

自动化属性

编辑

编译器将自动生成私有变量和适当的getter(get访问器)和setter(set访问器),如:

public string Name
{
    get; 
    set; 
}

扩展方法

编辑

扩展方法能够使现有的类型添加方法,而无需创建新的派生类型、重新编译或以其它方式修改原始类型。

使用扩展方法,必须在一个非嵌套、非泛型的静态类中定义一个静态方法,方法第一个参数必须附加this关键字作为前缀,第一个参数不能有其它修饰符(如ref或者out),这个方法将被编译器添加到该this的类型中。

public static class IntExtensions
{
    public static void PrintPlusOne(this int x) 
    {
        Console.WriteLine(x + 1);
    }
}
 
int foo = 0;
foo.PrintPlusOne();

注意事项:

  • 扩展方法只会增加编译器的工作,但不会影响程序运行性能(用继承的方式为一个类型增加特性反而会影响性能)
  • 如果原来的类中有一个方法,跟扩展方法一样,那么扩展方法不会被调用,编译器也不会提示

分部方法

编辑

允许代码生成器生成方法声明作为扩展点,如果有人在另一个部分类实现了它才会被包含于原代码编译。[7]

  1. 分部方法(Partial methods)必须定义在分部类(partial classes)中
  2. 定义分部方法需要用partial做修饰符
  3. 分部方法不一定总是有执行内容的,也就是说定义的方法可以一句操作语句都没有
  4. 分部方法返回值必须是void
  5. 分部方法可以是静态(static)方法
  6. 分部方法可以包含参数,参数可以包含以下修饰词:this,ref,params
  7. 分部方法必须是私有(private)方法

例子:

partial class C
{
    static partial void M(int i); // defining declaration
}
partial class C
{
    static partial void M(int i)
    {
        dosomething();
    }
}

C# 4.0的特性

编辑

dynamic类型

编辑

C# 4.0新增dynamic关键字,提供动态编程(dynamic programming),把既有的静态物件标记为动态物件,类似javascript, PythonRuby

dynamic关键字标记的实例被处理成一个特殊包装的object对象,取消了CLI的编译时类型检查,编译时被假定支持任何操作,但如果并不实际支持则运行时报错。

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

具名参数与可选参数

编辑
public StreamReader OpenFile(
    string path,
    int bufferSize = 1024)
{
...
}

呼叫OpenFile时,顺序可以完全颠倒:

OpenFile(bufferSize: 4096, path: "foo.txt");

与COM组件互动

编辑

在C#中打开一个Word文件:

static void Main(string[] args) {
    Word.Application wordApplication = new   
       Word.Application() {Visible = true};     
    wordApplication.Documents.Open(@"C:\plant.docx",   
       ReadOnly: true);  
}

在C#中指定Excel的某一格文字:

excelObj.Cells[5, 5].Value = "This is sample text";

泛型的协变和逆变

编辑

C# 4.0支援协变和逆变,例如在泛型介面可以加上in、out关键字。

  public interface IComparer<in T>  
  {  
    int Compare(T left, T right);  
  }

  public interface IEnumerable<out T> : IEnumerable
  {
    IEnumerator<T> GetEnumerator();
  }

C# 5.0的特性

编辑

async和await是一对语法糖,允许开发人员非常轻松的调用基于TASK的异步编程。async-await关键字并不会真的创建一个线程池任务,完成这个动作依赖于被调用方法中的函数。

Caller info attributes

编辑

CallerInfoAttributes用于调试时访问调用者的信息。包括三个主要的类:

  • [CallerMemberName] :返回调用函数的名称。
  • [CallerFilePath] :返回调用函数所在源文件全路径信息 。
  • [CallerLineNumber] :返回调用函数调用具体行号。
using System;
using System.Runtime.CompilerServices;

namespace TestPro
{
    class Program
    {
        public static void Main()
        {
            Log("Test.");
        } 

        // 对日志消息进行记录,同时所有内容均有默认值,如果获取失败,则使用默认值。
        public static void Log(string message,
            [CallerMemberName] string callerName = "unknown", 
            [CallerFilePath] string callerFilePath = "unknown", 
            [CallerLineNumber] int callerLineNumber = -1)
        {
            Console.WriteLine("Message: {0}", message);
            Console.WriteLine("Caller's Name: {0}", callerName);
            Console.WriteLine("Caller's FilePath: {0}", callerFilePath);
            Console.WriteLine("Caller's LineNumber: {0}", callerLineNumber);
        }
    }
}
/*
程序执行以后会显示以下内容。
Message: Test.
Caller Name: Main
Caller FilePath: C:UsersAdministratorsource
eposTestProProgram.cs
Caller Line number: 10
请按任意键继续. . .*/

绑定运算符

编辑
=: 绑定运算符简化了数据绑定
comboBox1.Text :=: textBox1.Text; //将文本框的内容绑定到下拉框。  

带参数的泛型构造函数

编辑

这个的加入给一些设计增加了强大功能,泛型早在C#2.0加入后就有着强大的应用,一般稍微设计比较好的框架,都会用到泛型,C#5.0加入带参数泛型构造函数,则在原有基础上对C#泛型完善了很多。

class MyClass<T>
    where T : class, new ()
{
    public MyClass()
    {
        this.MyObject = new T();
    }

    T MyObject { get; set; }
}

case支持表达式

编辑

以前case里只能写一个具体的常量,而现在可以加表达式

switch(myobj){
  case string.IsNullorEmpty(myotherobj):
 ..... 
  case myotherobj.Trim().Lower: 
 ....
}

扩展属性

编辑
[Associate(string)]
public static int MyExtensionProperty { get;set;}

支持null类型运算

编辑
int x? = null;
int y? = x + 40;

Myobject obj = null;
Myotherobj obj2 = obj.MyProperty ??? new Myotherobj();

C# 6.0的特性

编辑

只读自动属性

编辑

不必使用完整属性语法定义属性。可以在声明属性的位置或类型的构造函数中初始化属性。

class Person
    {
        //新语法
        private string Name { get; } = "Fanguzai";  //不用带上 private set;

        //旧语法
        public int Age { get; private set; } ;
    }

自动属性初始化

编辑
class MyClass
       {
           public string Name { get; set; } = "Fanguzai";
       }

具有表达式体的函数成员

编辑

可以采用与用于 lambda 表达式相同的轻量语法,声明代码为单个表达式的成员。具有立即仅返回表达式结果,或单个语句作为方法主体。C# 6.0支持方法和属性get。C# 7.0扩展支持了构造函数、终结器、属性set、索引器。

using System;

public class Person
{
   public Person(string firstName, string lastName)
   {
      fname = firstName;
      lname = lastName;
   }

   private string fname;
   private string lname;

   public override string ToString() => $"{fname} {lname}".Trim(); //返回值类型string
   public void DisplayName() => Console.WriteLine(ToString()); //返回值类型void
   public string Name => $"{fname} {lname}".Trim();//只读属性
}

使用静态类

编辑

可以导入静态类型的可访问静态成员,以便可以在不使用类型名限定访问的情况下引用成员。

using System;
using static System.Console;

namespace _usingStatic
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hi,Fanguzai!");
            WriteLine("Hi,Fanguzai!");  // 使用了 using static System.Console;
        }
    }
}

nameof 运算符

编辑
class Program
    {

        private static void Func1(int x) { }
         private string F<T>() => nameof(T);
        private void Func2(string msg) { }
 
         static void Main(string[] args)
         {
             var program = new Program();
 
             Console.WriteLine(nameof(System.Text));
             Console.WriteLine(nameof(Func1));
             Console.WriteLine(nameof(Program));
             Console.WriteLine(nameof(program));
             Console.WriteLine(nameof(F));
 
             Console.Read();
         }
    }

内插字符串

编辑

以$为前缀构造字符串。 内插字符串表达式类似于包含表达式的模板字符串。内插字符串表达式通过将包含的表达式替换为表达式结果的ToString表现形式来创建字符串。

            var name = "Fanguzai";
            Console.WriteLine($"Hello, {name}");

想要在内插字符串中包含大括号“{” 或 “}”,使用两个大括号,即 “Template:” 或 “”。

NULL 条件运算符

编辑

用于在执行成员访问 ?. 或索引 ?[ 操作之前,测试是否存在 NULL 值。

         static void Main(string[] args)
         {
            string name = null;
            Console.WriteLine($"1:{name?.Length}");
            
            name = "Fanguzai";
            Console.WriteLine($"2:{name?.Length}");
            Console.WriteLine($"3: {name?[0]}");
        }

//普通的委托调用
             Func<int> func = () => 0;
             if (func!=null)
             {
                 func();
             }
 
             //简化调用
             func?.Invoke();

Catch 和 Finally 区块中的 Await

编辑
async Task Test()
         {
             var wc = new WebClient();
             try
             {
                 await wc.DownloadDataTaskAsync("");
             }
             catch (Exception)
             {
                 await wc.DownloadDataTaskAsync("");  //OK
             }
             finally
             {
                 await wc.DownloadDataTaskAsync("");  //OK
             }
         }

索引初始设定

编辑
    var nums = new Dictionary<int, string>
            {
                [7] = "seven",
                [9] = "nine",
                [13] = "thirteen"
            };

            //这是旧的方式
            var otherNums = new Dictionary<int, string>()
            {
                {1, "one"},
                {2, "two"},
                {3, "three"}
            };

异常过滤器 (Exception filters) 

编辑
            try
            {
                var numbers = new Dictionary<int, string> {[7] = "seven",[9] = "nine",[13] = "thirteen" };
            }
            catch (ArgumentNullException e)
            {
                if (e.ParamName == "customer")
                {
                    Console.WriteLine("customer can not be null");
                }
            }

集合初始设定式的扩充方法

编辑

改进的多载解析

编辑

C# 7.0的特性

编辑

具有表达式体的函数成员

编辑

可以采用与用于 lambda 表达式相同的轻量语法,声明代码为单个表达式的成员。具有立即仅返回表达式结果,或单个语句作为方法主体。C# 6.0支持方法和属性get。C# 7.0扩展支持了构造函数、终结器、属性set、索引器。

   class MyClass
        {
           public string this[int i] //索引器
           {
             get => types[i];
             set => types[i] = value;
            }

            private string[] types = { "Baseball", "Basketball", "Football","Hockey", "Soccer", "Tennis","Volleyball" };

            public MyClass(string name) => Name = name; //构造函数
            private string locationName;

            ~Destroyer() => Console.WriteLine($"The {ToString()} destructor is executing.");//终结器

             public string Name => locationName; //属性get

             public string Name{
                 get => locationName;
                 set => locationName = value; //属性set
             }
        }
using System;
public class Location
{
   private string locationName;

   public Location(string name) => Name = name; //构造函数

   public string Name
   {
      get => locationName;   //get属性
      set => locationName = value;  //set属性
   }

   public override string ToString() => GetType().Name;

   ~Location() => Console.WriteLine($"The {ToString()} finalizer is executing."); //析构函数

   private string[] types = { "Baseball", "Basketball", "Football",
                              "Hockey", "Soccer", "Tennis",
                              "Volleyball" };

   public string this[int i] 
   {
      get => types[i];            //索引器
      set => types[i] = value;
   }
}

out 变数

编辑

能够直接宣告一个变数在它要传入的地方,当成一个 out 的引数[8]

static void AssignVal(out string strName)
        {
            strName = "I am from OUT";
        }
static void Main(string[] args)
        {
            AssignVal(out var szArgu); //无需提前声明该变量
        }

弃元

编辑

元组/对象的解构:

var tuple = (1, 2, 3, 4, 5);
(_, _, _, _, var fifth) = tuple;

使用 is/switch 的模式匹配:

var obj = CultureInfo.CurrentCulture.DateTimeFormat;
switch (obj)
{
 case IFormatProvider fmt:
   Console.WriteLine($"{fmt} object");
   break;
 case null:
   Console.Write("A null object reference");
   break;
 case object _:
   Console.WriteLine("Some object type without format information");
   break;
}

if (obj is object _)
{
 ...
}

对具有 out 参数的方法的调用:

var point = new Point(10, 10);
// 只要 x, 不关心 y
point.GetCoordinates(out int x, out _);

作用域内独立使用场景:

void Test(Dto dto)
{
   _ = dto ?? throw new ArgumentNullException(nameof(dto));
}

元组

编辑
( string, string, string, string) getEmpInfo()
{
    //read EmpInfo from database or any other source and just return them
    string strFirstName = "abc";
    string strAddress = "Address";
    string strCity= "City";
    string strState= "State";
     return (strFirstName, strAddress, strCity, strState); // tuple literal
}
 
//Just call above method and it will return multiple values 
 var empInfo= getEmpInfo();
Console.WriteLine($"Emp info as  {empInfo .Item1} {empInfo .Item2} {empInfo .Item3} {empInfo .Item4}.");

//也可以给元组中各项命名:
(string strFName, string strAdd, string strC, string strSt) getEmpInfo_named()
{
    //code goes here
    //直接在元组文字中返回其名称,如下所示
    return (strFName: strFirstName, strAdd: strAddress, strCity: strC, strState: strSt);
} 
//Now when you call method get values with specific name as below 
var empInfo1= getEmpInfo();
Console.WriteLine($"Emp info as {empInfo1.strFName} {empInfo1.strAdd} {empInfo1.strC} {empInfo1.strSt}.");

.NET有Tuple类型,但是它是一个引用类型,会导致性能问题,但是C#7.0带来了具有值类型的Tuple,该类型具有更快的性能和可变的类型。

元组的解构

大多数时候,不想访问整个元组包,或者只需要内部值,那么可以使用元组解构并获取所需的值

    ( string strFName,  string strAdd,  string strC, string strSt) = getEmpInfo(); 
    Console.WriteLine($"Address: { strAdd }, Country: { strC }");

记录类型

编辑

记录类型只不过是属性和变量的容器。借助记录类型可以减少工作量。

//一个带有属性,构造函数和变量的类,因此访问和声明变量需要编写更多代码:
    class studentInfo
    {
        string _strFName;
        string _strMName;
        string _strLName;
        studentInfo(string strFN, string strMN, string strLN){
            this._strFName = strFN;
            this._strMName = strMN;
            this._strLName = strLN;
        }
        public string StudentFName {get{ return this._strFName;}}
        public string StudentMName {get{ return this._strMName;}}
        public string StudentLName {get{ return this._strLName;}}
    }

//具有相同功能的记录类型:
class studentInfo(string StudentFName, string StudentMName, string StudentLName);

不可为空的引用类型

编辑
int objNullVal;     //non-nullable value type
int? objNotNullVal;    //nullable value type
string! objNotNullRef; //non-nullable reference type 不可为空的引用类型
string objNullRef;  //nullable reference type

本地方法

编辑

可以在其中使用out或ref。

private static void Main(string[] args)
{
    int local_var = 100;
    int LocalFunction(int arg1)
    {
        return local_var * arg1;
    } 
    Console.WriteLine(LocalFunction(100));
}

数值字面量中包含下划线分隔符以提高可读性

编辑
        static void Main(string[] args)
        {
            var lit1 = 478_1254_3698_44;
            var lit2 = 0Xab_bc47;
            //C# also come with binary literal for bunary values
            var binLit = 0b1_00;
        }

模式匹配

编辑

允许在IS语句和SWITCH语句中把模式与任何数据类型匹配。模式可以是常量模式、类型模式、变量模式。

    public  void Method1( object obj)
    {
        //following null is constant pattern
         if (obj  is null)  return;
        //datatype pattern, string is datatype that we check directly     
         if (obj  is  string st)
        { //code goes here }
        else
        return; 
    }

class Calculate();
class Add(int a, int b, int c) : Calculate;
class Substract(int a, int b) : Calculate;
class Multiply(int a, int b, int c) : Calculate;
 
Calculate objCal = new Multiply(2, 3, 4);
switch (objCal)
{
    case Add(int a, int b, int c):
        //code goes here
        break;
    case Substract(int a, int b):
        //code goes here
        break;
    case Multiply(int a, int b, int c):
        //code goes here
        break;
    default:
        //default case
        break;
}

变量作为Ref从方法返回

编辑

通过Ref传递一个变量,将它作为Ref返回,并将它们存储为Ref

   class Program
    {
        static string emptyString;
        static ref string getFromList(string strVal, string[] Values)
        {            
            var kk = Values.GetLength(0);
            for (int i = 0; i < Values.GetLength(0); i++)
            {
                if (strVal == Values[i])
                    return ref Values[i]; //return location as ref not actual value
            }
            return ref emptyString;
        }
        static void Main(string[] args)
        {
            string[] values = { "a", "b", "c", "d" };
            ref string strSubstitute = ref getFromList("b", values);
            strSubstitute = "K"; // replaces 7 with 9 in the array
            Console.WriteLine(values[1]); // it prints "K"
        }
    }

在上面的示例中,我们通过从方法返回Ref找到并替换了字符串。

从表达式中抛出异常

编辑
public string getEmpInfo( string EmpName)
    {
        string[] empArr = EmpName.Split(",");
        return (empArr.Length > 0) ? empArr[0] : throw new Exception("Emp Info Not exist");
    }

C# 7.1的特性

编辑

async Main方法

编辑
static async Task Main()//注意返回Task
{
    await AsyncMethod1();
}

default常值运算式

编辑
Func<int, string> whereClause = default;

推断的 tuple 项目名称

编辑
int count = 3;
string colors = "colors of the flag";
var tupleCol = (count,colors); // here syntax gets trimmed

C# 7.2的特性

编辑

有条件的“ref”

编辑
ref var finalVal = ref (value1 != null ? ref val1_arr[0] : ref val2_arr[0]);

无需名称的非尾部命名参数

编辑

如果参数的顺序正确,则无需为参数配上名称

//suppose I have written a function and used below named arguments
EmpDetails(EmpID: 3, firstName: "Manavya", City: "Daswel");
 
//here in C#7.2, I can write the above code line as
EmpDetails(EmpID: 3, "Manavya", City: "Daswel");
 
//if you observed that firstName: "Manavya" is replaced by only "manavya" 
// and it is the beauty of the C#7.2

数值常值中的前置下划线分隔符

编辑
// 在C# 7.2中,_可以用在`0x`或`0b`之后
0x_1_2 // C# 7.2及更高版本可用
0b_1_0_1 // C# 7.2及更高版本可用

私有保护访问修饰符

编辑

只有当前项目里的继承关系才能用,出了这个程序集就没办法用了,即 private protected 为私有继承(protected 且 internal)。

C# 7.3的特性

编辑

改进stackalloc运算符

编辑
int* Array1 = stackalloc int[3] {5, 6, 7};//老语法

Span<int> Array = stackalloc [] {10, 20, 30};//新语法

固定语句支持更多类型

编辑

固定语句是防止垃圾回收器清除可移动变量的语句,可以使用fixed关键字创建这些语句。从C#7.3开始,固定语句支持具有以下内容的任何类型:GetPinnableReference()方法,返回的ref T是固定的。

非托管约束

编辑

一个成员是一个非托管型,如果它是byte,short,sbyte,int,long,char,bool类型。可以使用非托管约束来显示类型参数必须是具有非指针的非托管类型。

    unsafe public static byte[] convert_to_byte<T>(this T argument) where T : unmanaged
    {
        var size = sizeof(T);
        var result = new Byte[size];
        return result1;
    }

还可以使用System.Enum和System.Delegate作为基类约束。

元组支持等于和不等于运算符

编辑
    var exp1 = (val1: 100, val2: 20);
    var exp2 = (val1: 100, val2: 20);
    exp1 == exp2; //it will return displays as 'true'

重载的“in”方法的更改

编辑

为“按引用”和“按值”方法命名相同的方法时,它会抛出歧义异常,为避免此错误,C#7.3使用了“in”方法:

    static void calculateArea(var1 arg);
    static void calculateArea(in var1 arg);

扩展参数边界

编辑

可以在构造函数初始值设定项、属性初始值设定项中定义out参数。

    //here, we have defined the out parameter in constructor
    public class parent
    {
       public parent(int Input, out int Output)
       {
          Output = Input;
       }
    }
     
    //now let's use the above class in the following class
    public class Child : parent
    {
       public Child(int i) : base(Input, out var Output)
       {
          //"The value of 'Output' is " + Output
       }
    }

多个编译器选项

编辑
  • 使用公钥签名程序集:添加参数作为-public签名。
  • 从构建环境替换路径:用较早的映射源路径替换构建中的源路径:-pathmap:path1=sourcePath1,path2=sourcePath2

C# 8.0的特性

编辑
  1. 递归的模式匹配
  2. 在编译器可做类型推断的情况下,允许进一步省略类型声明

可释放的引用结构

编辑

声明为ref 的struct 无权实现任何接口,因为无权实现IDisposable 接口。必须使我们需要使用可释放的ref结构来访问void Dispose()方法。也可以配置readonly ref结构。

表达式形式的Switch关键字

编辑

switch用于模式匹配的表达式及其语法有所变化。

    public enum RandomNum
    {
        One,
        Three,
        Five,
        Seven,
        Six,
    }

    public static string getRandomNum(RandomNum iNum) =>
        iNum switch //switch关键字在变量之后使用
        {
            RandomNum.One => return "1",
            RandomNum.Three => return "3",
            RandomNum.Five => return "5",
            RandomNum.Seven => return "7",
            RandomNum.Six => return "6",
            _              => throw new Exception("invalid number value"), //默认关键字由_(下划线)符号代替
        };

空值结合赋值运算符

编辑

??=空值结合赋值运算符(null-coalescing):

some_Value ??= some_Value2;

以代替啰嗦的写法:

some_Value = some_Value ?? some_Value2;
    List<int> lstNum = null;
    int? a = null;
     
    (lstNum ??= new List<int>()).Add(100);
    Console.WriteLine(string.Join(" ", lstNum)); 
     
    // the output would be : 100
     
    lstNum.Add(a ??= 0);
    Console.WriteLine(string.Join(" ", numbers));  // output: 100 0
    // the output would be : 100 0

局部静态函数

编辑

将局部函数设为static函数,可证明局部函数不包含内部封闭循环中的变量。

    int Add()
    {
        int A = 5;
        return Sum(A);
     
        static int Sum(int val) => val + 3;
    }

默认接口方法

编辑

允许在声明接口时为接口成员提供默认实现。

=将结构成员设为只读

编辑

现在可以将struct成员设为只读,而不是将整个struct成员设置为只读。

    public readonly struct getSqure1 //老的实现
    {
        public int InputA { get; set; }
        public int InputB { get; set; }
        public int output => Math.Pow(A,B);
     
        public override string ToString() =>
            $"The answer is : {output} ";
    }

    public struct getSqure //新的实现
    {
        public int InputA { get; set; }
        public int InputB { get; set; }
        public readonly int output => Math.Pow(A,B);
     
        public override string ToString() =>
            $"The answer is : {output} ";
    }

插值字符串的增强

编辑

string以$开头,这样的string则称为插值string,可包含插值表达式:

    string szName = "ABC";
    int iCount = 15;
     
    // String interpolation:
    Console.WriteLine($"Hello, {szName}! You have {iCount} apples");//输出将为“ Hello, ABC! You have 15 apples”

逐字字符串以@"开头。在C#8中,$@""或者@$""表示可以通过@或$来开始string。在早期版本的C#中,@仅在$之后被允许。

stackalloc作为表达式

编辑

语法:stackalloc T[E] 其中T是非托管类型,E是类型int的表达式。stackalloc用作表达式,如果返回结果System.Span<T>。

    Span<int> num = stackalloc[] { 20, 30, 40 };
    var index = num.IndexOfAny(stackalloc[] { 20 });
    Console.WriteLine(index);  // output: 0

新型索引(Index )和区间(Range)类型

编辑

System.Index和System.Range用于从列表/数组中获取项。

^(脱字符)表示索引符号。通常,从数组中获取项目时,索引以开头0,但是当使用这种新类型时,其索引(indices)从结尾开始并向上。

..范围运算符获取其操作数的开始和结束。

    var simArray = new string[]
    {                 
        "one",         // 0      ^4
        "two",         // 1      ^3
        "three",       // 2      ^2
        "four",        // 3      ^1
    };

    Console.WriteLine("fetch using simple array " + simArray[1]);
    //above code gives output as "two"
    Console.WriteLine("fetch using indices caret operator " + simArray[^1]);
    //above code gives output as "four"

            var dd1 = simArray[..];
            //above code gives output as "one" to "four"
            var dd2 = simArray[..3];
            //above code gives output as "one" to "three"
            var dd3 = simArray[2..];
            //above code gives output as "three" to "four"

在上面的示例中,数组中有四个项;索引从0开始,以3结束;但是对于索引(indices)从^ 4()到^ 1。

using 关键字声明变量

编辑

using关键字声明变量,并在使用该变量的地方结束之前释放该变量:

    var ReadFile(string szpath)
    {
     using StreamReader StrFile = new StreamReader(szpath);
     string iCount;
     string szLine;
     while ((szLine = StrFile.ReadLine()) != null) 
      { 
      Console.WriteLine(szLine); 
      iCount++; 
      } 
     // 达到该方法的闭合括号,便会立即disposed该变量。
    }

    var ReadFile_OldStyle(string szpath)
    {
    using (StreamReader StrFile = new StreamReader(szpath))
     {
      string iCount;
      string szLine;
      while ((szLine = StrFile.ReadLine()) != null) 
      { 
      Console.WriteLine(szLine); 
      iCount++; 
      }     
     } //StrFile 对象在using 作用域结束后就被释放了
    }

消费异步流

编辑

异步流由一个方法执行,该方法使用IAsyncEnumerable<T>枚举器返回异步流。此方法声明为anync修饰符,此函数还包含yield return 语句用于返回异步流。为了消费anync流,我们需要在实际返回值之前使用await关键字。

    public async System.Collections.Generic.IAsyncEnumerable<int> asyncStream()
    {
        for (int iCount = 0; iCount < 10; iCount++)
        {
            await Task.Delay(10);
            yield return iCount;
        }
    }
    //call above method in mail method
    await foreach (var num in asyncStream())
    {
        Console.WriteLine(num);
    }

C# 9.0的特性

编辑

新的“Record”类型

编辑

记录类型, 是一种引用类型, 默认是不可变的。 记录类型的相等判断可以通过引用或者结构进行判断的。

  • 优点:记录类型是轻量级的不可变类型,可以减少大量的代码, 可以按照结构和引用进行比较;
  • 缺点:需要实例化大量的对象;
// 默认不可变的记录类型
public record Person(string Name, int Age);
// 可变记录类型
public record MutablePerson(string Name, int Age) {
 public string Name { get; set; } = Name;
 public int Age { get; set; } = Age;
}
 
var person1 = new Person("Alice", 40);
var person2 = new Person("Alice", 40);
 
Console.WriteLine(person1 == person2); // True 结构相同
Console.WriteLine(person1.Equals(person2)); // True 结构相同
Console.WriteLine(ReferenceEquals(person1, person2)); // False, 引用不同
 
// 改变默认的记录! --> 创建一个新的记录。
var person3 = person1 with { Age = 43 };
Console.WriteLine(person3 == person1); // False 结构不同
 
// 解构 (Destruct) 一个记录, 将记录的属性提取为本地变量
var (name, age) = person3;
 
var person4 = new MutablePerson("Alice", 40);
person4.Age = 43;
 
// 记录类型也可以被继承
public record Citizen(string Name, int Age, string Country) : Person(Name, Age);
var citizen = new Citizen("Alice", 40, "China");
Console.WriteLine(person1 == citizen); // False 类型不同;

“init”访问子

编辑

init存取子表示该属性所属类型仅能在建构函式(Constructor)中或是属性初始化式子中赋予其值,如果尝试在其他地方设定该属性的值,在编译时便会遭编译器阻止。

范例如下: 在这个范例中,建立了一个Student类型,并且属性StudentNameStudentID只能在初始化时赋予其值。

public class Student
{
	public string StudentName { get; init; } = "Default Name";
	public string StudentID { get; init; } = "00000000";
	public Student()
    {
		
	}
	public Student(string studentName,string studentID)
	{
		StudentName = studentName;
		StudentID = studentID;
	}
}

如果在此时撰写以下程式码:

Student DemoStudent = new Student();
DemoStudent.StudentName = "Test Name";

编译器便会无法编译并且掷回错误。

而如果要建立学生名称为“Test Name”,学生ID为“0001”的学生,则需要写成:

Student DemoStudent = new Student() //物件初始化運算式
{
    StudentName = "Test Name";
    StudentID = "0001"
};

或是

Student DemoStudent = new Student("Test Name","0001"); //藉由類型的建構式初始化StudentName以及StudentID。

最上层陈述式或称顶级语句

编辑

在以前的版本,开发者在撰写最上层陈述式(如Program.cs)程式码时,需要包含完整的namespace与class架构,因此如果要撰写Hello World程式时,程式码就会是:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

但是在C#9.0之后,最上层语句(top-level statement)的程式码不需要包含namespace以及class,可将其简化为:

using System;

Console.WriteLine("Hello World!");
//或者简化为一行语句:
System.Console.WriteLine(Hello World!);

注意, 一个程序中, 只能有一个文件使用顶级语句, 并且顶级语句必须位于命名空间或类型定义之前!

lambda弃元参数

编辑
Func<int, int, int> zero = (_, _) => 0;
Func<int, int, int> func = delegate (int _, int _) { return 0; };

在 C# 9 之前,即便不使用的 Lambda 参数也需要给它命名。C# 9 支持弃元参数一方面简化了命名,另一方面也节省了内存分配。更重要的是它使得编程的意图更明确,让人一看就知道这个参数是不用的,增强了代码的可读性和可维护性。

只能初始化的设置器

编辑

Init only setters,只能通过对象初始化进行赋值的属性。

public class InitDemo {
 public string Start { get; init; }
 public string Stop { get; init; }
}
 
// initDemo.Start = "Now"; // Error
// initDemo.End = "Tomorrow"; // Error
 
var initDemo = new InitDemo {
 Start = "Now",
 Stop = "Tomorrow"
};

函数指针

编辑

使用 delegate* 可以声明函数指针。

unsafe class FunctionPointer {
 static int GetLength(string s) => s.Length;
 delegate*<string, int> functionPointer = &GetLength;
}
 
public void Test() {
 Console.WriteLine(functionPointer("test")); // 4;
}

跳过本地初始化

编辑
[System.Runtime.CompilerServices.SkipLocalsInit]
static unsafe void DemoLocalsInit() {
 int x;
 // 注意, x 没有初始化, 输出结果不确定;
 Console.WriteLine(*&x);
}

原生整数类型

编辑

两个新的整数类型 nint 和 nunit , 依赖宿主机以及编译设定。

协变返回类型

编辑

协变返回类型为重写方法的返回类型提供了灵活性。覆盖方法可以返回从被覆盖的基础方法的返回类型派生的类型。

class Person { public virtual Person GetPerson() { return new Person(); } }
class Student : Person { public override  Student GetPerson() { return new Student(); } }

模块初始化代码

编辑

ModuleInitializerAttribute 为组件 (assembly) 定义初始化代码, 当初始化/加载时执行, 可以类比类的静态构造函数, 但是是组件级别的。

  • 必须是静态的、无参数的、无返回值的方法;
  • 不能是范型方法,也不能包含在范型类中;
  • 不能是私有函数,必须是公开 (public) 或者内部 (internal) 的函数;

静态 lambda 表达式

编辑

static 修饰符添加到 lambda 表达式或匿名方法 。这将无法捕获局部变量或实例状态,从而防止意外捕获其他变量。

分部方法扩展

编辑

移除了分部方法的下述限制:

  • 必须具有 void 返回类型。
  • 不能具有 out 参数。
  • 不能具有任何可访问性(隐式 private )。

初始化表达式的简化

编辑

如果创建对象的类型已知时,可以在new表达式中省略该类型。

Point p = new(1, 1);
Dictionary<string, int> dict = new();
 
Point[] points = { new(1, 1), new (2, 2), new (3, 3) };
var list = new List<Point> { new(1, 1), new(2, 2), new(3, 3)};

在本地函数上添加标记

编辑
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace CoreApp2
{  
    class Program
    {
        static void Main(string[] args)
        {
            [Conditional("DEBUG")]
            static void DoSomething([NotNull] string test)
            {
                System.Console.WriteLine("Do it!");
            }
            DoSomething("Doing!");
        }
   }
}

GetEnumerator 扩展

编辑

可以为任意类型添加一个 GetEnumerator 扩展方法, 返回一个 IEnumerator 或者 IAsyncEnumerator 实例, 从而在 foreach 循环中使用。

using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace CoreApp2
{
    public static class Extensions
    {
        public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
    }

    class Program
    {
        static void Main(string[] args)
        {
            IEnumerator<string> enumerator = new Collection<string> {"A", "B", "C"}.GetEnumerator();
            foreach (var item in enumerator)
            {
                Console.WriteLine(item);
            }
        }
    }
}

模式匹配增强

编辑

Type patterns 类型匹配,判断一个变量的类型

object obj = new int();
var type = obj switch {
 string => "string",
 int => "int",
 _ => "obj"
};
Console.WriteLine(type); // int

Relational patterns 关系匹配:

class Person { 
        public string name; 
        public int age; 
        public Person(string a, int b) { name = a;age = b; }
        public void Deconstruct(out string a,out int b){a = name;b = age; }
    }
class Program
    {        
        static void Main(string[] args)
        {
            var person1 = new Person("Alice", 40);
            var inRange = person1 switch
            {
                (_, < 18) => "less than 18",
                (_, > 18) => "greater than 18",
                (_, 18) => "18 years old!"
            };
            Console.WriteLine(inRange); // greater than 18
         }
     }

Conjunctive and patterns 逻辑合取匹配:

// And pattern
var person1 = new Person("Alice", 40);
var ageInRange = person1 switch {
 (_, < 18) => "less than 18",
 ("Zhang Zhimin", _) and (_, >= 18) => "Alice is greater than 18"
};
Console.WriteLine(ageInRange); // Alice is greater than 18

Disjunctive or patterns 逻辑析取匹配:

// Or pattern
var person1 = new Person("Alice", 40);
var ageInRange = person1 switch {
 (_, < 18) => "less than 18",
 (_, 18) or (_, > 18) => "18 or greater"
};
Console.WriteLine(ageInRange); // 18 or greater

Negated not patterns 逻辑非匹配

// Not pattern
var person1 = new Person("Alice", 40);
var meOrNot = person1 switch {
 not ("Alice", 40) => "Not me!",
 _ => "Me :-)"
};
Console.WriteLine(meOrNot); // Me :-)

Parenthesized patterns 带括号的优先级匹配:

// Parenthesized patterns
var is10 = new IsNumber(true, 10);
var n10 = is10 switch {
 ((_, > 1 and < 5) and (_, > 5 and < 9)) or (_, 10) => "10",
 _ => "not 10"
};
Console.WriteLine(n10); // 10

C# 10.0的特性

编辑

record struct

编辑

解决了 record 只能给 class 而不能给 struct 用的问题:

record struct Point(int X, int Y);

sealed record ToString 方法

编辑

可以把 record 里的 ToString 方法标记成 sealed

结构的无参数构造函数

编辑

无参数的结构体构造函数(Parameterless constructors in structs)。无参构造函数使得new struct() 和 default(struct) 的语义不一样。

        struct Person
        {
            public string Name { get; }
            public int Age { get; }
            public Person(string a, int b) { Name = a; Age = b; }
            public Person() : this("Joe", 40) {}
        }

用with创建新的匿名类型对象

编辑
   var x = new { A = 1, B = 2 };
   var y = x with { A = 3 };

这里 y.A 将会是 3 。

全局的 using

编辑

可以给整个项目启用 using,不需要每个文件都写一份。

文件范围的 namespace

编辑

以前写 namespace 还得带一层大括号。现在如果一个文件里只有一个 namespace 的话,直接在文件开头写: namespace MyNamespace;

常量字符串插值

编辑
   const string x = "hello";
   const string y = $"{x}, world!";

lambda的改进

编辑

lambda 可以带 attributes

编辑
   f = [Foo] (x) => x; // 给 lambda 设置
   f = [return: Foo] (x) => x; // 给 lambda 返回值设置
   f = ([Foo] x) => x; // 给 lambda 参数设置

指定返回值类型

编辑

此前 C# 的 lambda 返回值类型靠推导,C# 10允许在参数列表之前显式指定 lambda 返回值类型:

f = int () => 4;

支持 ref 、in 、out 等修饰

编辑
f = ref int (ref int x) => ref x; // 返回一个参数的引用

头等函数

编辑

函数可以隐式转换到 delegate,于是函数上升为头等函数(first function):

   void Foo() { Console.WriteLine("hello"); }
   var x = Foo;
   x(); // hello

自然委托类型

编辑

lambda 可自动创建自然委托类型,于是不再需要写出类型:

   var f = () => 1; // Func<int>
   var g = string (int x, string y) => $"{y}{x}"; // Func<int, string, string>
   var h = "test".GetHashCode; // Func<int>

CallerArgumentExpression

编辑

使用CallerArgumentExpression这个attribute,编译器会自动填充调用参数的表达式字符串,例如:

   void Foo(int value, [CallerArgumentExpression("value")] string? expression = null)
   {
       Console.WriteLine(expression + " = " + value);
   }

当你调用 Foo(4 + 5) 时,会输出 4 + 5 = 9。这对测试框架极其有用

tuple 的混合定义和使用

编辑
   int y = 0;
   (var x, y, var z) = (1, 2, 3);

于是 y 就变成 2 了,同时还创建了两个变量 x 和 z,分别是 1 和 3 。

接口支持抽象静态方法

编辑

.NET 6中这个特性为preview特性。

泛型 attribute

编辑

在方法上指定 AsyncMethodBuilder

编辑

在方法上用 [AsyncMethodBuilder(...)],来使用自己实现的 async method builder,代替自带的 Task 或者 ValueTask 的异步方法构造器。有助于实现零开销的异步方法。

line 指示器支持行列和范围

编辑

以前 #line 只能用来指定一个文件中的某一行,现在可以指定行列和范围:

   #line (startLine, startChar) - (endLine, endChar) charOffset "fileName"
    
   // 比如 #line (1, 1) - (2, 2) 3 "test.cs"

嵌套属性模式匹配改进

编辑

以前在匹配嵌套属性的时候需要这么写:

if (a is { X: { Y: { Z: 4 } } }) { ... }

现在只需要简单的:

if (a is { X.Y.Z: 4 }) { ... }

改进的字符串插值

编辑

实现接近零开销的字符串插值。

Source Generator v2

编辑

包括强类型的代码构建器,以及增量编译的支持等

参见

编辑

参考文献

编辑
  1. ^ An Introduction to C# Generics. [2020-09-25]. (原始内容存档于2019-09-24). 
  2. ^ Anonymous Methods (C#). [2008-10-24]. (原始内容存档于2008-04-17). 
  3. ^ Covariance and Contravariance in Delegates (C#). [2008-10-24]. (原始内容存档于2008-10-12). 
  4. ^ Tim Anderson. C# pulling ahead of Java - Lead architect paints rosy C# picture. Reg Developer. The Register. 2006-11-14 [2007-01-20]. (原始内容存档于2007-01-21). 
  5. ^ LINQ. Microsoft MSDN. 2007 [2007-08-13]. (原始内容存档于2007-01-16) (英语). 
  6. ^ The Mellow Musings of Dr. T : What is a collection?. [2008-10-24]. (原始内容存档于2008-12-18). 
  7. ^ Partial Methods. [2007-10-06]. (原始内容存档于2007-10-16). 
  8. ^ 一覽 C# 7.0 中的新功能. [2016-09-14]. (原始内容存档于2018-10-02). 
  1. Archer, Tom. Inside C#. Microsoft Press. 2001. ISBN 0-7356-1288-9. 
  2. Bart de Smet on Spec#页面存档备份,存于互联网档案馆

外部链接

编辑