带你了解C#每个版本新特性

640?wx_fmt=jpeg

上学时学习C#和.NET,当时网上的资源不像现在这样丰富,所以去电脑城买了张盗版的VS2005的光盘,安装时才发现是VS2003,当时有一种被坑的感觉,但也正是如此,让我有了一个完整的.NET的学习生涯。

一直都认为学习语言应该系统的进行学习,了解每一个版本的新增特性,才能在实际应用中做到有的放矢。最近发现团队中有不少人虽然用着最新的技术,但知识储备还停留在一个比较初始的状态,这样在编码过程中会走不少弯路。

本文梳理下C#从1.0到7.0版本的一些常用特性,对于不常用的或者我没有用到过的一些特性,会列出来,但不会做详细描述。另外C#8.0现在还没有正式推出,并且目前我们也只是在使用dotNet Core2.1,所以C#8.0本文也不会涉及。

C#1.X

C#VS版本CLR版本.NET Framework
1.0VS20021.01.0
1.1VS20031.11.1

在C#1.0或1.1版本中,从语言的角度就是基本的面向对象的语法,可以说任何一本C#语言的书籍都包含了C#1.X的所有内容。

如果您已经在使用C#语言编写代码,那么C#1.X的相关知识应该已经掌握。基础语法部分这里就不再赘述了。

C#2.0

C#VS版本CLR版本.NET Framework
2.0VS20052.02.0

2.0中对应VS2005我用的也不多,因为很快就被VS2008替代了,不过在语言方面却带来了很多新的东西。

泛型

C#2中最重要的一个特性应该就是泛型。泛型的用处就是在一些场景下可以减少强制转换来提高性能。在C#1中就有很多的强制转换,特别是对一些集合进行遍历时,如ArrayList、HashTable,因为他们是为不同数据类型设计的集合,所以他们中键和值的类型都是object,这就意味着会平凡发生装箱拆箱的操作。C#2中有了泛型,所以我们可以使用List、Dictionary。泛型能够带来很好的编译时类型检查,也不会有装箱拆箱的操作,因为类型是在使用泛型的时候就已经指定了。

.NET已经通过了很多的泛型类型供我们使用,如上面提到的List,Dictionary,我们也可以自己来创建泛型类型(类、接口、委托、结构)或是方法。在定义泛型类型或时可以通过定义泛型约束来对泛型参数进行限制,更好的使用编译时检查。泛型约束是通过关键字where来实现的,C#2中的泛型约束有4种:

分部类(Partil)

分部类可以允许我们在多个文件中为一个类型(class、struct、interface)编写代码,在Asp.Net2.0中用的极为广泛。新建一个Aspx页面,页面的CodeBehind和页面中的控件的定义就是通过分部类来实现的。如下:

public partial class _Default : System.Web.UI.Page public partial class _Default class _Default : System.Web.UI.Page 
public partial class _Default 

分部类使用关键字partial来定义,当一个类中的代码非常多时,可以使用分部类来进行拆分,这对代码的阅读很有好处,而且不会影响调用。不过现在我们前后端分离,后端代码要做到单一职责原则,不会有很多大的类,所以这个特性很少用到。

静态类

静态类中的公用方法必须也是静态的,可以由类名直接调用,不需要实例化,比较适用于编写一些工具类。如System.Math类就是静态类。工具类有一些特点,如:所有成员都是静态的、不需要被继承、不需要进行实例化。在C#1中我们可以通过如下代码来实现:

//声明为密封类防止被继承 public sealed class StringHelper{    //添加私有无参构造函ˉ数防止被实例化,如果不添加私有构造函数     //会自动生成共有无参构造函数     private StringHelper(){};    public static int StringToInt32(string input)    {        int result=0;        Int32.TryParse(input, out result);        return result;    }}
public sealed class StringHelper
{
    //添加私有无参构造函ˉ数防止被实例化,如果不添加私有构造函数 
    //会自动生成共有无参构造函数 
    private StringHelper(){};
    public static int StringToInt32(string input)
    
{
        int result=0;
        Int32.TryParse(input, out result);
        return result;
    }
}

C#2中可以使用静态类来实现:

public static class StringHelper{    public static int StringToInt32(string input)    {        int result=0;        Int32.TryParse(input, out result);        return result;    }}static class StringHelper
{
    public static int StringToInt32(string input)
    
{
        int result=0;
        Int32.TryParse(input, out result);
        return result;
    }
}

属性的访问级别

在C#1中声明属性,属性中的get和set的访问级别是和属性一致,要么都是public要么都是private,如果要实现get和set有不同的访问级别,则需要用一种变通的方式,自己写GetXXX和SetXXX方法。在C#2中可以单独设置get和set的访问级别,如下:

private string _name;public string Name{    get { return _name; }    private set { _name = value; }}string _name;
public string Name
{
    get { return _name; }
    private set { _name = value; }
}

需要注意的是,不能讲属性设置为私有的,而将其中的get或是set设置成公有的,也不能给set和get设置相同的访问级别,当set和get的访问级别相同时,我们可以直接设置在属性上。

