上一篇
第七章 实现select、selectmany、where、first、cast、oftype、tolist和 toarray
在开始本章之前,我们新建一个控制台程序,并且删除using System.Linq;
因为我们实现的方法和其中的类似,去掉可以避免干扰。
前面六章我们学习了很多C#的语法和概念。也许你觉得学的太多难以消化,那么本章和第八章就是给你一个练习和复习的机会,这两章没有新的知识点,完全是运用之前学的东西的练习。
我们的第一个题目是编写一个叫select的函数,它的作用是对输入的集合的每个元素做一个操作,得到一个新的元素,并且输出一个集合,这个集合由转换后的元素构成。是不是还不太明白?没关系,我们从最简单的开始:给定一个字符串数组data,输出一个整数数组result,整数数组的每个下标的元素是这个字符串数组的字符串的字面值。
代码框架如下,你填空就好了:
static void Main(string[] args)
{
string[] data = { "1", "3", "2", "6", "4" };
var result = Select(data);
foreach (int i in result)
Console.WriteLine(i);
}
static int[] Select(string[] data)
{
在此填写代码
}
参考程序:
static int[] Select(string[] data)
{
int[] result = new int[data.GetLength(0)];
for (int i = 0; i < result.GetLength(0); i++)
result[i] = int.Parse(data[i]);
return result;
}
还记得yield return和foreach么?我们将参数修改为IEnumerable<string>,返回值修改为 IEnumerable<int>,改写这个程序:
static IEnumerable<int> Select(IEnumerable<string> data)
{
foreach (string s in data)
yield return int.Parse(s);
}
因为int[]和string[]分别实现了IEnumerable<int>和IEnumerable<string>,所以调用程序并不要修改。
下面,我们复习下委托的用法,我们将字符串转换为数字的过程提取成一个委托,让调用者去实现,请完成如下代码:
static void Main(string[] args)
{
string[] data = { "1", "3", "2", "6", "4" };
var result = Select(data, delegate(string s) { return int.Parse(s); });
foreach (int i in result)
Console.WriteLine(i);
}
static IEnumerable<int> Select(IEnumerable<string> data, Func<string, int> selector)
{
在这里填写适当的代码
}
是不是很简单:
static IEnumerable<int> Select(IEnumerable<string> data, Func<string, int> selector)
{
foreach (string s in data)
yield return selector(s);
}
现在我们把转化过程都提取出来了,那么我们可以让这个函数更通用一点,我们将string和int用泛型参数代替,这样可以在任意两个类型之间转换了:
static IEnumerable<TResult> Select<TInput, TResult>(IEnumerable<TInput> data, Func<TInput, TResult> selector)
{
foreach (TInput s in data)
yield return selector(s);
}
还记得静态类和扩展方法么?我们把它们提取到单独的静态类中,并且用扩展方法简化调用吧,思考下怎么改写。静态类的名字可以随意起,不过我们叫它MyLinq吧。
static class MyLinq
{
public static IEnumerable<TResult> Select<TInput, TResult>(this IEnumerable<TInput> data, Func<TInput, TResult> selector)
{
foreach (TInput s in data)
yield return selector(s);
}
}
我们如何调用它呢?
是不是
string[] data = { "1", "3", "2", "6", "4" };
var result = data.Select(delegate(string s) { return int.Parse(s); });
foreach (int i in result)
Console.WriteLine(i);
对了,我们还可以用Lambda表达式:
string[] data = { "1", "3", "2", "6", "4" };
var result = data.Select(s => int.Parse(s));
foreach (int i in result)
Console.WriteLine(i);
当然了,作为Lambda表达式的参数命名可以随便起。考虑到它的类型是字符串,我们叫s,但是写得顺手了,直接叫x也可以。
var result = data.Select(x => int.Parse(x));
不知不觉,我们写出了一个非常通用的Select方法,为什么通用呢?首先,只要是实现了IEnumerable<T>接口的类型,无论是数组、List、Dictionary……统统可以传入,其次,输入参数的类型和输出参数的类型,可以是任意的,你可以把任意的东西转换为任意的东西,这还不通用?最后,转换的方法由调用者决定,以selector这个参数传入,你可以充分发挥想象力和创造力,用你的方法去转换,我们看看这个方法还可以怎么用:
输入是如下的数据
1,张三,52
2,李四,37
3,王五,47
我们希望将每一行转换为一个Person对象:
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
我们可以这么写:
static void Main(string[] args)
{
string s = @"1,张三,52
2,李四,37
3,王五,47";
string[] data = s.Split(new string[] { "\r\n" }, StringSplitOptions.None);
var people = data.Select(x => new Person()
{
ID = int.Parse(x.Split(',')[0]),
Name = x.Split(',')[1],
Age = int.Parse(x.Split(',')[2])
});
foreach (Person p in people)
{
Console.WriteLine(p.Name);
}
}
我们也可以将集合对象中元素的某个属性提取出来:
var processNames = System.Diagnostics.Process.GetProcesses().Select(x => x.ProcessName);
foreach (string item in processNames)
{
Console.WriteLine(item);
}
processNames是一个字符串序列,它由Process[]转换而来,只包含了进程名。
我们也可以结合匿名方法选取几个属性:
var processNames = System.Diagnostics.Process.GetProcesses()
.Select(x => new { Name = x.ProcessName, ID = x.Id });
foreach (var item in processNames)
{
Console.WriteLine("{0}: {1}", item.ID, item.Name);
}
下一篇