Web开发之C#:(8)C#语言特性

作者:陆金龙    发表时间:2016-07-21 00:25   


C#语言主要特性

以下内容一部分从Jon Skeet的《深入理解C#(中文第三版)》(译者:姚琪琳)整理而来,有进一步兴趣的朋友可以阅读原著。

本文从互联网获取了部分内容做为素材。如果您认为侵犯到您的权利,请联系本人进行处理,本人邮箱:kinglong1984@126.com。

 

C#1和Java语言非常相似,C#还包含一些额外的特性:属性、委托和事件、foreach循环、using语句、显示方法重载、操作符重载、自定义值类型等。

 

C#1中“委托”是一个语言级特性,而在Java语言中没有直接的对应,但是我们可以通过动态代理来实现委托!

C#2与类型相关的主要新特性是泛型;同时通过匿名方法对C#1的委托进行了增强。

C#3的核心是LINQ,可通过Lambda表达式实例化委托,是对委托的进一步增强。

C#4之前,已引入了很多与动态(及函数式)语言相关的特性。

C#4增加了真正实用的动态类型。

C#5新增了异步编程的功能。

C#1

委托

委托类型实际上是参数类型的一个列表以及一个返回类型。规定了类型的实例能表示的操作。委托类型声明决定了哪个方法可用于创建委托实例。

例:public delegate stringt MyDelegate(int x, int y);表示有两个参数,并返回string型。

事件

事件存在的理由和“属性”差不多,添加了一个封装层,实现发布/订阅模式。

在订阅或取消一个事件时,看起来就像是在通过 =和-=运算符使用委托类型的字段。

编译器会将字段风格的事件转换成一个具有默认add/remove实现的事件和一个私有委托类型的字段。类内的代码能看见字段,类外的代码只能看见事件。表面上似乎在调用一个事件,但为了调用事件处理程序,实际做的事情是调用存储在字段中的委托实例。

值类型和引用类型

C# 语言的类型分为两大类:值类型 (Value type) 和引用类型 (reference type)。

结构和枚举是值类型。

类、数组、委托、接口是引用类型。不过接口可由值类型实现。

静态、显式、安全的类型

C#1的类型系统是静态的、显式的和安全的。