命名空间别名

命名空间可以用来组织类,当不同的命名空间中有相同的类时,可以使用完全限定名来防止类名的冲突,C#1中可以使用空间别名来简化书写,空间别名用using关键字实现。但还有一些特殊情况,使用using并不能完全解决,所以C#2中提供了下面几种特性:

我们在构建命名空间和类的时候,尽量避免出现冲突的情况,这个特性也较少用到。

友元程序集

当我们希望一个程序集中的类型可以被外部的某些程序集访问,这时如果设置成Public,就可以被所有的外部程序集访问。怎样只让部分程序集访问,就要使用友元程序集了,具体参考之前的博文《C#:友元程序集(http://blog.fwhyy.com/2010/11/csharp-a-friend-assembly/)》

可空类型

可空类型就是允许值类型的值为null。通常值类型的值是不应该为null的,但我们很多应用是和数据库打交道的,而数据库中的类型都是可以为null值的,这就造成了我们写程序的时候有时需要将值类型设置为null。在C#1中通常使用”魔值“来处理这种情况,比如DateTiem.MinValue、Int32.MinValue。在ADO.NET中所有类型的空值可以用DBNull.Value来表示。C#2中可空类型主要是使用System.Nullable的泛型类型,类型参数T有值类型约束。可以像下面这样来定义可空类型:

Nullable<int> i = 20;Nullable<bool> b = true;20;
Nullable<bool> b = true;

C#2中也提供了更方便的定义方式,使用操作符?:

int? i = 20;bool? b = true;20;
bool? b = true;

迭代器

C#2中对迭代器提供了更便捷的实现方式。提到迭代器,有两个概念需要了解

看下面一个例子:

public class Test {    static void Main()    {        Person arrPerson = new Person("oec2003","oec2004","oec2005");        foreach (string p in arrPerson)        {            Console.WriteLine(p);        }        Console.ReadLine();    }}public class Person:IEnumerable {    public Person(params string[] names)    {        _names = new string[names.Length];        names.CopyTo(_names, 0);    }    public string[] _names;    public IEnumerator GetEnumerator()    {        return new PersonEnumerator(this);    }    private string this[int index]    {        get { return _names[index]; }        set { _names[index] = value; }    }}public class PersonEnumerator : IEnumerator {    private int _index = -1;    private Person _p;    public PersonEnumerator(Person p) { _p = p; }    public object Current    {        get { return _p._names[_index]; }    }    public bool MoveNext()    {        _index++;        return _index < _p._names.Length;    }    public void Reset()    {        _index = -1;    }}class Test 
{
    static void Main()
    
{
        Person arrPerson = new Person("oec2003","oec2004","oec2005");
        foreach (string p in arrPerson)
        {
            Console.WriteLine(p);
        }
        Console.ReadLine();
    }
}
public class Person:IEnumerable 
{
    public Person(params string[] names)
    
{
        _names = new string[names.Length];
        names.CopyTo(_names, 0);
    }
    public string[] _names;
    public IEnumerator GetEnumerator()
    
{
        return new PersonEnumerator(this);
    }
    private string this[int index]
    {
        get { return _names[index]; }
        set { _names[index] = value; }
    }
}
public class PersonEnumerator : IEnumerator 
{
    private int _index = -1;
    private Person _p;
    public PersonEnumerator(Person p{ _p = p; }
    public object Current
    {
        get { return _p._names[_index]; }
    }
    public bool MoveNext()
    
{
        _index++;
        return _index < _p._names.Length;
    }
    public void Reset()
    
{
        _index = -1;
    }
}

C#2中的迭代器变得非常便捷,使用关键字yield return关键字实现,下面是C#2中使用yield return的重写版本:

public class Test {    static void Main()    {        Person arrPerson = new Person("oec2003","oec2004","oec2005");        foreach (string p in arrPerson)        {            Console.WriteLine(p);        }        Console.ReadLine();    }}public class Person:IEnumerable {    public Person(params string[] names)    {        _names = new string[names.Length];        names.CopyTo(_names, 0);    }    public string[] _names;    public IEnumerator GetEnumerator()    {        foreach (string s in _names)        {            yield return s;        }    }}class Test 
{
    static void Main()
    
{
        Person arrPerson = new Person("oec2003","oec2004","oec2005");
        foreach (string p in arrPerson)
        {
            Console.WriteLine(p);
        }
        Console.ReadLine();
    }
}
public class Person:IEnumerable 
{
    public Person(params string[] names)
    
{
        _names = new string[names.Length];
        names.CopyTo(_names, 0);
    }
    public string[] _names;
    public IEnumerator GetEnumerator()
    
{
        foreach (string s in _names)
        {
            yield return s;
        }
    }
}

匿名方法

匿名方法比较适用于定义必须通过委托调用的方法,用多线程来举个例子,在C#1中代码如下:

private void btnTest_Click(object sender, EventArgs e){    Thread thread = new Thread(new ThreadStart(DoWork));    thread.Start();}private void DoWork(){    for (int i = 0; i < 100; i++)    {        Thread.Sleep(100);        this.Invoke(new Action<string>(this.ChangeLabel),i.ToString());    }}private void ChangeLabel(string i){    label1.Text = i + "/100";}
    Thread thread = new Thread(new ThreadStart(DoWork));
    thread.Start();
}
private void DoWork()
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(100);
        this.Invoke(new Action<string>(this.ChangeLabel),i.ToString());
    }
}
private void ChangeLabel(string i)
{
    label1.Text = i + "/100";
}

