• C#语法糖(Csharp Syntactic sugar)


    目录

    一、C#语法糖大汇总

    1. 经过简化的Property
    2. 经过两次变异的委托写法
    3. 集合类的声明
    4. 集合类各个项的操作
    5. using == try finally
    6. 可爱的var
    7. 问号的演变
    8. 类型实例化的语法糖
    9. 传说中的扩展方法
    10.使用匿名类

    二、C#之6.0语法糖剖析

    2.1 自动属性默认初始化
    2.2 自动只读属性默认初始化
    2.3 表达式为主体的函数
    2.4 表达式为主体的属性(赋值)
    2.5 静态类导入
    2.6 Null条件运算符
    2.7 字符串格式化
    2.8 索引初始化
    2.9 异常过滤器when
    2.10 catch和finally代码块内的Await
    2.11 nameof表达式
    2.12 扩展方法
    总结

     


    一、C#语法糖大汇总

    首先需要声明的是“语法糖”这个词绝非贬义词,它可以给我带来方便,是一种便捷的写法,编译器会帮我们做转换;而且可以提高开发编码的效率,在性能上也不会带来损失。这让java开发人员羡慕不已,呵呵。

    1.  经过简化的Property

    早些时候我们这样声明Property

    private string _myName;
    public string MyName
    {
        get { return _myName; }
        set { _myName = value; }
    }

    千篇一律的这样声明,没有多大意义,于是C#的设计人员将这个千篇一律的工作交给了编译器帮我们做了,我们现在可以这样声明

    public string MyName { get; set; }

    当然他不会牺牲灵活性,我们可以单独给get或者set设定访问限制符,例如

    public string MyName { get; protected internal set; }

    2.  经过两次变异的委托写法

    在.net 1.1时我们不得不声明方法后才在委托中使用,在.net 2.0之后我们可以使用匿名委托,他不单可以简化写法,还可以在匿名委托中访问范围内的变量;再后来拉姆达表达式来了,写法就更简便了。

    class MyClass
    {
        public delegate void DoSomething(int a);
        //定义方法委托
        private void DoIt(int a) {
            Console.WriteLine(a);
        }
        private void HowtoDo(DoSomething doMethod,int a) {
            doMethod(a);
        }
        public static void Main(string[] args) {
            MyClass mc = new MyClass();
            //调用定义的方法委托
            mc.HowtoDo(new DoSomething(mc.DoIt), 10);
            int x = 10;
            //使用匿名委托
            mc.HowtoDo(delegate(int a){
                Console.WriteLine(a + x);
            },10);
            //使用lamda表达式
            mc.HowtoDo(a=>Console.WriteLine(a+x),10);
            Console.ReadLine();
        }
    }

    3.  集合类的声明

    //之前我们声明一个List并给list赋初始值,必须得这么写:
    List<string> list = new List<string>();
    list.Add("a一");
    list.Add("b二");
    list.Add("c三");
    
    //现在不需要了,直接写就可以了
    List<string> list = new List<string> {"def","OK"};

    4.  集合类各个项的操作

    //我们为了逐个处理集合中的项,需要这么写:
    foreach (string item in list)
    {
         Console.WriteLine(item);
    }
    
    //现在不需要了,这样就可以了
    list.ForEach(a => Console.WriteLine(a));

    5.  using == try finally

    为了在使用完毕时释放资源,我们经常要用using,using实质上就是try fiannaly的一个语法糖而已。例如

    StreamWriter sw = null;
    try
    {
        sw = new StreamWriter("d:abc.txt");
        sw.WriteLine("test");
    }
    finally {
        if(sw!= null) sw.Dispose();
    }
    
    //上面的代码可以简化为:
    using (var sw = new StreamWriter("d:abc.txt")) {
        sw.WriteLine("test");
    }

    6.  可爱的var

    var的意义时不必写声明的类型,编译器会根据后面对var的赋值判断它的类型,var的类型一旦确认就不能再改变,它只能作为局部变量使用,不能用做字段也不能用做参数声明。

    var writer = new StreamWriter(path);
    
    for(var i=0;i<100;i++){}

    7.  问号的演变

    老掉牙的一个问号+冒号

    var b = 3;
    var a = b > 9?b.ToString():”0”+b;

    新宝宝两个问号 ??,它表示左边的变量如果为null则值为右边的变量,否则就是左边的变量值

    string a = null;
    var b = a??””;

    8.  类型实例化的语法糖

    public class Abc
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
    }
    
    //我们没有为上面的类声明构造函数,但是我们可以像下面的形式来实例化它
    public static void Main(string[] args) {
            var abc = new Abc{
                ID=1,
                Name="yukaizhao",
                Url="http://yukaizhao.cnblogs.com/"
            };
        }

    9.  传说中的扩展方法

    在c#3.5时引入了扩展方法,我们可以在不修改类源码的情况下给类增加实例方法,这个很有意义。它的实质也是一种语法糖的实现
    例如我们给String类扩展一个IsNumber的方法:

    public static class StringExt {
        static private Regex regexNumber = new Regex("\d+");
        static public bool IsNumber(this string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return false;
            }
            return regexNumber.IsMatch(input);
        }
    }
    
    //我们可以在String实例上调用这个方法了
    var abc = “123”;
    var isNumber = abs.IsNumber();

     10.使用匿名类

    var a = new {
        ID = 1,Name=”yukaizhao”,BlogUrl=”http://www.cnblogs.com/yukaizhao/”
    };

    匿名类在linq to sql或者entity framework中返回查询数据时很好用。


    C#6

    静态类导入using static System.Console;

    11. NULL条件运算符

    //使用代码
    Customer customer = new Customer();
    string name = customer?.Name;
    
    //编译代码
    Customer customer = new Customer();
    if (customer != null)
    {
        string name = customer.Name;
    }

    也可以和??组合起来使用

    if (customer?.Face()??false)

    还可以两个一起组合来使用

    int? contactNameLen = contact?.Name?.Length; 

    这个语法糖的目的是在对象使用前检查是否为null。如果对象为空,则赋值给变量为空值,所以例子中需要一个可以为空的int类型、即int?。如果对象不为空,则调用对象的成员取值,并赋值给变量。 

    12. 字符串格式化

    String.Format有些不方便的地方是:必须输入"String.Format",使用{0}占位符、必须顺序来格式化、这点容易出错。

    var contactInfo = string.Format("Id:{0} Name:{1} EmailAddr:{2} PhoneNum:{3}", contact.Id, contact.Name, contact.EmailAddress, contact.PhoneNum);
    
    //新的语法
    var contactInfo2 = $"Id:{contact.Id} Name:{contact.Name} EmailAddr:{contact.EmailAddress} PhoneNum:{contact.PhoneNum}";
    
    //新格式化方式还支持任何表达式的直接赋值:
    var contactInfo = $"Id:{contact.Id} Name:{(contact.Name.Length == 0 ? "Frank" : contact.Name)} EmailAddr:{contact.EmailAddress} PhoneNum:{contact.PhoneNum}";

    二、C#之6.0语法糖剖析

    2.1 自动属性默认初始化

    //使用方法:
    public string Name { get; set; } = "hello world";
    
    //为了便于理解使用2.0语法展示,编译器生成代码如下:
    public class Customer 
    {
     [CompilerGenerated] 
    private string kBackingField = "hello world"; 
    public Customer() 
    { 
      this.kBackingField = "hello world"; 
    }
    
    public string Name
    {
        [CompilerGenerated]
        get
        {
            return this.<Name>k__BackingField;
        }
        [CompilerGenerated]
        set
        {
            this.<Name>k__BackingField = value;
        }
    }
    } 
    //从生成代码中可以看出编译器是在实例构造函数时,初始化属性信息的。

    2.2 自动只读属性默认初始化

    //使用方法:
    public string Name1 { get; } = "hello world";
    
    //编译器生成代码如下:
    [CompilerGenerated] 
    private readonly string kBackingField; 
    public Customer() 
    {
        this.kBackingField = "hello world";
    } 
    public string Name1 
    {
        [CompilerGenerated] 
        get { return this.k__BackingField; }
    }
    //由于初始化默认值实在构造函数中赋值的,所以跟属性只读没关系。

    2.3 表达式为主体的函数

    //使用方法:
    Body Get(int x, int y) => new Body(1 + x, 2 + y);
    
    //编译器生成如下:
    private Program.Body Get(int x, int y)
    {
        return new Program.Body(1 + x, 2 + y);
    }
    //简化了单行方法的编写,省去写大括号的功夫。
    
    //同时支持没有返回值的写法: 
    void OutPut(int x, int y) => Console.WriteLine("hello world");
    
    //也支持异步函数的编写:
    async void OutPut(int x, int y) => await new Task(() => Console.WriteLine("hello wolrd"));

    2.4 表达式为主体的属性(赋值)

    //使用方法:
    public string Name2 => "hello world";
    
    //编译器生成代码如下:
    public string Name2 
    { 
      get { return "mushroomsir"; }
    }
    //编译器只生成了个只读属性。

    2.5 静态类导入

    //这个特性可以一次性导入某类型的所有静态成员,使静态成员在后面的代码中没有类型限制直接使用,像使用本类型下面的静态方法一样。
    using static System.Console;
    class Program 
    { 
      static void Main(string[] args) 
      {
        WriteLine("hello wolrd"); 
      }
    }
    
    //编译器生成代码如下:
    private static void Main(string[] args)
    {
      Console.WriteLine("hello wolrd"); 
    }
    //省去了类型名称的重复编写。

    2.6 Null条件运算符

    //使用方法:
    Customer customer = new Customer();
    string name3 = customer?.Name;
    
    //等同于:
    Customer customer = new Customer();
    if (customer1 != null)
    {
        string name = customer1.Name;
    }
    
    //可以和??组合起来使用:
    if (customer?.Face2()??false)
    
    //还可以2个一起用:
    int? Length = customer?.Name?.Length;
    
    //也可以方法调用:
    customer?.Face();

    这个语法糖的目的是在对象使用前检查是否为null。如果对象为空,则赋值给变量为空值,所以例子中需要一个可以为空的int类型、即int?。

    如果对象不为空,则调用对象的成员取值,并赋值给变量。

    2.7 字符串格式化

    //String.Format有些不方便的地方是:必须输入"String.Format",使用{0}占位符、必须顺序来格式化、这点容易出错。
    var s = String.Format("{0} is {1} year {{s}} old", p.Name, p.Age);
    
    //新的语法糖使用起来相对更轻松些:
    var s = $"{p.Name} is {p.Age} year{{s}} old";
    
    //编译器生成如下,和之前没有区别:
    var s = String.Format("{0} is {1} year{{s}} old", p.Name, p.Age);
    
    //有趣的是,新格式化方式还支持任何表达式的直接赋值:
    var s = $"{p.Name} is {p.Age} year{(p.Age == 1 ? "" : "s")} old";

    2.8 索引初始化

    //List虽然这样写可以编译通过,但是会抛异常的,使用方法:
    var numbers = new List<string> { [7] = "seven", [9] = "nine", [13] = "thirteen" };
    
    //编译器生成代码如下:
    List list = new List(); 
    list[7] = "seven";
    list[9] = "nine"; 
    list[13] = "thirteen";
    
    //Dictionary可以执行,因为二者内部索引机制不一样:
    var numbers = new Dictionary<int, string> {[7] = "seven",[9] = "nine",[13] = "thirteen" }; //编译器生成代码: Dictionary<int, string> dictionary2 = new Dictionary<int, string>(); dictionary2[7] = "seven"; dictionary2[9] = "nine"; dictionary2[13] = "thirteen"; Dictionary<int, string> dictionary = dictionary2;

    2.9 异常过滤器when

    //使用方法:
    try 
    { 
        throw new ArgumentException("string error");
    }
    catch (ArgumentException e) when (myfilter(e))
    { 
        Console.WriteLine(e.Message);
    }
    
    static bool myfilter(ArgumentException e)
    { 
        return false;
    }

    When语法作用是:在进入到catch之前、验证when括号里myfilter方法返回的bool,如果返回true继续运行,false不走catch直接抛出异常。

    使用这个filter可以更好的判断一个错误是继续处理还是重新抛出去。按照以前的做法,在catch块内如需再次抛出去,需要重新throw出去,这时的错误源是捕捉后在抛的,而不是原先的,有了when语法就可以直接定位到错误源。 

    2.10 catch和finally代码块内的Await

    Await异步处理是在c#5.0提出的,但不能在catch和finally代码块内使用,这次在C#6.0更新上支持了。

    使用方法:

       async void Solve()
        {
            try
            {
                await HttpMethodAsync();
            }
            catch (ArgumentException e)
            {
                await HttpMethodAsync();
            }
            finally
            {
                await HttpMethodAsync();
            }
        }

    编译器把catch和finally的await生成到状态机里面的MoveNext()里面。原来里面只有 TaskAwaiter,现在多了2个。状态机里面的代码和原先的一样,只是更复杂了下,有兴趣的童鞋可以先看下Async、Await剖析再去深究。

    2.11 nameof表达式

    //使用方法:
    string name = "";
    Console.WriteLine(nameof(name));
    //控制台输出 "name"。

    有时候会需要程序中一些成员的字符串名称,比如抛出ArgumentNullException异常的时候,想知道ArgumentNullException类型的字符串名称,这时候就可以用nameof获取字符

    串“ArgumentNullException”。现在做法都是手动复制一下,但重构改名的时候容易忘记变更字符串,使用nameof就可以避免了。

    //当如下使用的时候,编译器会只取最后的ZipCode。
    nameof(person.Address.ZipCode)
    
    //编译器生成如下代码:
    Console.WriteLine("name");

    2.12 扩展方法

        using static System.Linq.Enumerable; //引入类型,而不是命名空间
        class Program
        {
            static void Main()
            {
                var range = Range(5, 17);                // Ok: 不是扩展方法
                var odd = Where(range, i => i % 2 == 1); // Error, 不在全局作用域里
                var even = range.Where(i => i % 2 == 0); // Ok
            }
        }

    首先Enumerable是个静态类,里面是各种扩展方法,比如range。static的作用是把类型的静态成员一次性导入,rang虽然是静态方法,但不能导入,比如where。

    因为扩展方法虽然是一个静态方法,但是语法规定它作为一个实例方法使用(打点),所以不能在全局作用域里当静态方法用,因此var odd = Where(range, i => i % 2 == 1)是错误的。

    但是static却能把类型的扩展方法作为扩展方法本身角色的功能导入进去,所以var even = range.Where(i => i % 2 == 0)是ok的。

    这里可能稍微有点绕,lz尽量写清楚,static新用法有2个功能:

    把静态成员导入,但扩展方法比较特殊、排除在外。这时static是c# 6.0的新功能。
    等同于把扩展方法的命名空间导入,所以在集合上可以打点调用扩展方法。这是之前就有的功能,而不是把扩展方法转成单纯的静态方法导入使用。

    总结

    看到园子里有介绍的文章,一时来兴趣了,下班后安装个社区版就研究分享下。 虽然微软一直出新东西,但都是由下至上迭代的,所以学习起来是非常快的。

  • 相关阅读:
    Linq查询
    Lambda表达式与标准运算符查询
    第四章 面向对象与IO操作
    第三章 C#循环与方法
    第二章 C#基本数据类型
    FPGA与嵌入式一点见解
    FPGA中RAM使用探索
    可控硅的工作原理和主要作用
    异步电路中时钟同步的方法
    FPGA中计数器设计探索
  • 原文地址:https://www.cnblogs.com/zhaoshujie/p/9594646.html
Copyright © 2020-2023  润新知