C# 匿名函数
目录
- 1、什么是匿名函数?
- 1.1 一般函数的定义
- 1.2 匿名函数的定义
- 2、理解匿名函数的例子
- 2.1 带输入参数的匿名函数
- 2.2 带返回值的匿名函数
- 2.3 匿名函数可以使用函数外部定义的变量
- 2.4 把匿名函数作为其他函数的输入参数
- 2.5 匿名函数作事件处理器
- 3、为什么要使用匿名函数?
- 3.1 不使用匿名函数
- 3.2 使用匿名函数
- 4、匿名函数的局限性
- 4.1 匿名函数中不能包含跳转语句
- 4.2 匿名函数不能使用外部函数的ref或out参数
1、什么是匿名函数?
匿名函数(anonymous method),顾名思义,就是没有名字的函数。或者没有名字的代码块。在C#中,我们使用delegate
关键字来定义匿名函数,并且匿名函数可以赋值给委托类型的变量。委托参见C# 委托。
匿名函数的形式。
delegate(输入参数){函数体};
1.1 一般函数的定义
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Action action = WriteBlog;
action.Invoke();
Console.ReadKey();
}
static void WriteBlog()
{
Console.WriteLine("I'm writing a blog.");
}
}
}
一般函数有函数名,在这个例子中函数名为WriteBlog
。我们将有名字的函数绑定到委托类型的变量上。
1.2 匿名函数的定义
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Action action = delegate() { Console.WriteLine("I'm writing a blog."); };
action.Invoke();
Console.ReadKey();
}
}
}
匿名函数没有函数名。我们将没有名字的函数(匿名函数)绑定到委托类型的变量上。
通过这个例子的对比,我们可以感性地看出,匿名函数使代码量减少了。
2、理解匿名函数的例子
2.1 带输入参数的匿名函数
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Action<string> action = delegate(string name) { Console.WriteLine("{0} is writing a blog.", name); };
action.Invoke("Mike");
Console.ReadKey();
}
}
}
2.2 带返回值的匿名函数
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Func<double,double,double> func1 = delegate(double a,double b) { return a + b; };
double result = func1.Invoke(100,200);
Console.WriteLine(result);
Console.ReadKey();
}
}
}
2.3 匿名函数可以使用函数外部定义的变量
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string name = "Mike";
Action action = delegate () { Console.WriteLine("{0} is writing a bolg.",name); };
action.Invoke();
Console.ReadKey();
}
}
}
当然,匿名函数也可以调用外部函数。
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Action action = delegate ()
{
bool b = Test();
Console.WriteLine(b);
};
action.Invoke();
Console.ReadKey();
}
static bool Test()
{
return true;
}
}
}
2.4 把匿名函数作为其他函数的输入参数
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Predicate<int> isEven = delegate (int num) { return num % 2 == 0; };
int[] nums = { 1, 2, 3, 4, 5 };
ProcessNums(isEven, nums);
Console.ReadKey();
}
static void ProcessNums(Predicate<int> predicate,int[] nums)
{
Console.WriteLine("Processing nums...");
foreach (int num in nums)
{
bool b = predicate.Invoke(num);
if(b)
{
Console.WriteLine("{0} is even number.",num);
}
else
{
Console.WriteLine("{0} is not even number.", num);
}
}
}
}
}
/*
Outputs:
Processing nums...
1 is not even number.
2 is even number.
3 is not even number.
4 is even number.
5 is not even number.
*/
值得一提的是,Predicate类型的委托用来封装有一个输入参数并且返回值为bool类型的方法。在这个例子中,Predicate委托封装的方法输入一个int类型的数字,方法判断数字是否为偶数,然后返回判断的结果。
我们将匿名函数作为ProcessNums的输入参数,在ProcessNums函数体中对匿名函数进行调用。
2.5 匿名函数作事件处理器
事件参见C# 事件。简单回顾一下,事件模型由5部分组成:事件的拥有者、事件、事件的响应者、事件处理器、事件订阅。下面分别使用代码演示不适用匿名函数定义事件,以及使用匿名函数定义事件。
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Chicken chicken = new Chicken();
People zuDi = new People();
chicken.Crow += zuDi.Action;
chicken.StartCrow();
Console.ReadLine();
}
}
class Chicken
{
public event EventHandler Crow;
public void StartCrow()
{
Console.WriteLine("koke-kokko");
OnCrow(EventArgs.Empty);
}
protected virtual void OnCrow(EventArgs e)
{
Crow?.Invoke(this, e);
}
}
class People
{
public void Action(object sender, EventArgs e)
{
Console.WriteLine("I get up.");
Console.WriteLine("I started practicing my sword.");
}
}
}
/*
Outputs:
koke-kokko
I get up.
I started practicing my sword.
*/
这是不使用匿名函数定义的事件,事件处理器作为People的成员方法Action。
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Chicken chicken = new Chicken();
chicken.Crow += delegate (object sender, EventArgs e)
{
Console.WriteLine("I get up.");
Console.WriteLine("I started practicing my sword.");
};
chicken.StartCrow();
Console.ReadLine();
}
}
class Chicken
{
public event EventHandler Crow;
public void StartCrow()
{
Console.WriteLine("koke-kokko");
OnCrow(EventArgs.Empty);
}
protected virtual void OnCrow(EventArgs e)
{
Crow?.Invoke(this, e);
}
}
}
/*
Outputs:
koke-kokko
I get up.
I started practicing my sword.
*/
这是使用匿名函数定义的事件,我们不需要定义一个People类,再在类里面定义Action方法,也不需要在Main函数中创建一个People类的实例,然后将实例的Action方法绑定到委托。直接一个匿名函数就将上面说的一大串操作完成了。
3、为什么要使用匿名函数?
使用匿名函数可以减少代码量,一般地,对于那种我们只使用一次的函数,且函数体的代码量比较少的情况,推荐使用匿名函数。
下面再举一个稍微复杂一点的例子,来看一看匿名函数的便利性。
例子来源。
3.1 不使用匿名函数
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Predicate<Employee> employeePredicate = new Predicate<Employee>(IsEmployeeExist);
List<Employee> listEmployees = new List<Employee>()
{
new Employee{ ID = 101, Name = "Pranaya", Gender = "Male", Salary = 100000},
new Employee{ ID = 102, Name = "Priyanka", Gender = "Female", Salary = 200000},
new Employee{ ID = 103, Name = "Anurag", Gender = "Male", Salary = 300000},
new Employee{ ID = 104, Name = "Preety", Gender = "Female", Salary = 400000},
new Employee{ ID = 104, Name = "Sambit", Gender = "Male", Salary = 500000},
};
Employee employee = listEmployees.Find(x => employeePredicate(x));
Console.WriteLine(@"ID : {0}, Name : {1}, Gender : {2}, Salary : {3}",
employee.ID, employee.Name, employee.Gender, employee.Salary);
Console.ReadKey();
}
public static bool IsEmployeeExist(Employee emp)
{
return emp.ID == 103;
}
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public double Salary { get; set; }
}
}
/*
Outputs:
ID : 103, Name : Anurag, Gender : Male, Salary : 300000
*/
上面的代码可以分为以下几步:
- 1.首先创建一个Employee类,类中有4个成员属性,用来表示雇员的状态。
- 2.创建一个函数IsEmployeeExist,函数的输入参数为Employee类型的参数,函数的返回值为bool。函数的功能是,根据Employee的ID值判断Employee是否存在。
- 3.
Predicate<Employee> employeePredicate = new Predicate<Employee>(IsEmployeeExist);
创建一个Predicate委托类型的实例,Predicate委托相匹配的函数签名为:函数的输入参数类型为Employee,函数的返回值类型为bool。因此IsEmployeeExist函数签名和委托签名相匹配。将IsEmployeeExist函数名作为参数传递给Predicate的构造函数。这样我们就将函数绑定到委托了,可以通过调用委托实例来间接调用函数。 - 4.创建一个列表,列表中存储的数据类型为Employee,列表中一共存储了5个Employee类型的实例,也就是说,列表中存储了5个雇员的信息。因此我们可以将这个列表看成是雇员信息系统。
- 5.
Employee employee = listEmployees.Find(x => employeePredicate(x));
调用listEmployees的Find方法,该方法接收一个Predicate类型的实例。这行代码实现的功能是,我们将listEmployees中的每一个雇员实例取出来,然后将每一个雇员作为参数传递给employeePredicate委托,也就是对于每一个雇员,委托类型的实例employeePredicate都会被调用,在内部,将调用IsEmployeeExist函数,函数接收listEmployees中的每一个雇员实例,判断雇员的ID是否等于103,如果等于,就将其存储到employee变量中,如果不等于,不存储。
3.2 使用匿名函数
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
List<Employee> listEmployees = new List<Employee>()
{
new Employee{ ID = 101, Name = "Pranaya", Gender = "Male", Salary = 100000},
new Employee{ ID = 102, Name = "Priyanka", Gender = "Female", Salary = 200000},
new Employee{ ID = 103, Name = "Anurag", Gender = "Male", Salary = 300000},
new Employee{ ID = 104, Name = "Preety", Gender = "Female", Salary = 400000},
new Employee{ ID = 105, Name = "Sambit", Gender = "Male", Salary = 500000},
};
Employee employee = listEmployees.Find(delegate (Employee x) { return x.ID == 103; });
Console.WriteLine(@"ID : {0}, Name : {1}, Gender : {2}, Salary : {3}",
employee.ID, employee.Name, employee.Gender, employee.Salary);
Console.ReadKey();
}
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public double Salary { get; set; }
}
}
/*
Outputs:
ID : 103, Name : Anurag, Gender : Male, Salary : 300000
*/
通过使用匿名函数,我们可以省略掉2、3步。
这样,我们就可以很明显地看出来匿名函数的优势了。
4、匿名函数的局限性
4.1 匿名函数中不能包含跳转语句
匿名函数中不能包含跳转语句,如goto、break、continue。
4.2 匿名函数不能使用外部函数的ref或out参数
我们可以使用局部变量来解决这种问题。