使用C#2中的匿名方法,上面的例子中可以省去DoWork和ChangeLabel两个方法,代码如下:

private void btnTest_Click(object sender, EventArgs e){    Thread thread = new Thread(new ThreadStart(delegate() {        for (int i = 0; i < 100; i++)        {            Thread.Sleep(100);            this.Invoke(new Action(delegate() { label1.Text = i + "/100"; }));        }    }));    thread.Start();}
    Thread thread = new Thread(new ThreadStart(delegate() {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(100);
            this.Invoke(new Action(delegate() { label1.Text = i + "/100"; }));
        }
    }));
    thread.Start();
}

其他相关特性

C#3.0

C#VS版本CLR版本.NET Framework
3.0VS20082.03.0 3.5

如果说C#2中的核心是泛型的话,那么C#3中的核心就应是Linq了,C#3中的特性几乎都是为Linq服务的,但每一项特性都可以脱离Linq来使用。下面就来看下C#3中有哪些特性。

自动实现的属性

这个特性非常简单,就是使定义属性变得更简单了。代码如下:

public string Name { get; set; }public int Age { private set; get; }string Name { getset; }
public int Age { private setget; }

隐式类型的局部变量和扩展方法

隐式类型的局部变量是让我们在定义变量时可以比较动态化,使用var关键字作为类型的占位符,然后由编译器来推导变量的类型。

扩展方法可以在现有的类型上添加一些自定义的方法,比如可以在string类型上添加一个扩展方法ToInt32,就可以像“20”.ToInt32()这样调用了。

具体参见《C#3.0学习(1)—隐含类型局部变量和扩展方法(http://blog.fwhyy.com/2008/02/learning-csharp-3-0-1-implied-type-of-local-variables-and-extension-methods/)》。

隐式类型虽然让编码方便了,但有些不少限制:

对象集合初始化器

简化了对象和集合的创建,具体参见《C#3.0学习(2)—对象集合初始化器(http://blog.fwhyy.com/2008/02/learning-c-3-0-2-object-collection-initializer/)》。

隐式类型的数组

和隐式类型的局部变量类似,可以不用显示指定类型来进行数组的定义,通常我们定义数组是这样:

string[] names = { "oec2003", "oec2004", "oec2005" };"oec2003""oec2004""oec2005" };

使用匿名类型数组可以想下面这样定义:

protected void Page_Load(object sender, EventArgs e){    GetName(new[] { "oec2003", "oec2004", "oec2005" });}public string GetName(string[] names){    return names[0];}
    GetName(new[] { "oec2003""oec2004""oec2005" });
}
public string GetName(string[] names)
{
    return names[0];
}

匿名类型

匿名类型是在初始化的时候根据初始化列表自动产生类型的一种机制,利用对象初始化器来创建匿名对象的对象,具体参见《C#3.0学习(3)—匿名类型(http://blog.fwhyy.com/2008/03/learning-csharp-3-0-3-anonymous-types/)》。

Lambda表达式

实际上是一个匿名方法,Lambda表达的表现形式是:(参数列表)=>{语句},看一个例子,创建一个委托实例,获取一个string类型的字符串,并返回字符串的长度。代码如下:

Func<string, int> func = delegate(string s) { return s.Length; };Console.WriteLine(func("oec2003"));int> func = delegate(string s) { return s.Length; };
Console.WriteLine(func("oec2003"));

使用Lambda的写法如下:

Func<string, int> func = (string s)=> { return s.Length; };Func<string, int> func1 = (s) => { return s.Length; };Func<string, int> func2 = s => s.Length;intfunc = (string s)=> { return s.Length; };
Func<stringint> func1 = (s) => { return s.Length; };
Func<stringint> func2 = s => s.Length;

上面三种写法是逐步简化的过程。

Lambda表达式树

是.NET3.5中提出的一种表达方式,提供一种抽象的方式将一些代码表示成一个对象树。要使用Lambda表达式树需要引用命名空间System.Linq.Expressions,下面代码构建一个1+2的表达式树,最终表达式树编译成委托来得到执行结果:

Expression a = Expression.Constant(1);Expression b = Expression.Constant(2);Expression add = Expression.Add(a, b);Console.WriteLine(add); //(1+2) Func<int> fAdd = Expression.Lambda<Func<int>>(add).Compile();Console.WriteLine(fAdd()); //3 
Expression b = Expression.Constant(2);
Expression add = Expression.Add(a, b);
Console.WriteLine(add); //(1+2) Func<int> fAdd = Expression.Lambda<Func<int>>(add).Compile();
Console.WriteLine(fAdd()); //3 

