偶然翻看了n年前自己写的几篇技术博文,真真切切切切实实实实在在有恍如隔世之感。当年那么晦涩枯燥的汇编代码亦能写的行云流水般,如今却几乎看不懂了,让美貌与智慧为化身的作者君那颗强大的玻璃心飘过一丝蛋蛋的忧伤。于是乎,不仅感叹计算机语言技术的发展速度,以及对生产效率带来的影响,其中C#的语法糖具有相当突出的代表性。语法糖比那些貌美如花嗲的掉渣的程序员鼓励师实用多了。
趁月落乌啼夜黑风高好下手,赶紧把当前的C#语法糖整理和汇总一下,第一温故知新,第二备忘,第三给需要的人参考,不足之处欢迎批评斧正,和谐社会禁止拍砖。魏滔序原创,欢迎转载但请注明出处。
先看一段来自百度百科的解释:
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
.Net从2.0到3.0到3.5底层CLR并没有做更新,没有添加任何新的IL指令,所以C#从2.0到3.0中所有的特性都是语法糖,就算从1.0到2.0,也只有一个泛型不是语法糖,其余的新特性也都是如此,但是每一次C#发布新特性的时候我们不得不为之而鼓舞,为之而喝彩。新的语法可以酿造更好的编程风格,一些难以实现的方面也轻而易举了。
题外话:忽然赶脚CSDN的编辑器比n年前一成不变的难用好用多了,此处给个赞(宇宙全无敌黄金广告位招租),下面言归正传。
属性(Property)
常规写法如下:
public class Product
{
private string _name;
public Product()
{
_name = "白色经典长袖衬衫";
//或者
//Name = "白色经典长袖衬衫";
}
public string Name
{
get { return _name; }
set { _name = value; }
}
}
或者如下:
public class Product
{
private string _name = "白色经典长袖衬衫";
public string Name
{
get { return _name; }
set { _name = value; }
}
}
两者的区别只是赋初始值时机不同,代码的冗余量不相上下,在C#3.0后,语法做了如下的简化处理:
public class Product
{
public Product()
{
Name = "白色经典长袖衬衫";
}
public string Name { get; set; }
}
属性声明一行足矣,代码冗余量大幅减少,当然也不会因此失去灵活性,比如给get或set指定访问限制符:
public string Name { protected internal get; set; }
然而,善解人衣的MS工程师们认为这样还是甜不过初恋,在C#6.0中又撒了一把糖(自动属性初始化器),且看:
public class Product
{
public string Name { get; set; } = "白色经典长袖衬衫";
}
从开始的那么一大坨变成一行,多么令人陶醉的写法。需要注意的是在C#6.0中,除了以上方法直接赋值外,在构造函数中也可以为只读属性赋值,之前。。。那是不允许滴:
public class Product
{
public string Name { get; }
public Product()
{
Name = "白色经典长袖衬衫";
}
}
但是,如果在声明中已经赋值,那么在构造函数中依然不允许,编译器是位多么称职的保姆啊啊啊。
public class Product
{
public string Name { get { return "白色经典长袖衬衫"; } }
//C#6.0中还可以使用属性表达式为只读属性赋值,如下行代码:
//public string Name => "白色经典长袖衬衫";
public Product()
{
Name = "白色经典长袖衬衫"; //编译器说了,不要酱紫嘛
}
}
这糖,甜过初恋。
可空类型(T?)
可空类型可以表示基础类型的所有值,另外还可以表示 null 值。可空类型可通过下面两种方式中的一种声明:
System.Nullable variable
//或
T? variable
T 是可空类型的基础类型。T 可以是包括 struct 在内的任何值类型;但不能是引用类型。可空类型有一个HasValue的bool类型的只读属性,当该属性为true的时候,那么该值为非空实例,可以正常访问该值的Value。如果HasValue的属性为false的时候,那么访问该值的Value导致异常。
public class Program
{
public static void Main()
{
int? a = null;
int? b = 500;
int? c;
if (a.HasValue)
{
c = a.Value;
}
else
{
c = b;
}
}
}
注意,在C#6.0中对所有可以为null的数据类型做了增强,下面的代码如果name为null时,r的值也为null:
public class Program
{
public static void Main()
{
string name = "白色经典长袖衬衫";
int? r = name?.Length;
}
}
这糖,甜过初恋。
条件运算符(?:)
根据 Boolean 表达式的值返回两个值之一。 下面是条件运算符的语法。
condition ? first_expression : second_expression;
condition 的计算结果必须为 true 或 false。 如果 condition 为 true,则将计算 first_expression 并使其成为结果。 如果 condition 为 false,则将计算 second_expression 并使其成为结果。 只计算两个表达式之一。
first_expression 和 second_expression 的类型必须相同,或者必须存在从一种类型到另一种类型的隐式转换。
对于上面的可空类型的示例代码可以简化成:
public class Program
{
public static void Main()
{
int? a = null;
int? b = 500;
int? c = a.HasValue ? a.Value : b;
}
}
这糖,甜过初恋。
空接合操作符(??)
空接合操作符(null-coalescing operator),要获取两个操作数。假如左边的操作数不是null,就返回这个操作数的值。如果左边的操作数是null,就返回右边这个操作数的值。利用空接合操作符可以方便的设置变量的默认值。它的另一个妙处在于,它既能用于引用类型,也能用于可空值类型。
public class Program
{
public static void Main()
{
int? a = null;
int? b = 500;
int? c = a ?? b;
//上面代码等价于
//int? c = a.HasValue ? a.Value : b;
}
}
这糖,甜过初恋。
委托(delegate)
常规写法如下:
public delegate void DemoDelegate(string name);
public class Program
{
public static void Main()
{
ReceiveDelegateArgs(new DemoDelegate(DelegateFunction)); //声明函数
}
public static void ReceiveDelegateArgs(DemoDelegate demoDelegate)
{
demoDelegate("白色经典长袖衬衫");
}
//声明的委托调用函数
public static void DelegateFunction(string name)
{
Console.WriteLine(name);
}
}
C#2.0引入了匿名委托的概念,简化了调用函数的声明,代码自然简化不少:
public delegate void DemoDelegate(string name);
public class Program
{
public static void Main()
{
ReceiveDelegateArgs(delegate (string name) //匿名委托
{
Console.WriteLine(name);
});
}
public static void ReceiveDelegateArgs(DemoDelegate demoDelegate)
{
demoDelegate("白色经典长袖衬衫");
}
}
然而,C#3.0出现的Lambda表达式又进一步简化了代码冗余量,显然看起来没那么忧伤了:
public delegate void DemoDelegate(string name);
public class Program
{
public static void Main()
{
ReceiveDelegateArgs(x => Console.WriteLine(x)); //Lambda表达式
}
public static void ReceiveDelegateArgs(DemoDelegate demoDelegate)
{
demoDelegate("白色经典长袖衬衫");
}
}
这糖,甜过初恋。
拉姆达(Lambda)
既然上面提到了Lambda,那就说说这个语法神器。C#的Lambda表达式的本质是匿名函数,在C#6.0的Lambda表达式可以当作函数体使用:
public class Program
{
public static void Main()
{
Print("白色经典长袖衬衫");
}
public static void Print(string name) => Console.WriteLine(name);
}
但也仅适合一条语句的情况,多条语句就要单独封装了,比如:
public class Program
{
public static void Main()
{
Print("白色经典长袖衬衫", "XXL");
}
public static void Print(string name, string size) => Prints(name, size);
public static void Prints(string name, string size)
{
//WriteLine可以输出多值,下面仅为表示多行语句
Console.WriteLine(name);
Console.WriteLine(size);
}
}
这糖,甜过初恋。
var类型
var类型出现于.Net3.5,其目的是弱化类型的定义。var可替代任何类型,编译器会根据上下文来判断你想用的真正类型,一旦初始完成就不能再给其赋与初始化类型不同的值了。var与object不同,var在效率上和使用强类型方式定义的变量完全一样,var只能作为局部变量使用,不能用做字段也不能用做参数声明。
var name = "白色经典长袖衬衫"; // string
var size = 160; // int
var color = ConsoleColor.Blue; // ConsoleColor
这糖,甜过初恋。
匿名类
这货在EF、JSON序列化、Linq to SQL中很好用:
public class Program
{
public static void Main()
{
var product = new
{
Name = "白色经典长袖衬衫",
Size = "XXL"
};
}
}
这糖,甜过初恋。
集合初始化
通常,对集合类型的对象的初始赋值是酱紫的:
public class Product
{
public Product()
{
IList<string> Sizes = new List<string>();
Sizes.Add("L");
Sizes.Add("XL");
Sizes.Add("XXL");
}
}
自从有了语法糖,腰不酸了腿不疼了代码也可以写成这样了:
public class Product
{
public Product()
{
IList<string> Sizes = new List<string> { "L", "XL", "XXL" };
//或数组
//string[] Sizes = new string[] { "L", "XL", "XXL" };
}
}
一口气能爬19楼不费劲,甚至在C#6.0中的Dictionary可以这样写了(学名叫“带索引的对象初始化器”):
public class Product
{
public Product()
{
Dictionary<string, string> Colors = new Dictionary<string, string>()
{
["Red"] = "红色",
["Blue"] = "蓝色"
};
}
}
这糖,甜过初恋。
集合操作
通常遍历集合中的项的代码是这样写的:
public class Program
{
public static void Main()
{
List<string> Sizes = new List<string> { "L", "XL", "XXL" };
foreach (string size in Sizes)
{
Console.WriteLine(size);
}
}
}
在语法糖的光辉照耀下,可以这样:
public class Program
{
public static void Main()
{
List<string> Sizes = new List<string> { "L", "XL", "XXL" };
Sizes.ForEach(x => Console.WriteLine(x));
//更多使用方法(包括但不限于这些 ,自己挖吧,伙伴们!):
//确定“XL”是否在集合中
var r1 = Sizes.Contains("XL");
//确定集合中是否包含与条件匹配的元素
var r2 = Sizes.Exists(x => x == "XL");
//返回集合项中包含“X”的第一个元素
var r3 = Sizes.Find(x => x.Contains("X"));
}
}
这糖,甜过初恋。
using VS try finally
在C#和其他托管语言中,没有自动、决定性的析构方式,而是有一个垃圾收集器,它会在未来的某个时刻释放资源。它是非决定性的,因为我们不能确定这个过程在什么时候发生,通常使用try fiannaly来实现资源释放,但using用起来更简洁高效。例如:
public class Program
{
public static void Main()
{
Stream stream = null;
try
{
stream = new MemoryStream();
//......
}
catch
{
throw;
}
finally
{
if (stream != null)
stream.Dispose();
}
}
}
以上代码等价于:
public class Program
{
public static void Main()
{
using (var stream = new MemoryStream())
{
//......
}
}
}
这糖,甜过初恋。
类实例化
先看下这个简单的类:
public class Product
{
public string Name { get; set; }
public string Size { get; set; }
}
这个类有两个属性,没有声明构造函数,可以通过下面的方法一步完成对象的实例化和初始赋值:
public class Program
{
public static void Main()
{
var product = new Product()
{
Name = "白色经典长袖衬衫",
Size = "XXL"
};
}
}
这糖,甜过初恋。
扩展方法
扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。
public static class MyExtensions
{
static public bool IsUpper(this string input)
{
return input == input.ToUpper();
}
}
public class Program
{
public static void Main()
{
var name = "ABC";
var r = name.IsUpper();
}
}
这糖,甜过初恋。
字符串嵌入值(String.Format)
简单来说就是将指定字符串中的每个格式项替换为相应对象的值的文本等效项,比用拼接字符串的方式显得有biger,是install13的必备神器:
public class Program
{
public static void Main()
{
var name = String.Format("{0}经典长袖衬衫", "白色");
var r = String.Format("{0,-10:C}", 126347.89m);
}
}
C#6.0中又增加了让人看起来有些晕菜但很巧妙的更高biger的方式:
public class Program
{
public static void Main()
{
var color = "白色";
var name = $"{color}经典长袖衬衫";
}
}
这糖,甜过初恋。
using静态类
C#6.0的新东东,可以在代码的顶部这样引用静态类,写法怪异,不乏实用:
using static System.Console;
这糖,甜过初恋。
nameof
C#6.0的新东东,nameof运算符可以作用于变量、函数、类以及名字空间中,用于返回返回其名称,例如:
public class Program
{
public static void Main()
{
Console.WriteLine(nameof(Main)); //输出 "Main"
}
}
这糖,甜过初恋。
成员索引
C#6.0的新东东,但美貌与智慧为化身的作者君真心没看出有什么用途,或许以后用的到:
public class Product
{
public Product()
{
Console.WriteLine(this["白色"]); //这里输出 白色经典长袖衬衫
}
public string this[string color]
{
get { return String.Format("{0}经典长袖衬衫", color); }
}
}
这糖,甜过初恋。
await
C#5.0引入的await运算符可以非常方便的执行异步运算,但在catch和finally中不能使用,C#6.0放开了这个限制,方便程度那是杠杠的。
这糖,甜过初恋。
无参数的结构体构造函数
C#6.0的新东东,通过new得到的结构体会被调用构造函数,而通过default得到的不会调用:
public class Product
{
public Product()
{
Name = "白色经典长袖衬衫";
}
public string Name { get; set; }
}
public class Program
{
public static void Main()
{
var p1 = new Product();
Console.WriteLine(p1.Name); //输出 白色经典长袖衬衫
var p2 = default(Product);
Console.WriteLine(p1.Name); //输出空值
}
}
这糖,甜过初恋。
亲,就这样收尾了?嗯,是的!
零零散散的搞了这么一大篇,涉及的技术却沧海一粟九牛一毛。那么,咱们约好待到山花烂漫时,你在丛中笑,我再继续搞。