每个变量都有一个特定的类型,该类型在编译时是已知的,类型已知的操作才是允许的,由编译器强制生效。(与显式类型相对的C#3中的隐式类型,隐式类型也是静态类型的一种。

C#2

泛型

泛型就是通过将数据类型参数化从而实现了代码的更为灵活的复用,泛型的出现使得C#能够使用同一段代码来操作多种数据类型。泛型的出现赋予了C#代码更强的类型安全,更好的复用,更高的效率和更清晰的约束。

 

在没有泛型集合的时候,使用ArrayList。ArrayList中的元素的类型始终为object,这样很多情况下出现装箱和拆箱的发生,常常得使用强制转换来重新获得具体类型的元素。

List<T>可以为集合类型指定元素的数据类型。

属性具有私有的赋值方法。

 

泛型为排序提供了更便利的实现,以下例子使用了匿名方法

List<Product> products = new List<Product>(){

new Product(“Book”,22.36m);

new Product(“Pen”,19.99m);

new Product(“NoteBook”,1.9m);

};

products.Sort(delegrate(Product x,Product y)

{return x.Name.CompareTo(y.Name);}

);

 

泛型集合在查询方面的便利:

说明:Predicate<Product> 即参数类型为Product,返回类型为bool的泛型委托。

Predicate<Product> dele = delegate(Product p) { return p.Price > 10m};

List<Product> matches = products.FindAll(dele);

 

说明:Action<Product>即参数类型为Product,无返回值的泛型委托。

Action<Product> print = Console.WriteLine;

matches .ForEach(print);

匿名方法

C# 2的匿名方法是对C#1中委托类型的增强,简化了委托类型实例化的过程。

匿名方法允许我们将代码直接与委托实例相关联,不再需要单独声明一个函数来用于创建委托实例,只需要使用delegate关键字编写事件实现代码。

迭代器

迭代器允许我们更加方便的编写用于foreach语句的类型。

分部类型

局部类型允许我们将一个类的代码分别写在不同的cs文件中。局部类型通过partial关键字来声明。

 

C#3

LINQ

LINQ是C#3的核心。LINQ全称Luanguage Integrated QUery,语言集成查询。

C# 3.0中加入的最为复杂的特性就是LINQ查询表达式了,这使我们可直接采用类似于SQL的语法对集合进行查询,这就使我们可以享受到关系数据查询的强大功能。

Linq查询表达式是建立在多种C# 3.0的新特性之上的。Linq查询的强大特性,允许我们进行简单查询,或者进行更为复杂的多重连接查询。且查询的结果还可以是自定义的匿名类型。例如:

var filtered = from Product p in products

where p.Price >10

select p;

foreach(Product product in filtered)

{

Console.WriteLine(product);

}

LINQ查询与Lambda表达式比起来,看上去没有那么简洁了,但是对于处理复杂一些查询很有用武之地,并且在复杂情况下LINQ查询语句可读性的优势很明显。

另外LINQ查询还有连接查询、排序、分组等很多类似SQL的强大功能。

 

Linq 查询可针对 Object,XML,SQL进行,查询语法基本相同。

 

(1)Linq To Object

 

List<Product> products =Product.GetData();

List<Supplier> suppliers=Supplier.GetData();

var objs = from p in products

   join s in suppliers

              on p.SupplierId equals s.SupplierId

           where p.Price>10

           order by s.Name,p.Name

           select new{SupplierName=s.Name,ProductName=p.Name};

foreach(var obj in objs)

{

Console.WriteLine(“{0},{1}”,obj.SupplierName,obj.ProductName);

}

按名称排序和过滤重复数据:

List<Product> productsNew = (from product in products select product).OderBy<Product (p=>p.Name).Distinct().ToList();

 

(2)Linq To XML

 

data.xml文档内容:

<?xml version=”1.0”?>

<Data>

<products >

<product Name=”West” Price=”9.99” SupplierId=”1” />

<product Name=”Frogs” Price=”13.99” SupplierId=”3” />

</products >

<suppliers >

<supplier Name=”Solely” SupplierId=”1” />

<supplier Name=”Barber” SupplierId=”2” />

<supplier Name=”Tom” SupplierId=”3” />

</suppliers >

</Data>

 

Linq To Xml 查询:

Xdocument doc = Xdocument.Load(“data.xml”);

var objs = from p in doc.Decendants(“products”);

   join s in doc.Decendants(“suppliers”);

              on (int)p.Attribute(“SupplierId”) equals (int)s.Attribute(“SupplierId”)

           where (decimal)p.Attribute(“Price”)>10

           order by (string)s.Attribute(“Name”),(string)p.Attribute(“Name”)

           select new{SupplierName=(string)s.Attribute(“Name”),ProductName=(string)p.Attribute(“Name”)

};

foreach(var obj in objs)

{

Console.WriteLine(“{0},{1}”,obj.SupplierName,obj.ProductName);

}

 

(3)Linq To SQL

 

查询是用C#代码来表示的,但是却是由SQL来执行的。

DataContext:

DataContext类型(数据上下文)是System.Data.Linq命名空间下的重要类型,用于把查询句法翻译成SQL语句,以及把数据从数据库返回给调用方和把实体的修改写入数据库。

LinqDemoDataContext db = new LinqDemoDataContext();

Linq To SQL 查询:

using(LinqDemoDataContext db = new LinqDemoDataContext())

{

var objs = from p in db.Products

   join s in db.Suppliers

              on p.SupplierId equals s.SupplierId

           where p.Price>10

           order by s.Name,p.Name

           select new{SupplierName=s.Name,ProductName=p.Name};

foreach(var obj in objs)

{

Console.WriteLine(“{0},{1}”,obj.SupplierName,obj.ProductName);

}

}

 

Lambda表达式

C#3中Lambda表达式是对委托类型进一步增强,是对匿名方法的简化,便于更简洁地创建委托实例。

新的Lambda事件处理代码看上去就像一个计算表达式,它使用"=>"符号来连接参数和处理代码。

在使用Lambda表达式编写处理代码时,无需指明参数的类型,且返回值就是最后一条语句的执行结果。

 

C#3中使用Lambda表达式可使前面例子中的排序有更简洁的实现。

List<Product> products = new List<Product>(){

new Product(“Book”,2.36m);

new Product(“Pen”,9.99m);

new Product(“NoteBook”,1.9m);

};

products.Sort((x,y)=>x.Name.CompareTo(y.Name));

另外,使用Orderby方法则可以在不改变原有集合的情况下,实现按需要的顺序遍历和处理。

foreach(Product product in products.OrderBy(p=p.Name))

{

Console.WriteLine(product);

}

 

泛型集合 Lambda表达式在查询方面的便利:

products.FindAll(p=>p.Price > 10m}).ForEach(Console.WriteLine);

匿名类型

有些时候我们需要临时保存一些运算的中间结果,特别是当这些中间结果是由多个部份组成时,我们常常会去声明一个新的类型,这个新类型只服务于这个函数,其它地方都不会再使用它了,就为这一个函数而去定义一个新的类型,确实有些麻烦。

C#3.0中的匿名类型特性就可以很好的解决上面提到的问题,通过匿名类型,我们可以简单使用new { 属性名1=值1, 属性名2=值2, ..... , 属性名n=值n }的形式直接在函数中创建新的类型。

在新类型中只能有字段成员,而且这些字段的类型也是通过初值的类型推断出来的。如果在声明新的匿名类型时,新类型的字段名、顺序以及初始值的类型是一致的,那么将会产生相同的匿名类型,当类型相同时,能进行赋值。

集合初始化器

