C#语法糖

语法糖

维基百科给出的解释:计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。从这个解释中我们可以看出两点,一是语法糖不会影响程序的功能,再者,语法糖会让编写者更开心,阅读者更舒心。对于许多不愿意使用这些语法还吐糟说看不懂,学习成本高的朋友,我只能呵呵啦,有本事你去写汇……哦不,机器语言啊,现代的编程语言其实也都是机器语言的语法糖了呢。

C#中的语法糖

懒人定律说,能坐着的就不站着,能躺着的就不坐着,能少敲点代码就少敲点代码,能少费点脑力就少费点脑力。微软不仅给我们提供了全宇宙最强大的IDE,还给我们提供了诸多牛x得不行的语言特性,作为.net阵营的忠实粉,我没有理由不去详细了解这些优秀的语法规则,做一个聪明的懒人。

属性

演变

爪哇的开发人员对以下的代码一定不会陌生:

private int _value;
public int GetValue()
{
    return _value;
}

public void SetValue(int value)
{
    _value = value;
}

当然,在.NET的版本中也时而可见这样的代码,对于随处可见的这样的代码,我们实在忍无可忍,于是有了这样的代码:

private int _value;
public int value
{
    get{return _value;}
    set{_value=value;}
}

简短了很多,我们还可以用VS提供的强大的快捷键Ctrl+R,R来快速生成代码,但是为什么每一个属性的背后都一定要有一个贤惠的从不出声的局部变量呢?所以诞生了这样的代码

public int Value{get;set;}

如果你用反编译软件查看,你会在IL代码里发现,编辑器帮我们生成了get_Value和set_Value这样的方法。
如果你想让这个属性只能在类内部进行赋值操作,完全可以在set前面加了private限定符,protected和public也都是支持的哦。如果你已经用上了C# 6.0,你还可以这样使用属性

public string Value{get;set;}="Hello World";

优势

当然,属性的魅力并不只是因为它简单、安全、灵活,很多时候,我们会写出如下代码

private string _mark="";
public string Mark
{
    get
    {
        if(!string.IsNullOrWhiteSpace(_mark)) return _mark;
        if(Score>=80 && Score<90) 
        {
            _mark= "良";
        }
        else if(Score>=60 && Score<80) 
        {
            _mark= "中";  
        }      
        else if(Score>=90) 
        {
            _mark= "优";
        }
        else if(Score<60) 
        {
            _mark= "不及格";
        }
        return _mark;
    }
}

一定程度上来讲,属性就是 包含了逻辑代码的变量,还有些时候,我们需要这样写:

private string _mobile="";
public string Mobile
{
    get
    {
        if(!string.IsNullOrWhiteSpace(_mobile))return _mobile;
        if(!HasMobile) return "";
        //从数据库里获取 balabalabala
    }
}

按需获取 ,而不是每次加载都获取,使用属性还能提高性能,对于属性,两个字:牛x。

隐式类型 var

对于var关键字,需要说的不多,看一下就知道。

//传统定义
int value=100;
//使用var定义
var value=100;

在IL中,以上两句代码是完全相同的。因为C#是强类型的编程语言,使用var定义的变量同样是强类型的,当我们放到变量上的时候,万能的VS会在我们为我们提示出它所推断出的CLR类型,如果你对var定义的变量使用点的话,它还会列出该类型的所有成员。