Lambda和Lambda表达式树为我们使用Linq提供了很多支持,如果我们在做的一个管理系统使用了Linq To Sql,在列表页会有按多个条件来进行数据的筛选的功能,这时就可以使用Lambda表达式树来进行封装查询条件,下面的类封装了And和Or两种条件:

public static class DynamicLinqExpressions {    public static Expression<Func<T, bool>> True<T>() { return f => true; }    public static Expression<Func<T, bool>> False<T>() { return f => false; }    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,                                                        Expression<Func<T, bool>> expr2)    {        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());        return Expression.Lambda<Func<T, bool>>              (Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);    }    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,                                                         Expression<Func<T, bool>> expr2)    {        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());        return Expression.Lambda<Func<T, bool>>              (Expression.And(expr1.Body, invokedExpr), expr1.Parameters);    }}static class DynamicLinqExpressions 
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                        Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
              (Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
                                                         Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
              (Expression.And(expr1.Body, invokedExpr), expr1.Parameters);
    }
}

下面是获取条件的方法:

public Expression<Func<Courses, bool>> GetCondition(){    var exp = DynamicLinqExpressions.True<Courses>();    if (txtCourseName.Text.Trim().Length > 0)    {        exp = exp.And(g => g.CourseName.Contains(txtCourseName.Text.Trim()));    }    if (ddlGrade.SelectedValue != "-1")    {        exp=exp.And(g => g.GradeID.Equals(ddlGrade.SelectedValue));    }    return exp;}
{
    var exp = DynamicLinqExpressions.True<Courses>();
    if (txtCourseName.Text.Trim().Length > 0)
    {
        exp = exp.And(g => g.CourseName.Contains(txtCourseName.Text.Trim()));
    }
    if (ddlGrade.SelectedValue != "-1")
    {
        exp=exp.And(g => g.GradeID.Equals(ddlGrade.SelectedValue));
    }
    return exp;
}

Linq

Linq是一个很大的话题,也是NET3.5中比较核心的内容,有很多书籍专门来介绍Linq,下面只是做一些简单的介绍,需要注意的是Linq并非是Linq To Sql,Linq是一个大的集合,里面包含:

下面以Linq To Object为例子来看看Linq是怎么使用的:

public class UserInfo {    public string Name { get; set; }    public int Age { get; set; }}public class Test {    static void Main()    {        List<UserInfo> users = new List<UserInfo>()        {            new UserInfo{Name="oec2003",Age=20},            new UserInfo{Name="oec2004",Age=21},            new UserInfo{Name="oec2005",Age=22}        };        IEnumerable<UserInfo> selectedUser = from user in users                                             where user.Age > 20                                             orderby user.Age descending select user;        foreach (UserInfo user in selectedUser)        {            Console.WriteLine("姓名:"+user.Name+",年龄:"+user.Age);        }        Console.ReadLine();    }}class UserInfo 
{
    public string Name { getset; }
    public int Age { getset; }
}
public class Test 
{
    static void Main()
    
{
        List<UserInfo> users = new List<UserInfo>()
        {
            new UserInfo{Name="oec2003",Age=20},
            new UserInfo{Name="oec2004",Age=21},
            new UserInfo{Name="oec2005",Age=22}
        };
        IEnumerable<UserInfo> selectedUser = from user in users
                                             where user.Age > 20
                                             orderby user.Age descending select user;
        foreach (UserInfo user in selectedUser)
        {
            Console.WriteLine("姓名:"+user.Name+",年龄:"+user.Age);
        }
        Console.ReadLine();
    }
}

可以看出,Linq可以让我们使用类似Sql的关键字来对集合、对象、XML等进行查询。

C#4.0

C#VS版本CLR版本.NET Framework
4.0VS20104.04.0

可选参数

VB在很早就已经支持了可选参数,而C#知道4了才支持,顾名思义,可选参数就是一些参数可以是可选的,在方法调用的时候可以不用输入。看下面代码:

public class Test {    static void Main()    {        Console.WriteLine(GetUserInfo()); //姓名:ooec2003,年龄:30         Console.WriteLine(GetUserInfo("oec2004", 20));//姓名:ooec2004,年龄:20         Console.ReadLine();    }    public static string GetUserInfo(string name = "oec2003", int age = 30)    {        return "姓名:" + name + ",年龄:" + age.ToString();    }}class Test 
{