C# 3.0中加入的集合初始化器,可使我们享受到与普通数组一样的待遇,从而在创建集合对象的同时为其指定初始值。为了做到这一点,需要让我们的集合实现ICollection<T>接口,在这个接口中,完成初始化操作的关键在于Add函数,当使用初始化语法为集合指定初始值时,C#编译器将自动调用ICollection<T>中的Add函数将初始列表中的所有元素加入到集合中,以完成集合的初始化操作。

对象初始化器

C#3.0中加入的对象构造者特性,使得对象的初始化工作变得格外简单,我们可以采用类似于数组初始化的方式来初始化类的对象,方法就是直接在创建类对象的表达式后面跟上类成员的初始化代码。

扩展方法

当我们需要对已有类的功能进行扩展时,我们通常会想到继承,继承已有类,然后为其加入新的行为。而C# 3.0中加入的扩展方法特性,则提供了另一种实现功能扩展的方式,我们可以在不使用继承的前提下实现对已有类本身的扩展,这种方法并不会产生新的类型,而是采用向已有类中加入新方法的方式来完成功能扩展。

在对已有类进行扩展时,我们需将所有扩展方法都写在一个静态类中,这个静态类就相当于存放扩展方法的容器,所有的扩展方法都可以写在这里面。而且扩展方法采用一种全新的声明方式:public static 返回类型 扩展方法名(this 要扩展的类型 sourceObj [,扩展方法参数列表]),与普通方法声明方式不同,扩展方法的第一个参数以this关键字开始,后跟被扩展的类型名,然后才是真正的参数列表。

 

隐式类型化本地变量

使用"var"关键字来声明局部变量,而不再需要指明变量的确切类型了,变量的确切类型可通过声明变量时的初始值推断出来。可以大大简化我们声明局部变量的工作量。

由于变量的类型是通过变量初始值推断而来的,所以在声明变量的同时必需为变量指定初始值。并且,变量并不是没有类型的,变量一旦初始化之后,类型就确定下来了,以后就只能存储某种类型的值了。

隐式类型还是静态类型,是在编译时就能通过类型推断确定具体类型的。

隐式类型化数组

这个特性是对隐式类型化本地变量的扩展,有了这个特性,将使我们创建数组的工作变得简单。我们可以直接使用"new[]"关键字来声明数组,后面跟上数组的初始值列表。在这里,我们并没有直接指定数组的类型,数组的类型是由初始化列表推断出来的。

var intArray=new[]{1,2,3,4,5};

var doubleArray=new[]{3.14,1.414};

自动属性

自动实现属性,节约了很多代码。

class Product

{

public string Name{ get; private set;}

public decimal Price{get;private set;}

}

 

C#4

命名实参

提供了命名实参,当参数较多且有多个重载时,使用命名实参能改善代码的清晰度。

new Product(name:“Book”,price:3.99m);等效于new Product(“Book”,9.99m);

可选参数

C#4可以为参数声明一个默认值,这样在调用的时候可以不传该参数。这个默认值必须是一个常量值,不一定为null。

在C#4之前,要实现同样的功能,需要提供一个重载。

public Product(string name,decimal? price =null)

{

this.name=name;

this.price = price;

}

 

简化COM互操作性

互操作性是C#4最重要的主题。以下是将数据保存到电子表格的例子:

var app = new Application(Visible = false);

Workbook workbook = app.Workbook.Add();

Worksheet worksheet = app.ActiveSheet;

int row =1;

foreach(var product in producs)

{

worksheet.Cells[row,1].Value = product.Name;

worksheet.Cells[row,2].Value=product.Price;

row ;

}

workbook.SaveAs(Filename:”demo.xls”,FileFormat:XlFileFormat.xlWorkNormal);

app.Application.Quit();

与动态语言互操作

dynamic 如果不能执行成功,只能在执行时才报错,编译时是不报错的。

动态类型只有在表达式为dynamic时才有效。

 

dynamic arg =demo==0?(dynamic)5:(dynamic)”A”;//此处使用与object类似:

dynamic result=Plus(arg);

private static dynamic Plus(dynamic arg)

{

return arg arg; //根据传入参数的实例类型不同,自动采用有不同的运算规则

}

 

C#5

异步编程

C#5的超级特性:异步函数。

个人认为该特性比较抽象,代码处理不好的话,会很绕,可读性和可维护性不好,慎用。

 

public class Test 

{  

    public Test ()  

    {  

        AsynTest(); 

       Console.WriteLine("构造函数结束。");  

}  

 

  //该方法的执行不会阻塞主线程  

  public async void AsynTest()  

    {  

         int value= await GetNewAsync(100);

         //之后的所有代码在GetNewAsync任务完成时调用  

         Console.WriteLine("新的值是: "   value);  

      } 

 

 

    public Task<int> GetNewAsync(int num)  

    {  

        return Task.Run(() =>  

        {  

            for (int i = 0; i < 1000000; i )  

            {  

                num=4*(num  num)/7;  

            }  

            return num;  

        });  

    }