var i = 0;
var l = 0L;
var s = "Hello Boy";
var b = true;
var sb = new StringBuilder();
var rnd = new Random();
var fs = new FileStream("c:\test.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);

int ii = 0;
long ll = 0L;
string str = "Hello Boy";
bool bb = true;
StringBuilder sbsb = new StringBuilder();
Random rndnd = new Random();
FileStream fss = new FileStream("c:\test.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); 

对于这两类写法,是仁者见仁,智者见智的,不过我更喜欢使用var的方式。
需要注意的是,使用var的时候,必须给变量赋值以推断出变量的类型,否则编译失败

匿名对象 没有类型的对象

有些朋友又该疑惑了,不是说C#是强类型的编程语言么,怎么又会出现 没有类型的对象?且看如下代码:

var person = new { Name="Tom",Age=20};

这种类似JSON的对象又是什么呢,IL会告诉我们如下信息

.locals init (
    [0] class '<>f__AnonymousType0`2'<string, int32> person
)

IL_0000: nop
IL_0001: ldstr "Tom"
IL_0006: ldc.i4.s 20
IL_0008: newobj instance void class '<>f__AnonymousType0`2'<string, int32>::.ctor(!0,  !1)
IL_000d: stloc.0

所以,匿名类型并不是没有类型,而是在编译时,编译器会为我们生成一个类型,同时,如果你再深入一点仔细看它生成的类,它并没有对属性的set方法,因而 匿名类型是只读的
匿名类型在linq中的应用是比较多,有了它,我们不需要定义一堆的类来进行linq的解析,但这种类型在运行时又确确实实是作为一个实在的类存在,这种语法规则是相当优雅行而有效的。

动态类型

dynamic a = 1;
a = "b";

编译并不会报错!很神奇的代码吧, 给什么值就是什么类型,MVC里的ViewBag就是这货了。

dynamic mbag=new ExpandObject();
mbag.intValue=10;
mbag.message="hello";
Action<string> act=(str)=>(console.write(str);)
mbag.say=act;
mbag.say("hello");

类型初始化器

传统写法

var p=new Person();
p.Name="Tom";
p.Age=20;

新型写法

var p=new Person{
    Name="Tom",
    Age=20    
};

连小括号都不要哦,强大。

用途

using(var conn = new SqlConnection(connStr))
{
    var cmd = conn.CreateCommand();
    cmd.CommandText = "GetPageRecord";
    cmd.Parameters.AddRange(new SqlParameter[]{
        new SqlParameter("@pageSize",SqlDbType.Int){Value=20},
        new SqlParameter("@pageIndex",SqlDbType.Int){Value=2},
        new SqlParameter("@totalCount",SqlDbType.BigInt){Direction=ParameterDirection.Output}                
    });
    var records=cmd.Execute();
    var count = (int)cmd.Parameters["totalCount"].Value;
}

我常常会在sql传参的时候使用这种方式

集合初始化

与类型初始化器类似的,我们可以对List、Dictionary等集合进行这样的初始化。

var list = new List<string>
{
    "Tom","Jake","Jerry","Mary"
};
var dic = new Dictionary<int, string>
{
    {1001,"Tom"},
    {1002,"Jake"},
    {1003,"Jerry"},
    {1004,"Mary"}
};

进阶

如果你已经用上了C#6.0,可以这样写

var dic = new Dictionary<int, string>
{
    [1001]="Tom",
    [1002]="Jake",
    [1003]="Jerry",
    [1004]="Mary"
};

using

using(var conn=new SqlConnection(connStr))
{
    //balabalabala
}
//等同于
SqlConnection conn;
try
{
    conn=new SqlConnection(connStr);
    //balabalabala
}
finally
{
    if(conn!=null)conn.Dispose();
}

using后会调用对象的Dispose方法,所以 使用using包起来的类型须实现IDispose接口

?: 和 ?? 和 ?.

作为唯一的三目运算符?:自不必多说

var str=a>=b?"a大于等于b":"b大于a";

对于??

object a = null;
var b = a ?? "a为空";
//等同于
var b=a!=null?a:"a为空";

很清楚吧,b的类型与a相同。

进阶

如果已经用上了C# 6.0,还可以这样写

int? GetCount()
{
    return list?.Count;
}
//等同于
int? GetCount()
{
    if(list==null) return null;
    return list.Count;
}


lamda表达式,linq

对于这两者,大可写无数篇幅来进行讨论,以下仅列举出简单的例子

lamda

delegate string SDelegate(string a);
string M1(string a)
{
    return a.ToUpper();
}
void test()
{
    d = new SDelegate(M1);
    //等同于
    d = new SDelegate(delegate(string a) {
        return a.ToUpper();
    });
    //等同于
    d = new SDelegate((a) =>
    {
        return a.ToUpper();
    });
    //等同于
    d = new SDelegate(a=>a.ToUpper());

    d("aaa");   //=AAA
}

linq

在VS中输入Enumerable,然后F12吧,好多好多,举例如下:

var list=goods
        .Where(x=>x.Age>80)
        .OrderBy(x=>x.Score)
        .Skip((pageIndex-1)*pageSize)
        .Take(pageSize)
        .Select(x=>new {
            Name=x.Name,
            Age=x.Age,
            Score=x.Score
        })
        .ToList();

是不是特别优雅。

扩展方法

定义

扩展方法使得使用者可以向现有的类型“添加”方法而无须创建新的类型、重新编译或以其他方式修改原始类型,从表现上看,扩展方法是静态方法,但可以像实例方法一样被调用。其实上述linq的方法也大多是对[System.Collections.IEnumerable]和[System.Collections.Generic.IEnumerable<T>]的扩展。

实例

public static class Exten
{
    public static String HtmlDecode(this string o)
    {
        if (o == null) return "";
        var s = o.ToString();
        if (string.IsNullOrWhiteSpace(s)) return "";
        return HttpUtility.HtmlDecode(s);
    }
}

这样我们就可以对字符串使用HtmlDecode这个“实例”方法了

Console.WriteLine("&lt;textarea&gt;你好啊,我要告诉你1&gt;0。&lt;textarea&gt;".HtmlDecode());

注意

  • 扩展方法须放置在静态类中;
  • 扩展方法必须声明为static;
  • 扩展方法的第一个参数需要使用this关键字修饰;
  • 扩展方法的第一个参数是要要扩展的类型,若要扩展string,则第一个参数形如this string str
  • 使用扩展方法的类必须引用扩展方法所在类的命名空间。
  • 扩展方法的优先级比类的同名实例方法优先级低。

建议

尽管扩展方法的使用会给开发者带来效率上的极大提升,但是微软建议只有在不得不使用扩展方法的时候才使用扩展方法,并且要谨慎地实现,因为您写出的扩展方法完全有可能不被调用,当使用其它类库的时候更要避免冲突。

ETC.

C#为我们提供的便利远远不止文中提到的这么多,也不是笔者寥寥数字所能阐述完全的,.NET版本的每一次迭代都会产生更多的语法特性,亦可称之为“语法糖”,就上面时不时提到的C#6.0而言,也还有不少其他特性,参见New features in C# 6.0。其余的各位自行探索,毕竟,我们是钟爱.NET的,不是吗?