C#高级知识点概要(3) - 特性、自动属性、对象集合初始化器、扩展方法、Lambda表达式和Linq查询
1.特性(Attributes)
特性(Attributes),MSDN的定义是:公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型、字段、方法和属性等。Attributes和Microsoft .NET Framework文件的元数据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响应用程序的行为。
例如,在一个方法前标注[Obsolete]特性,则调用该方法时VS则会提示该方法已过期的警告,如下图:
又如,在.Net Remoting的远程对象中,如果要调用或传递某个对象,例如类,或者结构,则该类或结构则必须标注[Serializable]特性。还有,我们在构建XML Web服务时用得很多的一个特性就是[WebMegthod],它可让通过HTTP请求的公开方法的返回值编码成XML进行传递。
特性实际上就是一个类,[Obsolete]特性的实际类名是ObsoleteAttribute,但我们在标注的时候可以不带Attribute后缀,系统在名称转换时会自动给我们加上。
上面说的都是些.NET系统定义的一些特性,当然还有很多。了解如何自定义特性,有利有我们更好的在ASP.NET MVC编程使用特性,比如给Model类的属性标注特性来验证表单输入的合法性(以后进行介绍)。
下面我们来模拟一个ASP.NET MVC经常要用到的StringLenth特性,它用于判断用户输入是否超出长度限制。我们现在来模拟它。先定义一个MyStringLenth特性:
// 用户自定义的带有可选命名参数的 MyStringLenthAttribute 特性类。
// 该特性通过AttributeUsage限制它只能用在属性和字段上。
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class MyStringLenthAttribute : Attribute {
public MyStringLenthAttribute(string displayName, int maxLength) {
this.MaxLength = maxLength;
this.DisplayName = displayName;
}
//显示的名称,对外是只读的,所以不能通过可选参数来赋值,必须在构造函数中对其初始化。
public string DisplayName { get; private set; }
//长度最大值,对外是只读的,所以不能通过可选参数来赋值,必须在构造函数中对其初始化。
public int MaxLength { get; private set; }
//错误信息,标注时可作为可选命名参数来使用。
public string ErrorMessage { get; set; }
//长度最小值,标注时可作为可选命名参数来使用。
public int MinLength { get; set; }
}
上面若不加AttributeUsage限制,特性可以声明在类型(如结构、类、枚举、委托)和成员(如方法,字段,事件,属性,索引)的前面。
然后我们把这个特性应用在下面的Order类之上:
// 应用自定义MyStringLenth特性于Order类的OrderID属性之上。MinLength和ErrorMessage是命名参数。
public class Order {
[MyStringLenth("订单号", 6,MinLength = 3, ErrorMessage = "{0}的长度必须在{1}和{2}之间,请重新输入!")]
public string OrderID { get; set; }
}
最后我们看看如何使用MyStringLenth特性验证用户输入字符串的长度:
//检查成员字符串长度是否越限。
private static bool IsMemberValid(int inputLength, MemberInfo member) {
foreach (object attribute in member.GetCustomAttributes(true)) {
if (attribute is MyStringLenthAttribute) {
MyStringLenthAttribute attr=(MyStringLenthAttribute)attribute;
string displayName = attr.DisplayName;
int maxLength = attr.MaxLength;
int minLength = attr.MinLength;
string msg = attr.ErrorMessage;
if (inputLength < minLength || inputLength > maxLength) {
Console.WriteLine(msg, displayName, minLength, maxLength);
return false;
}
else {
return true;
}
}
}
return false;
}
//验证输入是否合法
private static bool IsValid(Order order) {
if (order == null) return false;
foreach (PropertyInfo p in typeof(Order).GetProperties()) {
if (IsMemberValid(order.OrderID.Length, p))
return true;
}
return false;
}
public static void Main() {
string input=string.Empty;
Order order;
do {
Console.WriteLine("请输入订单号:");
input = Console.ReadLine();
order = new Order { OrderID = input };
}
while (!IsValid(order));
Console.WriteLine("订单号输入正确,按任意键退出!");
Console.ReadKey();
}
输出效果如下:
2.自动属性
在 C# 3.0 和更高版本中,当属性的访问器中不需要其他逻辑时,自动实现的属性可使属性声明更加简洁。
下面示例演示了属性的标准实现和自动实现:
class Program {
class Person {
//标准实现的属性
int _age;
public int Age {
get { return _age; }
set {
if (value < 0 || value > 130) {
Console.WriteLine("设置的年龄有误!");
return;
}
_age = value;
}
}
//自动实现的属性
public string Name { get; set; }
}
static void Main(string[] args) {
Person p = new Person();
p.Age = 180;
p.Name = "小王";
Console.WriteLine("{0}今年{1}岁。",p.Name,p.Age);
Console.ReadKey();
}
}
自动属性也可以有不同的访问权限,如:
public string Name { get;private set; }
注意,自动属性不能定义只读或者只写的属性,必须同时提供get和set访问器:
public string Name { get; }//编译出错
public string PetName { set; }//编译出错
3.对象和集合的初始化器
上面我们演示自动属性的时候给对象的实例初始化时是一个一个属性进行赋值的,有多少个属性就需要多少句代码。C# 3.0和更高版本中有了对象集合初始化器,有了它,只需一句代码就可初始化一个对象或一个对象集合的所有属性。这在里先创建一个“商品”类,用于后面的示例演示:
/// <summary>
/// 商品类
/// </summary>
public class Product {
/// <summary>
/// 商品编号
/// </summary>
public int ProductID { get; set; }
/// <summary>
/// 商品名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 商品描述
/// </summary>
public string Description { get; set; }
/// <summary>
/// 商品价格
/// </summary>
public decimal Price { get; set; }
/// <summary>
/// 商品分类
/// </summary>
public string Category { set; get; }
}
基于上面定义好的商品类,下面代码演示了如何通过初始化器来创建商品类的实例对象和集合:
static void Main(string[] args) {
//对象初始化器的使用 (可只给部分字段赋值)
Product product = new Product { ProductID = 1234, Name = "西瓜", Price = 2.3M };//创建并初始化一个实例
//集合初始化器的使用
List<Product> proList = new List<Product> {
new Product { ProductID = 1234, Name = "西瓜", Price = 2.3M },
new Product { ProductID = 2345, Name = "苹果", Price = 5.9M },
new Product { ProductID = 3456, Name = "樱桃", Price = 4.6M }
};
//打印
Console.WriteLine("对象初始化器:{0} {1} {2}", product.ProductID, product.Name, product.Price);
foreach (Product p in proList) {
Console.WriteLine("集合初始化器:{0} {1} {2}", p.ProductID, p.Name, p.Price);
}
Console.ReadKey();
}
另外还有一些其它类型也可以使用初始化器,如下:
//数组使用初始化器
string[] fruitArray = {"apple","orange","plum" };
//匿名类型使用初始化器
var books = new { Title = "ASP.NET MVC 入门", Author = "小王", Price = 20 };
//字典类型使用初始化器
Dictionary<string, int> fruitDic = new Dictionary<string, int>() {
{ "apple", 10 },
{ "orange", 20 },
{ "plum", 30 }
};
4.扩展方法
扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型或修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。例如,我们可以让Random类的所有实例对象拥有一个返回随机bool值的方法。我们不能对Random类本身进行修改,但可以对它进行扩展,如下代码所示:
static class Program {
/// <summary>
/// 随机返回 true 或 false
/// </summary>
/// <param name="random">this参数自动指定到Random的实例</param>
/// <returns></returns>
public static bool NextBool(this Random random) {
return random.NextDouble() > 0.5;
}
static void Main(string[] args) {
//调用扩展方法
Random rd = new Random();
bool bl = rd.NextBool();
Console.WriteLine(bl.ToString());
Console.ReadKey();
}
}
注意,扩展方法必须在非泛型的静态类中定义,上面的Program类如不加static修饰符则会报错。
我们可以创建一个接口的扩展方法,这样实现该接口的类都可以调用该扩展方法。看下面一个完整示例:
/// <summary>
/// 购物车类 (实现 IEnumerable<Product> 接口)
/// </summary>
public class ShoppingCart : IEnumerable<Product> {
public List<Product> Products { get; set; }
public IEnumerator<Product> GetEnumerator() {
return Products.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
/// <summary>
/// 定义一个静态类,用于实现扩展方法(注意:扩展方法必须定义在静态类中)
/// </summary>
public static class MyExtensionMethods {
/// <summary>
/// 计算商品总价钱
/// </summary>
public static decimal TotalPrices(this IEnumerable<Product> productEnum) {
decimal total = 0;
foreach (Product prod in productEnum) {
total += prod.Price;
}
return total;
}
}
class Program {
static void Main(string[] args) {
// 创建并初始化ShoppingCart实例,注入IEnumerable<Product>
IEnumerable<Product> products = new ShoppingCart {
Products = new List<Product> {
new Product {Name = "Kayak", Price = 275},
new Product {Name = "Lifejacket", Price = 48.95M},
new Product {Name = "Soccer ball", Price = 19.50M},
new Product {Name = "Corner flag", Price = 34.95M}
}
};
// 创建并初始化一个普通的Product数组
Product[] productArray = {
new Product {Name = "Kayak", Price = 275M},
new Product {Name = "Lifejacket", Price = 48.95M},
new Product {Name = "Soccer ball", Price = 19.50M},
new Product {Name = "Corner flag", Price = 34.95M}
};
// 取得商品总价钱:用接口的方式调用TotalPrices扩展方法。
decimal cartTotal = products.TotalPrices();
// 取得商品总价钱:用普通数组的方式调用TotalPrices扩展方法。
decimal arrayTotal = productArray.TotalPrices();
Console.WriteLine("Cart Total: {0:c}", cartTotal);
Console.WriteLine("Array Total: {0:c}", arrayTotal);
Console.ReadKey();
}
}
执行后输出如下结果:
5.Lambda 表达式
Lambda
表达式和匿名函数其实是一件事情。不同是,他们语法表现形式不同,Lambda
表达式在语法上实际上就是匿名函数的简写。直接介绍匿名函数和Lambda表达式的用法没什么意思,在这里,我要根据实际应用来讲一个两者用法的例子,这
样在介绍知识点的同时也能和大家分享一下解决问题的思想。
假如我们要实现一个功能强大的商品查询方法,这个商品查询方法如何查询商品是可以由用户自己来决定的,用户可以根据价格来查询商品,也可以根据分类
来查询商品等等,也就是说用户可以把自己的查询逻辑传递给这个查询方法。要编写这样一个方法,我们很自然的会想到用一个委托来作为这个方法的参数,这个委
托就是用户处理商品查询的逻辑。
我们不防把这个查询方法称为“商品查询器”。我们可以用静态的扩展方法来实现这个“商品查询器“,这样每个商品集合对象(如
IEnumerable<Product>
products)可以直接调用该静态方法返回查询结果。解决问题的思想有了,接下来就是实现了。或许你对这一段描述有点蒙,结合代码可能让你更清晰。下
面是这个“商品查询器”-Filter方法的实现代码:
/// <summary>
/// 定义一个静态类,用于实现扩展方法
/// </summary>
public static class MyExtensionMethods {
/// <summary>
/// 商品查询器
/// </summary>
/// <param name="productEnum">扩展类型的实例引用</param>
/// <param name="selectorParam">一个参数类型为Product,返回值为bool的委托</param>
/// <returns>查询结果</returns>
public static IEnumerable<Product> Filter(this IEnumerable<Product> productEnum, Func<Product, bool> selectorParam) {
foreach (Product prod in productEnum) {
if (selectorParam(prod)) {
yield return prod;
}
}
}
}
没错,我们就是用这么简短的Filter方法来满足各种需求的查询。上面Product类使用的是前文定义的。这里也再一次见证了扩展方法的功效。为了演示Filter查询方法的调用,我们先来造一批数据:
static void Main(string[] args) {
// 创建商品集合
IEnumerable<Product> products = new ShoppingCart {
Products = new List<Product> {
new Product {Name = "西瓜", Category = "水果", Price = 2.3M},
new Product {Name = "苹果", Category = "水果", Price = 4.9M},
new Product {Name = "ASP.NET MCV 入门", Category = "书籍", Price = 19.5M},
new Product {Name = "ASP.NET MCV 提高", Category = "书籍", Price = 34.9M}
}
};
}
接下来我们继续在上面Main方法中来调用查询方法Filter:
//用匿名函数定义一个具体的查询需求
Func<Product, bool> fruitFilter = delegate(Product prod) {
return prod.Category == "水果";
};
//调用Filter,查询分类为“水果”的商品
IEnumerable<Product> filteredProducts = products.Filter(fruitFilter);
//打印结果
foreach (Product prod in filteredProducts) {
Console.WriteLine("商品名称: {0}, 单价: {1:c}", prod.Name, prod.Price);
}
Console.ReadKey();
输出结果为:
上面我们使用的是委托和匿名函数来处理用户查询逻辑,并把它传递给Filter方法,满足了前面所说的需求。但若使用Lambda表达式代替上面的匿名函数能使上面的代码看上去更简洁更人性化,如下代码所示:
Func<Product, bool> fruitFilter = prod => prod.Category == "水果"; IEnumerable<Product> filteredProducts = products.Filter(fruitFilter);
没有了delegate关键字,没有了大小括号,看上去更舒服。当然上面两行代码可以继续简化为一行:
IEnumerable<Product> filteredProducts = products.Filter(prod => prod.Category == "水果");
这三种方式输出结果都是一样的。然后,我们还可以通过Lambda表达式实现各种需求的查询:
//查询分类为“水果”或者单价大于30元的商品
IEnumerable<Product> filteredProducts = products.Filter(prod =>
prod.Category == "水果" || prod.Price > 30
);
通过这个示例,相信大家已经清晰的了解并撑握了Lambda表达式的简单应用,而这就足够了:)。
6.LINQ
最后简单回顾一下LINQ。LINQ(Language Integrated Query语言集成查询)是 VS 2008 和 .NET Framework 3.5 版中一项突破性的创新,它在对象领域和数据领域之间架起了一座桥梁。
上面讲Lambda表达式时,用到的查询结果集的方式未免还是有点麻烦(因为自定义了一个Filter扩展方法),而Linq本身就集合了很多扩展方法,我们可以直接使用,大大的简化了编写查询代码的工作。例如,对于这样一个数据集合:
Product[] products = {
new Product {Name = "西瓜", Category = "水果", Price = 2.3M},
new Product {Name = "苹果", Category = "水果", Price = 4.9M},
new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M},
new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M}
};
如果要查询得到价钱最高的三个商品信息,如 果不使用Linq,我们可能会先写一个排序方法,对products根据价钱由高到低进行排序,排序时需要创建一个新的Product[]对象用于存储排 序好的数据。但用Linq可大大减少工作量,一两句代码就能搞定。如下代码所示,查出价钱最高的三个商品:
var results = from product in products
orderby product.Price descending
select new {
product.Name,
product.Price
};
//打印价钱最高的三个商品
int count = 0;
foreach (var p in results) {
Console.WriteLine("商品:{0},价钱:{1}", p.Name, p.Price);
if (++count == 3) break;
}
Console.ReadKey();
输出结果:
能熟练使用Linq是一件很爽的事情。上面
的Linq语句和我们熟悉的SQL查询语句类似,看上去非常整洁且易懂。但并不是每一种SQL查询语句在C#都有对应的关键字,有时候我们需要使用另外一
种Linq查询方式,即“点号”方式的Linq查询方式,这种方式中的Linq查询方法都是扩展方法。如下面这段代码和上面实现的效果是一样的:
var results = products
.OrderByDescending(e => e.Price)
.Take(3)
.Select(e => new { e.Name,e.Price});
foreach (var p in results) {
Console.WriteLine("商品:{0},价钱:{1}", p.Name, p.Price);
}
Console.ReadKey();
虽然类SQL的Linq查询方式比这种方式看上去更一目了然,但并不是每一种SQL查询语句在C#都有对应的关键字,比如这里的Take扩展方法就是类SQL的Linq查询语法没有的功能。
注意,有些Linq扩展方法分为“延后查询”(deferred)和“即时查询”(immediate)。延后查询意思是拥有“延后查询”扩展方法 的Linq语句只有当调用结果集对象的时候才开始真正执行查询,即时查询则是立即得到结果。比如上面的Linq语句的OrderByDescending 扩展方法就是一个“延后查询”方法,当程序执行到Linq语句定义部分时并没有查询出结果并放到results对象中,而是当程序执行到foreach循 环时才真正执行Linq查询语句得到查询结果。我们可以做个测试,在Ling语句之后,我们再将products[1]对象重新赋值,如下代码所示:
var results = products
.OrderByDescending(e => e.Price)
.Take(3)
.Select(e => new { e.Name, e.Price });
//在Linq语句之后对products[1]重新赋值
products[1] = new Product { Name = "榴莲", Category = "水果", Price = 22.6M };
//打印
foreach (var p in results) {
Console.WriteLine("商品:{0},价钱:{1}", p.Name, p.Price);
}
Console.ReadKey();
输出结果为:
我们发现results是重新赋值之后的结果。可想而知,查询语句是在results被调用之后才真正执行的。
C#高级知识点概要(3) - 特性、自动属性、对象集合初始化器、扩展方法、Lambda表达式和Linq查询的更多相关文章
- [ASP.NET MVC 大牛之路]03 - C#高级知识点概要(2) - 线程和并发
本人博客已转移至:http://www.exblr.com/liam 我也想过跳过C#高级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的.我希望通过自己的经验给大家一些指引,带着大家一起 ...
- C#高级知识点概要(2) - 线程和并发
原文地址:http://www.cnblogs.com/Leo_wl/p/4192935.html 我也想过跳过C#高级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的.我希望通过自己的经验 ...
- C#中的自动属性、隐式类型var、对象初始化器与集合初始化器、扩展方法
1.自动属性(Auto-Implemented Properties) //以前的写法 .net2.0 private string _userName; public string UserName ...
- [ASP.NET MVC 大牛之路]02 - C#高级知识点概要(1) - 委托和事件
在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知识点.照此,ASP.NET MVC 大牛之路系列也先给大家普及一下C#.NET中的高级知识点.每个知识点不太会过于详细,但 ...
- [ASP.NET 大牛之路]02 - C#高级知识点概要(1) - 委托和事件
在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知识点.照此,ASP.NET MVC 大牛之路系列也先给大家普及一下C#.NET中的高级知识点.每个知识点不太会过于详细,但 ...
- C# 9.0新特性详解系列之二:扩展方法GetEnumerator支持foreach循环
1.介绍 我们知道,我们要使一个类型支持foreach循环,就需要这个类型满足下面条件之一: 该类型实例如果实现了下列接口中的其中之一: System.Collections.IEnumerable ...
- 《Entity Framework 6 Recipes》中文翻译系列 (26) ------ 第五章 加载实体和导航属性之延缓加载关联实体和在别的LINQ查询操作中使用Include()方法
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-7 在别的LINQ查询操作中使用Include()方法 问题 你有一个LINQ ...
- C#高级知识点概要(2) - 线程并发锁
本文目录: 线程的简单使用 并发和异步的区别 并发控制 - 锁 线程的信号机制 线程池中的线程 案例:支持并发的异步日志组件 线程的简单使用 常见的并发和异步大多是基于线程来实现的,所以本文先讲线程的 ...
- C#高级知识点概要(1) - 委托和事件
本文目录: 委托 委托的简单使用 用委托实现插件式编程 多播委托 静态方法和实例方法对于委托的区别 泛型委托 Func 和 Action 委托 委托的兼容 事件 事件的基本使用 事件的标准模式 委托 ...
随机推荐
- jsp之间url传值出现中文乱码
示例: T1.jsp http://localhost:8080/test/Test.action?site=北京 T2.jsp ..... <%@ page language="ja ...
- 在linux下用tomcat部署java web项目的过程与注意事项(转)
一.安装JDK到http://www.oracle.com/technetwork/java/javase/downloads/index.html下载Java SE 6 Update 27根据操作系 ...
- C#的tooltip自动消失之后就不再显示了解决办法
转:http://www.51testing.com/?uid-569418-action-viewspace-itemid-832231 在控件上加上tooltip之后,鼠标放到控件上,toolti ...
- 关于@font-face的一些问题
@font-face 能够加载服务器端的字体文件,让客户端显示客户端所没有安装的字体.[微软的IE 5已经是开始支持这个属性,但是只支持微软自有的.eot (Embedded Open Type) 格 ...
- php入门自学小展示
<!doctype html> <html> <head> <title>PHP函数小展示</title> </head> &l ...
- 清除IE下链接虚线框
方法一:利用javascript的onfocus事件,实现如下: Html代码 <a href="http://www.www.baidu.com" onfocus=&qu ...
- 自己动手开发编译器(五)miniSharp语言的词法分析器
稍微说明一点,整型常量和上面的标识符的词法,在调用lex.DefineToken时都多传了一个参数.这个参数是可选的描述信息,如果不传会直接使用正则表达式的字符串形式.而标识符的正则表达式有4万多个字 ...
- linux常用命令(5)rmdir命令
rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的.(注意,rm - r dir命令可代替rmdir,但是有很大危险性.)删除某目录时也必须具有对父目录的写权限.1.命令格 ...
- ural 1020 Rope
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> # ...
- Protel 的自动推挤功能
大家都在用PROTEL99SE...有时候要修改布好的线...一条条的去拆,这样很麻烦.其实PROTEL99SE是有推挤功能的...虽然不是很强...但是可以给大家带来方便.下面我就发个自己制作的教程 ...