Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyCsStudy
{
/// <summary>
/// 图书类
/// </summary>
public class Book
{
public int ID { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public DateTime PublishDate { get; set; }
}
/// <summary>
/// 书店
/// </summary>
public class BookStore
{
public List<Book> Books { get; set; }
}
public class Program
{
static void Main(string[] args)
{
BookStore bs = new BookStore();
bs.Books = new List<Book>
{
new Book{ID = 1, Name="Asp.net", Price=77, PublishDate=DateTime.Parse("2009-04-30")},
new Book{ID = 2, Name="Sql Server", Price=99, PublishDate=DateTime.Parse("2008-12-30")},
new Book{ID =3, Name="JavaScript", Price=66, PublishDate=DateTime.Parse("2006-1-12")}
};
bs.Books.Sort(delegate(Book ba, Book bb) { return ba.Price.CompareTo(bb.Price); }); //匿名方法(这个我真不喜欢)
Console.WriteLine("books after sort:");
//显示排序后的图书排序
foreach (var item in bs.Books)
{
Console.WriteLine(item.Name);
}
Console.ReadLine();
}
}
}
下面的代码可以轻松实现相同的功能:
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyCsStudy
{
/// <summary>
/// 图书类
/// </summary>
public class Book
{
public int ID { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public DateTime PublishDate { get; set; }
}
/// <summary>
/// 书店
/// </summary>
public class BookStore
{
public List<Book> Books { get; set; }
}
public class BookComparasion : IComparer<Book>
{
public int Compare(Book ba, Book bb)
{
return ba.Price.CompareTo(bb.Price);
}
}
public class Program
{
static void Main(string[] args)
{
BookStore bs = new BookStore();
bs.Books = new List<Book>
{
new Book{ID = 1, Name="Asp.net", Price=77, PublishDate=DateTime.Parse("2009-04-30")},
new Book{ID = 2, Name="Sql Server", Price=99, PublishDate=DateTime.Parse("2008-12-30")},
new Book{ID =3, Name="JavaScript", Price=66, PublishDate=DateTime.Parse("2006-1-12")}
};
//bs.Books.Sort(new BookComparasion().Compare); //这个最常用
//bs.Books.Sort(delegate(Book ba, Book bb) { return ba.Price.CompareTo(bb.Price); }); //匿名方法(这个我真不喜欢)
bs.Books.Sort((a, b) => { return a.Price.CompareTo(b.Price); }); //Lamdba表达式(主流,学习ing)
//bs.Books.Sort((x, y) => { return x.PublishDate.CompareTo(y.PublishDate); }); //按出版日期排序
Console.WriteLine("books after sort:");
//显示排序后的图书排序
foreach (var item in bs.Books)
{
Console.WriteLine(item.Name);
}
Console.ReadLine();
}
}
}
上面的代码注释已经写的很清楚,没错,现在向“主流”靠拢,就是Lambda表达式了。
二、Lambda表达式学习笔记
在C#2.0中引入了匿名方法允许在期望出现委托的时候以“内联”的代码替代之。尽管匿名方法提供了函数式编程语言中的很多表达能力,但匿名方法的语法实在是太“不近人情”了,并且很不自然,用多了会造成阅读上的困难。举例来说:
Code
class Test
{
public static void Main()
{
List<int> intList = new List<int>();
var numArr = new int[] { 23, 41, 15, 6, 89, 12, 123, 54 };
intList.AddRange(numArr);
intList.Sort(delegate(int a, int b) //匿名方法
{
return a.CompareTo(b);
});
foreach (var item in intList)
{
Console.WriteLine(item);
}
Console.Read();
}
上面的匿名委托我们可以简化为Lambda表达式来表示。
实际上Lambda表达式的本质是匿名方法,也即是当编译我们的程序代码时,编译器会自动帮我们将Lambda表达式转换为匿名方法。看下改进后的代码:
下面系统学习Lambda表达式的相关语法和应用:
1、
创建Lambda表达式的书写方式是一个参数列表后跟“=>”记号,然后跟一个表达式或一个语句块,即Lambda表达式的语法格式为:参数列 => 语句或语句块。
(param1, param2, …paramN) =>
{
statement1;
statement2;
…
statementN;
return(lambda_expression_return_type);
}
2、关于“
参数列”
Lambda表达式的参数列可以具有显式的或隐式的类型。在一个具有显式类型的参数列表中,每个参数的类型都是显式声明的。在一个具有隐式类型的参数列表中,参数的类型是从Lambda表达式出现的上下文中推断出来的——具体来说,是当Lambda表达式被转换为一个兼容的委托类型时,该委托类型提供了参数的类型。
当Lambda表达式只有一个具有隐式类型的参数时,参数列表中的括号可以省略。即:(param) => expression可以简写为:param => expression。
注意,参数列中可包含任意个参数(与委托对应),如果参数列中有0个或1个以上参数,则必须使用括号括住参数列,举例如下:
() => Console.Write("0个参数");
i => Console.Write("1个参数时参数列中可省略括号,值为:{0}", i);
(x, y) => Console.Write("包含2个参数,值为:{0}:{1}", x, y);
而“语句或语句块”中如果只有一条语句,则可以不用大括号括住,否则则必须使用大括号,如下所示:
//只有一条语句,则可以不用大括号括住
i => Console.Write("只有一条语句");
i => { Console.Write("使用大括号的表达式"); };
//两条语句时必须要大括号
i => { i++; Console.Write("两条语句的情况"); };
如果“语句或语句块”有返回值时,如果只有一条语句则可以不写“return”语句,编译器会自动处理,否则必须加上,如下示例:
Code
class MyCsTest
{
delegate int AddOperation(int x, int y);
static void Print(AddOperation add)
{
Console.Write(add(1, 3));
}
static void Main()
{
Print((x, y) => x + y); //可以不写return
Print((x, y) => { int i = x * 10; return y + i; });
Console.Read();
}
}
3、
遵循规则
Lambda表达式是委托的实现方法,所以必须遵循以下规则:
(1)Lambda表达式的参数数量必须和委托的参数数量相同;
(2)如果委托的参数中包括有ref或out修饰符,则Lambda表达式的参数列中也必须包括有修饰符;
(3)如果委托有返回类型,则Lambda表达式的语句或语句块中也必须返回相同类型的数据;
(4)如果委托有几种数据类型格式而在Lambda表达式中编译器无法推断具体数据类型时,则必须手动明确数据类型。
示例如下:
Code
class MyCsTest
{
delegate void OutHandler(out int x);
static void Print(OutHandler test)
{
int i;
test(out i);
Console.Write(i);
}
static void Main()
{
Print((out int x) => x = 3); //out参数
Console.Read();
}
}
小结: C# 2.0规范中提到的匿名方法规范同样适用于Lambda表达式。Lambda表达式是匿名方法在功能行上的超集,提供了下列附加的功能:
(1)Lambda表达式允许省略参数类型并对其进行推断,而匿名方法要求参数类型必须显式地声明。
(2)Lambda表达式体可以是表达式或语句块,而匿名方法体只能是语句块。
(3)在类型参数推导和方法重载抉择时,Lambda表达式可以被作为参数传递。
(4)以一个表达式作为表达式体的Lambda表达式可以被转换为表达式树。
4、Lambda表达式的转换
两个代表:下面行文中的D代表Delegate(委托类型),L代表Lambda表达式
和匿名方法表达式类似,Lambda表达式可以归类为一种拥有特定转换规则的值。这种值没有类型,但可以被隐式地转换为一个兼容的委托类型。
特别地,当满足下列条件时,委托类型兼容于Lambda表达式:
(1) D和L具有相同数量的参数;
(2) 如果L具有显式类型的参数列表,D中每个参数的类型和修饰符必须和L中相应的参数完全一致;
(3) 如果L具有隐式类型的参数列表,则D中不能有ref或out参数;
(4) 如果D具有void返回值类型,并且L的表达式体是一个表达式,若L的每个参数的类型与D的参数一致,则L的表达式体必须是一个可接受为statement-expression的有效表达式;
(5) 如果D具有void返回值类型,并且L的表达式体是一个语句块,若L的每个参数的类型与D的参数一致,则L的表达式体必须是一个有效语句块,并且该语句块中不能有带有表达式的return语句;
(6) 如果D的返回值类型不是void,并且L的表达式体是一个表达式,若L的每个参数的类型与D的参数一致,则L的表达式体必须是一个可以隐式转换为D的返回值类型的有效表达式;
(7) 如果D的返回值类型不是void,并且L的表达式体是一个语句块,若L的每个参数的类型与D的参数一致,则L的表达式体必须是一个有效的语句块,该语句块不能有可达的终点
(即必须有return语句),并且每个return语句中的表达式都必须能够隐式转换为D的返回值类型。
(ps:ms这里的约束也太多了吧^_^。哎,不管了,先抄下来,以后会熟练运用就行了)
三、一个需要注意的地方
看下面的代码:
Code
using System;
using System.Collections.Generic;
using System.Linq;
namespace MyCsStudy
{
class Program
{
static void Main(string[] args)
{
List<Func<int>> list = new List<Func<int>>();
for (int i = 0; i < 3; i++)
{
list.Add(() => i); //list依次添加0,1,2??
}
foreach (var item in list)
{
Console.WriteLine(item()); //输出的竟然是?
}
}
}
}
运行试一下看看结果,如果你以前碰到过js里的闭包问题,相信你不会大惊小怪(而且可能已经知道了问题的原因),但是,如果你从来没有碰到过这种情况,是不是令你大吃一惊?!输出的竟然不是0,1,2,而是三个3,oh,my god。紧接着,立刻,你会大胆想到这里的list在Add方法执行的地方Add进去的是一个引用类型(这里是lambda表达式()=>i),它们执行的结果共同指向值为3的同一个引用地址!
没错,我们详细分析一下:
1、我们首先定义一个list,其存储格式为func<int>,即返回int型的代理;然后,用for循环将i封装进lambda表达式,并加入到该list中,最后,用foreach循环输出结果。
2、因为lambda表达式实质就是个委托,也就指向一个匿名函数,所以,在foreach输出的时候,使用item()来调用它,让它所指向的函数执行。
至于第2步中item()执行的结果为什么都是3,原因是这样的:
(1)在for循环中,只能有一个 i 变量。即在第一次循环时,i 的地址就分配好了(注意了,这里i的地址第一次分配后是不变的),不会因为循环次数的多少而发生任何改变,其改变的只能是里面装载的值。
(2)lambda表达式在构造时, 传进去的是变量的地址,而不是具体值。只有当真正执行这个lambda表达式时,才会去确定它的值。这就是为什么上面的例子中,其结果均为3(for循环在最后,当i=2时,i又加了1)。
那么如何解决这个问题?解决方案很简单,就是在for循环中,定义一临时变量tmpNum,存储i的值即可。因为编译器会对该临时变量重新分配内存,这样,每次循环,都重新分配新的内存,就不会有这个问题了。
最后把这个“功德圆满”的解决方案贴出来:
Code
using System;
using System.Collections.Generic;
using System.Linq;
namespace MyCsStudy
{
class Program
{
static void Main(string[] args)
{
List<Func<int>> list = new List<Func<int>>();
for (int i = 0; i < 3; i++)
{
int tmpNum = i; //临时变量
list.Add(() => tmpNum);
}
foreach (var item in list)
{
Console.WriteLine(item());
}
}
}
}
好了,Lambda表达式先学到这里,多读多写熟练就不害怕了,继续学习去也。