    static void Main()
    
{
        Console.WriteLine(GetUserInfo()); //姓名:ooec2003,年龄:30 
        Console.WriteLine(GetUserInfo("oec2004"20));//姓名:ooec2004,年龄:20 
        Console.ReadLine();
    }
    public static string GetUserInfo(string name = "oec2003"int age = 30)
    
{
        return "姓名:" + name + ",年龄:" + age.ToString();
    }
}

命名实参

命名实参是在制定实参的值时,可以同时指定相应参数的名称。编译器可以判断参数的名称是否正确,命名实参可以让我们在调用时改变参数的顺序。命名实参也经常和可选参数一起使用,看下面的代码:

static void Main(){    Console.WriteLine(Cal());//9     Console.WriteLine(Cal(z: 5, y: 4));//25     Console.ReadLine();}public static int Cal(int x=1, int y=2, int z=3){    return (x + y) * z;}
    Console.WriteLine(Cal());//9 
    Console.WriteLine(Cal(z: 5, y: 4));//25 
    Console.ReadLine();
}
public static int Cal(int x=1int y=2int z=3)
{
    return (x + y) * z;
}

通过可选参数和命名参数的结合使用,我们可以减少代码中方法的重载。

动态类型

C#使用dynamic来实现动态类型,在没用使用dynamic的地方,C#依然是静态的。静态类型中当我们要使用程序集中的类,要调用类中的方法,编译器必须知道程序集中有这个类,类里有这个方法,如果不能事先知道,编译时会报错,在C#4以前可以通过反射来解决这个问题。看一个使用dynamic的小例子:

dynamic a = "oec2003";Console.WriteLine(a.Length);//7 Console.WriteLine(a.length);//string 类型不包含length属性,但编译不会报错,运行时会报错 Console.ReadLine();"oec2003";
Console.WriteLine(a.Length);//7 
Console.WriteLine(a.length);//string 类型不包含length属性,但编译不会报错,运行时会报错 
Console.ReadLine();

您可能会发现使用dynamic声明变量和C#3中提供的var有点类似,其他他们是有本质区别的,var声明的变量在编译时会去推断出实际的类型,var只是相当于一个占位符,而dynamic声明的变量在编译时不会进行类型检查。

dynamic用的比较多的应该是替代以前的反射,而且性能有很大提高。假设有一个名为DynamicLib的程序集中有一个DynamicClassDemo类,类中有一个Cal方法,下面看看利用反射怎么访问Cal方法:

namespace DynamicLib{    public class DynamicClassDemo     {        public int Cal(int x = 1, int y = 2, int z = 3)        {            return (x + y) * z;        }    }}static void Main(){    Assembly assembly = Assembly.Load("DynamicLib");    object obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");    Type type = obj.GetType();    MethodInfo method = type.GetMethod("Cal");    Console.WriteLine(method.Invoke(obj, new object[] { 1, 2, 3 }));//9     Console.ReadLine();}DynamicLib
{
    public class DynamicClassDemo 
    {
        public int Cal(int x = 1int y = 2int z = 3)
        
{
            return (x + y) * z;
        }
    }
}
static void Main()
{
    Assembly assembly = Assembly.Load("DynamicLib");
    object obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");
    Type type = obj.GetType();
    MethodInfo method = type.GetMethod("Cal");
    Console.WriteLine(method.Invoke(obj, new object[] { 123 }));//9 
    Console.ReadLine();
}

用dynamic的代码如下:

Assembly assembly = Assembly.Load("DynamicLib");dynamic obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");Console.WriteLine(obj.Cal());Console.ReadLine();assembly = Assembly.Load("DynamicLib");
dynamic obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");
Console.WriteLine(obj.Cal());
Console.ReadLine();

在前后端分离的模式下,WebAPI接口的参数也可以采用dynamic来定义,直接就可以解析前端传入的json参数,不用每一个接口方法都定义一个参数类型。不好的地方就是通过Swagger来生产API文档时,不能明确的知道输入参数的每个属性的含义。

C#4中还有一些COM互操作性的改进和逆变性和协变性的改进,我几乎没有用到,所以在此就不讲述了。

C#5.0

C#VS版本CLR版本.NET Framework
5.0VS2012\20134.04.5

异步处理

异步处理是C#5中很重要的一个特性,会涉及到两个关键字:async和await,要讲明白这个需要单独写一篇来介绍。

可以简单理解为,当Winform窗体程序中有一个耗时操作时,如果是同步操作,窗体在返回结果之前会卡死,当然在C#5之前的版本中有多种方法可以来解决这个问题,但C#5的异步处理解决的更优雅。

循环中捕获变量

与其说是一个特性,不如说是对之前版本问题的修复,看下面的代码:

public static void CapturingVariables(){    string[] names = { "oec2003","oec2004","oec2005"};    var actions = new List<Action>();    foreach(var name in names)    {        actions.Add(() => Console.WriteLine(name));    }    foreach(Action action in actions)    {        action();    }}
    string[] names = { "oec2003","oec2004","oec2005"};
    var actions = new List<Action>();

