Web开发之C#:(8)C#语言特性
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;
});
}
}