本篇博文主要对asp.net mvc开发需要撑握的C#语言知识点进行简单回顾,尤其是C# 3.0才有的一些C#语言特性。对于正在学asp.net mvc的童鞋,不防花个几分钟浏览一下。本文要回顾的C#知识点有:特性、自动属性、对象集合初始化器、扩展方法、Lambda表达式和Linq查询。C#资深“玩家”可路过。

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被调用之后才真正执行的。

Linq非常强大也非常好用,这里只是把它当作一个学习ASP.NET MVC之前需掌握的知识点进行简单回顾。要灵活熟练地使用Linq还需要经常使用才行。

C#知识点提要的更多相关文章

  1. 学习《Python核心编程》做一下知识点提要,方便复习(一)

    学习<Python核心编程>做一下知识点提要,方便复习. 计算机语言的本质是什么? a-z.A-Z.符号.数字等等组合成符合语法的字符串.供编译器.解释器翻译. 字母组合后产生各种变化拿p ...

  2. ASP.NET MVC C#知识点提要

    ASP.NET MVC C#知识点提要 本篇博文主要对asp.net mvc开发需要撑握的C#语言知识点进行简单回顾,尤其是C# 3.0才有的一些C#语言特性.对于正在学asp.net mvc的童鞋, ...

  3. [ASP.NET MVC 小牛之路]02 - C#知识点提要

    本人博客已转移至:http://www.exblr.com/liam  本篇博文主要对asp.net mvc开发需要撑握的C#语言知识点进行简单回顾,尤其是C# 3.0才有的一些C#语言特性.对于正在 ...

  4. .net与数据库知识点

    <%服务器方法;%> (调用服务器方法,要写;) <=%服务器方法%> (有返回值输出,不能写;) public ActionResult Index(int id = 0) ...

  5. 自学MVC看这里——全网最全ASP.NET MVC 教程汇总

    MVC架构已深得人心,微软也不甘落后,推出了Asp.net MVC.小编特意整理博客园乃至整个网络最具价值的MVC技术原创文章,为想要学习ASP.NET MVC技术的学习者提供一个整合学习入口.本文从 ...

  6. [ASP.NET MVC 小牛之路]03 - Razor语法

    本人博客已转移至:http://www.exblr.com/liam  Razor是MVC3中才有的新的视图引擎.我们知道,在ASP.NET中,ASPX的视图引擎依靠<%和%>来调用C#指 ...

  7. [ASP.NET MVC 小牛之路]13 - Helper Method

    我们平时编程写一些辅助类的时候习惯用“XxxHelper”来命名.同样,在 MVC 中用于生成 Html 元素的辅助类是 System.Web.Mvc 命名空间下的 HtmlHelper,习惯上我们把 ...

  8. [ASP.NET MVC 小牛之路]15 - Model Binding

    Model Binding(模型绑定)是 MVC 框架根据 HTTP 请求数据创建 .NET 对象的一个过程.我们之前所有示例中传递给 Action 方法参数的对象都是在 Model Binding ...

  9. ASP.NET MVC Razor

    Razor是MVC3中才有的新的视图引擎.我们知道,在ASP.NET中,ASPX的视图引擎依靠<%和%>来调用C#指令.而MVC3以后有了一套新的使用@标记的Razor语法,使用起来更灵活 ...

随机推荐

  1. PHP 去掉emoji字符

    function isMatchEmoji($str) { $pattern='/./u'; $rs=preg_match_all($pattern,$str,$match); if($rs>0 ...

  2. WinForm中ListBox的使用

    获取选中数据:listbox.SelectedItem as XXX 重绘每一行item DrawMode设置为DrawMode.OwnerDrawVariable 然后实现DrawItem(obje ...

  3. Android学习笔记AutoCompleteTextView的使用

    activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&qu ...

  4. const限定符、constexpr和常量表达式------c++ primer

    编译器将在编译过程中把用到const变量的地方都替换成对应的值,为了执行这种替换,编译器必须知道变量的初始值.如果程序包含多个文件,则那个用了const对象的文件都必须能访问到它的初始值才行.要做到这 ...

  5. 数据库抽象层 pdo

    一 . PDO的连接 $host = "localhost"; $dbname = "hejuntest"; $username = "root&qu ...

  6. C# 抽象

    好多人将抽象类也作为多态的一种,其实我觉得并不是特别的好. 抽象在C#中是类的一种表现. 如果将类作为多态,那么前面所有的东西不就白费了吗? C#的 抽象很简单. 那就是抽象. 基本就是高度抽象. 那 ...

  7. 757. Set Intersection Size At Least Two

    An integer interval [a, b] (for integers a < b) is a set of all consecutive integers from a to b, ...

  8. Django 错误:TypeError at / 'bool' object is not callable

    使用 Django自带的 auth 用户验证功能,编写函数,使用 is_authenticated 检查用户是否登录,结果报错: TypeError at / 'bool' object is not ...

  9. 【FAQ】服务下线

    原因:磁盘已满

  10. jq学习笔记(一)

    1 .attr() 与 .removeAttr()方法 - atr()方法用来获取和设置元素属性 attr()有4个表达式: attr(传入属性名):获取属性的值 attr(属性名, 属性值):设置属 ...