    foreach(var name in names)
    {
        actions.Add(() => Console.WriteLine(name));
    }
    foreach(Action action in actions)
    {
        action();
    }
}

这段代码在之前的C#版本中,会连续输出三个oec2005,在C#5中会按照我们的期望依次输出oec2003、oec2004、oec2005。

如果您的代码在之前的版本中有利用到这个错误的结果,那么在升级到C#5或以上版本中就要注意了。

调用者信息特性

我们的程序通常是以release形式发布,发布后很难追踪到代码执行的具体信息,在C#5中提供了三种特性(Attribute), 允许获取调用者的当前编译器的执行文件名、所在行数与方法或属性名称。代码如下:

static void Main(string[] args){    ShowInfo();    Console.ReadLine();}public static void ShowInfo(   [CallerFilePath] string file = null,   [CallerLineNumber] int number = 0,   [CallerMemberName] string name = null){    Console.WriteLine($"filepath:{file}");    Console.WriteLine($"rownumber:{number}");    Console.WriteLine($"methodname:{name}");}
    ShowInfo();

    Console.ReadLine();

}
public static void ShowInfo(
   [CallerFilePath] string file = null,
   [CallerLineNumber] int number = 0,
   [CallerMemberName] string name = null
)
{
    Console.WriteLine($"filepath:{file}");
    Console.WriteLine($"rownumber:{number}");
    Console.WriteLine($"methodname:{name}");
}

调用结果如下:

filepath:/Users/ican_macbookpro/Projects/CsharpFeature/CsharpFeature5/Program.csrownumber:12methodname:Main
rownumber:12
methodname:Main

C#6.0

C#VS版本CLR版本.NET Framework
6.0VS20154.04.6

在C#6中提供了不少的新功能,我认为最有用的就是Null条件运算符和字符串嵌入。

Null条件运算符

在C#中,一个常见的异常就是“未将对象引用到对象的实例”,原因是对引用对象没有做非空判断导致。在团队中虽然再三强调,但依然会在这个问题上栽跟头。下面的代码就会导致这个错误:

class Program{    static void Main(string[] args)    {        //Null条件运算符        User user = null;        Console.WriteLine(user.GetUserName());        Console.ReadLine();    }}class User{    public string GetUserName() => "oec2003";}Program
{
    static void Main(string[] args)
    
{
        //Null条件运算符
        User user = null;
        Console.WriteLine(user.GetUserName());
        Console.ReadLine();
    }
}
class User
{
    public string GetUserName() => "oec2003";
}

要想不出错,就需要对user对象做非空判断

if(user!=null){    Console.WriteLine(user.GetUserName()); }null)
{
    Console.WriteLine(user.GetUserName()); 
}

在C#6中可以用很简单的方式来处理这个问题

//Null条件运算符User user = null;Console.WriteLine(user?.GetUserName()); 
User user = null;
Console.WriteLine(user?.GetUserName()); 

注:虽然这个语法糖非常简单,也很好用,但在使用时也需要多想一步,当对象为空时,调用其方法返回的值也是空,这样的值对后续的操作会不会有影响,如果有,还是需要做判断,并做相关的处理。

字符串嵌入

字符串嵌入可以简化字符串的拼接,很直观的就可以知道需要表达的意思,在C#6及以上版本中都应该用这种方式来处理字符串拼接,代码如下:

//字符串嵌入string name = "oec2003";//之前版本的处理方式1Console.WriteLine("Hello " + name);//之前版本的处理方式2Console.WriteLine(string.Format("Hello {0}",name));//C#6字符串嵌入的处理方式Console.WriteLine($"Hello {name}");
string name = "oec2003";
//之前版本的处理方式1
Console.WriteLine("Hello " + name);
//之前版本的处理方式2
Console.WriteLine(string.Format("Hello {0}",name));
//C#6字符串嵌入的处理方式
Console.WriteLine($"Hello {name}");

其他相关特性

C#7.0

C#VS版本.NET Framework
7.0VS2017 15.0.NET Core1.0
7.1VS2017 15.3.NET Core2.0
7.2VS2017 15.5.NET Core2.0
7.3VS2017 15.7.NET Core2.1

out 变量

此特性简化了out变量的使用,之前的版本中使用代码如下:

int result = 0;int.TryParse("20", out result);Console.WriteLine(result);0;
int.TryParse("20"out result);
Console.WriteLine(result);

优化后的代码,不需要事先定义一个变量

int.TryParse("20", out var result);Console.WriteLine(result);"20"out var result);
Console.WriteLine(result);

模式匹配

这也是一个减少我们编码的语法糖,直接看代码吧

public class PatternMatching{    public void Test()    {        List<Person> list = new List<Person>();        list.Add(new Man());        list.Add(new Woman());        foreach (var item in list)        {                 //在之前版本中此处需要做类型判断和类型转换            if (item is Man man)                Console.WriteLine(man.GetName());            else if (item is Woman woman)                Console.WriteLine(woman.GetName());        }    }}public abstract class Person{    public abstract string GetName();}public class Man:Person{    public override string GetName() => "Man";}public class Woman : Person{    public override string GetName() => "Woman";}class PatternMatching
{
    public void Test()
    
{
        List<Person> list = new List<Person>();
        list.Add(new Man());
        list.Add(new Woman());
        foreach (var item in list)
        {
                 //在之前版本中此处需要做类型判断和类型转换
            if (item is Man man)
                Console.WriteLine(man.GetName());
            else if (item is Woman woman)
                Console.WriteLine(woman.GetName());
        }
    }
}
public abstract class Person
{
    public abstract string GetName();
}
public class Man:Person
{
    public override string GetName() => "Man";
}
public class Woman : Person
{
    public override string GetName() => "Woman";
}

详细参考官方文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/pattern-matching

本地方法

可以在方法中写内部方法,在方法中有时需要在多个代码逻辑执行相同的处理,之前的做法是在类中写私有方法,现在可以让这个私有方法写在方法的内部,提高代码可读性。

static void LocalMethod(){    string name = "oec2003";    string name1 = "oec2004";    Console.WriteLine(AddPrefix(name));    Console.WriteLine(AddPrefix(name1));    string AddPrefix(string n)    {        return $"Hello {n}";    }}
    string name = "oec2003";
    string name1 = "oec2004";

    Console.WriteLine(AddPrefix(name));
    Console.WriteLine(AddPrefix(name1));

    string AddPrefix(string n)
    
{
        return $"Hello {n}";
    }
}

异步 main 方法

这个最大的好处是,在控制台程序中调试异步方法变得很方便。

static async Task Main(){    await SomeAsyncMethod();}
    await SomeAsyncMethod();
}

private protected 访问修饰符

可以限制在同一个程序集中的派生类的访问,是对protected internal的一种补强,protected internal是指同一程序集中的类或派生类进行访问。

其他相关特性

总结

每个特性都需要我们去编码实现下,了解了真正的含义和用途,我们才能在工作中灵活的运用。

本文所涉及到的实例代码后面也会上传到Github上。

希望本文对您有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/315465.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Educational Codeforces Round 106 (Rated for Div. 2) D. The Number of Pairs 数论gcd

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给三个数c,d,xc,d,xc,d,x&#xff0c;求满足c∗lcm(a,b)−d∗gcd(a,b)xc*lcm(a,b)-d*gcd(a,b)xc∗lcm(a,b)−d∗gcd(a,b)x条件的(a,b)(a,b)(a,b)的数量。 思路&#xff1a; 考虑将lcm(a,b)lcm(a,b)lcm(a,b…

从零开始制作 NuGet 源代码包(全面支持 .NET Core / .NET Framework / WPF 项目)

默认情况下&#xff0c;我们打包 NuGet 包时&#xff0c;目标项目安装我们的 NuGet 包会引用我们生成的库文件&#xff08;dll&#xff09;。除此之外&#xff0c;我们也可以专门做 NuGet 工具包&#xff0c;还可以做 NuGet 源代码包。然而做源代码包可能是其中最困难的一种了&…

HDU - 4497 GCD and LCM 数论gcd

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给三个数的lcmlcmlcm和gcdgcdgcd&#xff0c;求满足条件的三元组组合个数。 思路&#xff1a; 首先lcmmodgcd0lcm\bmod gcd0lcmmodgcd0是有组合的条件&#xff0c;否则输出0。 现在可知lcm(x′,y′,z′)lc…

.NET Core 3.0 Preview 6中对ASP.NET Core和Blazor的更新

我们都知道在6月12日的时候微软发布了.NET Core 3.0的第6个预览版。针对.NET Core 3.0的发布我们国内的微软MVP-汪宇杰还发布的官翻版的博文进行了详细的介绍。具体的可以点这里进行阅读译 | .NET Core 3.0 Preview 6 已发布。而我们这篇文章将会介绍本次更新中对ASP.NET Core和…

Codeforces Round #686 (Div. 3) F. Array Partition 二分 + 线段树

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 化简一下题意就是求满足max(1,x)min(x1,y)max(y1,n)max(1,x)min(x1,y)max(y1,n)max(1,x)min(x1,y)max(y1,n)的len1x,len2y−x,len3n−ylen1x,len2y-x,len3n-ylen1x,len2y−x,len3n−y。 思路&#xff1a; …

Dapper.Common基于Dapper的开源LINQ超轻量扩展

Dapper.CommonDapper.Common是基于Dapper的LINQ实现,支持.net core,遵循Linq语法规则、链式调用、配置简单、上手快,支持Mysql,Sqlserver(目前只实现了这两个数据库&#xff0c;实现其他数据库也很轻松)&#xff0c;支持单表&#xff0c;多表&#xff0c;自定义函数等功能。源码…

Codeforces Round #686 (Div. 3) E. Number of Simple Paths 基环树 + 容斥

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给你一颗基环树&#xff0c;求长度>1>1>1的路径个数。 思路&#xff1a; 先考虑一棵树&#xff0c;他的答案显然是n∗(n−1)2\frac{n*(n-1)}{2}2n∗(n−1)​。因为是个基环树&#xff0c;所以先考…

.NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖

我们有多种工具可以将程序集合并成为一个。比如 ILMerge、Mono.Merge。前者不可定制、运行缓慢、消耗资源&#xff08;不过好消息是现在开源了&#xff09;&#xff1b;后者已被弃用、不受支持且基于旧版本的 Mono.Cecil。而本文介绍用来替代它们的 ILRepack&#xff0c;使用 I…

4G的小程序与5G的Chromium OS?

提到桌面操作系统&#xff0c;Windows 与 macOS 自然是家喻户晓&#xff0c;稍微有点 IT 知识的人也会知道 Linux&#xff0c;而 Fuchsia 和鸿蒙&#xff08;方舟&#xff09;则还在积极开发中&#xff0c;除了这几大类比较热门的&#xff0c;当前还有一大潜力股 Chrome OS&…

Codefest 18 (rated, Div. 1 + Div. 2)-D-Valid BFS--思维--已知bfs序,求是否正确

Codefest 18 (rated, Div. 1 Div. 2)-D-Valid BFSvj地址 题意&#xff1a;给你一段bfs序列&#xff0c;让你判断是否正确 思路&#xff1a;先给节点排序&#xff08;很关键&#xff09; 我们给每一个的点的邻接表按照给出的bfs序列排序&#xff0c;排好后&#xff0c; 我跑一…

P3605 [USACO17JAN]Promotion Counting P dfs序

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 思路&#xff1a; 这个题可以用各种姿势a掉&#xff0c;树启和线段树合并都可以&#xff0c;比较无脑。下面给一个解决这种问题比较巧妙的做法。 考虑暴力怎么写&#xff0c;我们先将每个点的权值离散化一下…

推荐VSCode多语言开发,支持一键JAVA

哈喽大家周一好&#xff01;好久不见鸭&#xff0c;最近在看一本书&#xff0c;很好&#xff0c;《人类简史》&#xff0c;适合夏日星空&#xff0c;仰观宇宙之大?这个专题是“做贡献”&#xff0c;也是作为软粉&#xff0c;打算一直推广微软自己的东西&#xff0c;上一次还是…

思维--找规律--Codeforces Round #645 (Div. 2) c题

C. Celex Update 题目大意&#xff1a;给出两点的坐标&#xff0c;找出不同的路径的总数&#xff08;路径数字总和不同&#xff09; 思路&#xff1a;根据观察向下走比向右走的增加幅度加1&#xff0c;所以在第i步 向下 对sum的影响是 n-i1 所以最小数字为12。。。。y&#xf…

Codeforces Round #709 (Div. 1, based on Technocup 2021 Final Round) A. Basic Diplomacy

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 有nnn个小朋友&#xff0c;让后你要在mmm天中每天都选择一个小朋友&#xff0c;给出这mmm天都可以选哪个小朋友&#xff0c;每个小朋友选的次数不超过⌈m2⌉\left \lceil \frac{m}{2} \right \rceil⌈2m​⌉…

分布式Redis的分布式锁 Redlock

引言之前自己在用redis来实现分布式锁的时候都是基于单个Redis实例&#xff0c;也就是说Redis本身是有单点故障的&#xff0c;Redis的官方文档介绍了一种"自认为"合理的算法&#xff0c;Redlock来实现分布式Redis下的分布式锁。Martin Kleppmann写了一篇文章分析Redl…

双指针--Codeforces Round #645 (Div. 2) d题

D. The Best Vacation 题目大意&#xff1a; 算出连续x天最多的拥抱&#xff0c;一个月第i号就有i个拥抱 思路&#xff1a;双指针&#xff0c;扫描过去&#xff08;每个月每个月的计算&#xff0c;最后超出的部分再一天一天算&#xff09; 代码 &#xff1a; #include<cstd…

Educational Codeforces Round 106 (Rated for Div. 2) C. Minimum Grid Path 奇偶 + 思维

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给一个二维平面&#xff0c;起点在(0,0)(0,0)(0,0)&#xff0c;终点在(n,n)(n,n)(n,n)&#xff0c;每次只能往上和往右走&#xff0c;距离随意&#xff0c;总步数不超过nnn&#xff0c;每一步有一个代价cic_…

从CLR GC到CoreCLR GC看.NET Core对云原生的支持

内存分配概要前段时间在园子里看到有人提到了GC学习的重要性&#xff0c;很赞同他的观点。充分了解GC可以帮助我们更好的认识.NET的设计以及为何在云原生开发中.NET Core会占有更大的优势&#xff0c;这也是一个程序员成长到更高层次所需要经历的过程。在认识GC的过程中&#x…

Orac and LCM #641(div2) c题--求质因数次小指数

Orac and LCM cf地址 For the multiset of positive integers s{s1,s2,…,sk}, define the Greatest Common Divisor (GCD) and Least Common Multiple (LCM) of s as follow: gcd(s) is the maximum positive integer x, such that all integers in s are divisible on x. …

Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4) dfs + 思维

传送门 文章目录题意&#xff1a;思路&#xff1a;题意&#xff1a; 给一张图&#xff0c;求必须经过aaa点和bbb点的路径条数。 思路&#xff1a; 通过观察我们发现&#xff0c;这个路径无非就是x−>a−>b−>yx->a->b->yx−>a−>b−>y或者x−>…