代码改变世界

Linq学习之路(03) - 什么使Linq变得如此简单(二)?

2013-05-12 16:30  ARMdong  阅读(1265)  评论(1编辑  收藏  举报

  我们接着上篇Linq系列文章继续谈Linq的基石,上篇文章中,我们谈到了隐式类型局部变量以及对象集合初始化器,今天我们说说Lambda表达式、扩展方法和匿名类型。按计划,这篇文章昨天就应该写出来了,可是昨天确实很累,给自己定的目标是每天都写一篇技术文章,来总结每天学习的成果。又给自己的懒惰找借口了。。。

  好吧,言归正传。对了,昨天马云好像辞去了阿里巴巴CEO一职,哎,羡慕啊,向他学习。希望能从他身上学点东西……

  呵呵,又扯了,这次真的进入到主题。我们接着上篇文章中用到的实例,我们继续给这个实例进行进一步的优化。现在,我想给DisplayProcess方法添加一个过滤条件,输出占用内存空间大于20M的进程,首先我利用“硬编码”来实现过滤,其他代码不变主要是在DisplayProcess方法中添加一个if判断,好的,看代码:

DisplayProcess
        /// <summary>
        /// 给方法加上过滤条件,输出占用内存超过20M的进程
        /// </summary>
        static void DisplayProcess()
        {
            var processes = new List<ProcessData>();

            foreach (var process in Process.GetProcesses())
            {
                //硬编码,添加过滤条件
                if (process.WorkingSet64 >= 20 * 1024 * 1024)
                {
                    processes.Add(new ProcessData
                    {
                        Id = process.Id,
                        Name = process.ProcessName,
                        Memory = process.WorkingSet64
                    });
                }
            }    

  show结果:

上图列出的进程Memory大小都是计算机中分配的物理内存大于20M的进程。可是硬编码有局限性,假如我现在改变过滤条件,我希望输出占用内存大小超过30M的进程,那么我们又要到源代码中修改过滤条件,这样一来肯定很麻烦,所以我们想到一个解决办法,就是添加一个过滤器来替代硬编码,让我们写的程序更加的通用。

熟悉委托:

  这里,我们的过滤器使用的是委托中的Predicate<T>委托,我们通常叫做“断言”,这个委托是通过我们输入的类型来返回true或false,我们可以根据它来实现过滤,我这样说大家可能不好理解,那我直接写代码,大家看完代码就能明白我说的是什么意思了。

  修改DisplayProcess方法:把过滤器作为参数传进去

DisplayProcess
        /// <summary>
        /// 将过滤条件作为参数传进去
        /// </summary>
        /// <param name="match">过滤条件</param>
        static void DisplayProcess(Predicate<Process> match)
        {
            var processes = new List<ProcessData>();
            foreach (var process in Process.GetProcesses())
            {
                if (match(process))
                {
                    processes.Add(new ProcessData
                    {
                        Id = process.Id,
                        Name = process.ProcessName,
                        Memory = process.WorkingSet64
                    });
                }
            }

            //遍历processes,输出到控制台
            foreach (var data in processes)
            {
                Console.Write("Id = {0}\tName = {1}\tMemory = {2}", data.Id, data.Name, data.Memory);
                Console.WriteLine();
            }
        }

增加过滤器方法:Filter

Filter
        /// <summary>
        /// 过滤器
        /// </summary>
        /// <param name="process">参数</param>
        /// <returns>返回值,true或false</returns>
        static Boolean Filter(Process process)
        {
            return process.WorkingSet64 >= 20 * 1024 * 1024;
        }

  在Mian函数中调用DisplayProcess方法时将Filter传进去:

Main
        static void Main(string[] args)
        {
            DisplayProcess(Filter);
            Console.ReadKey();
        }

好的,我们看运行结果:

  哈哈,同样实现了过滤,这样做的优势是,当我们想要改变过滤条件的时候,我们只要改变过滤器中的条件即可,而不用去动DisplayProcess,我们可以将条件写在App.config或Web.config配置文件中,这样修改起来不用重新编译源代码了。(我这里为了省事,没有将条件写在配置文件中。。。)

  当然,我们也可以不写Filter方法,直接用匿名方法来代替,在Main函数中调用的时候,我们可以这样写:

        static void Main(string[] args)
        {
            //DisplayProcess(Filter);

            DisplayProcess(delegate(Process process)
            {
                return process.WorkingSet64 >= 20 * 1024 * 1024;
            });
            Console.ReadKey();
        }

  效果跟上面是一样的。

  写了这里,大家是不是感觉到不管是Filter方法还是在Main函数中使用匿名方法,都比较麻烦;下面就进入到我们的主题:Lambda

 

重点一:Lambda表达式

  我们只要在Main函数中调用Display方法的时候传入一个Lambda表达式即可,先给大家看一看:

        static void Main(string[] args)
        {
            //DisplayProcess(Filter);

            #region 匿名方法
            //DisplayProcess(delegate(Process process)
            //    {
            //        return process.WorkingSet64 >= 20 * 1024 * 1024;
            //    }); 
            #endregion

            //Lambda表达式
            DisplayProcess(process => process.WorkingSet64 >= 20 * 1024 * 1024);

            Console.ReadKey();
        }

  哈哈,是不是很爽,同样能达到效果。这就是我们今天的第一个重点,Lambda表达式。有关于Lamda表达式的详细内容这里我就不再详细介绍了,大家可以去MSDN上学习,MSDN真的是学习的一个很好的资源,希望大家能够好好利用,另外在上文中我介绍到了委托,有关于委托的原理前段时间我写了一篇文章,大家可以去看看,我把链接帖上来。http://www.cnblogs.com/ARMdong/archive/2013/05/01/3053678.html,好,我们进入今天的第二个重点:扩展方法。

 

重点二:扩展方法

  在开始扩展方法之前,我们引进一个小插曲,也就是在上面的实例中,我们想假如一个统计所有进程占用的内存总大小的方法。首先我们想到的就是添加一个统计的方法不就得了,好的,我根据大家的思维来写这样的一个方法:

TotalMemory
        /// <summary>
        /// 统计所有进程占用内存大小的方法
        /// </summary>
        /// <param name="processes">所有的进程集合</param>
        /// <returns>返回值,消耗内存空间</returns>
        static Int64 TotalMemory(IEnumerable<ProcessData> processes)
        {
            Int64 result = 0;

            foreach (var process in processes)
            {
                result += process.Memory;
            }

            return result;
        }

  在DisplayProcess中调用TotalMemory方法:

DisplayProcess
        /// <summary>
        /// 将过滤条件作为参数传进去
        /// </summary>
        /// <param name="match">过滤条件</param>
        static void DisplayProcess(Predicate<Process> match)
        {
            var processes = new List<ProcessData>();
            foreach (var process in Process.GetProcesses())
            {
                if (match(process))
                {
                    processes.Add(new ProcessData
                    {
                        Id = process.Id,
                        Name = process.ProcessName,
                        Memory = process.WorkingSet64
                    });
                }
            }

            //遍历processes,输出到控制台
            foreach (var data in processes)
            {
                Console.Write("Id = {0}\tName = {1}\tMemory = {2}", data.Id, data.Name, data.Memory);
                Console.WriteLine();
            }
            Console.WriteLine();

            //统计占用的内存空间
            Console.WriteLine("消耗的内存总大小为:{0}M", TotalMemory(processes) / (1024 * 1024));
        }

  Main函数中列出所有进程,不添加过滤条件:

Main
        static void Main(string[] args)
        {
            //DisplayProcess(process => process.WorkingSet64 >= 20 * 1024 * 1024);

            //这里我们就不再过滤了,列出所有的进程
            DisplayProcess(process => process.WorkingSet64 > 0);

            Console.ReadKey();
        }

  我们看看运行结果:

打开任务管理器:

  我的机器物理内存大小是4G,其实真正能用的只有3.8~3.9G,所有进程消耗的内存是1989M,所占百分比为51%,差不多。

  讲到这里,大家可能在想,这跟扩展方法有毛的关系啊,是的,到目前为止,跟扩展方法还没有关系。下面我让它跟扩展方法有关系,我让他能在IEnumerable<ProcessData> processes的泛型集合processes中直接能点(.)出来,让他成为IEnumerable<ProcessData>自己的方法,像这样:processes.TotalMemory(),代码中能点是一种享受,你们觉得呢?好吧下面我开始小小的改动一下TotalMemory的方法:

TotalMemory
        /// <summary>
        /// 为IEnumerable<ProcessData>泛型添加扩展方法
        /// </summary>
        /// <param name="processes">所有的进程集合</param>
        /// <returns>返回值,消耗内存空间</returns>
        static Int64 TotalMemory(this IEnumerable<ProcessData> processes)
        {
            Int64 result = 0;

            foreach (var process in processes)
            {
                result += process.Memory;
            }

            return result;
        }

  我在IEnumerable<ProcessData> processes参数前面加上一个this关键字,然后修改DisplayProcess中对TotalMemory的调用:

            //统计占用的内存空间
            //Console.WriteLine("消耗的内存总大小为:{0}M", TotalMemory(processes) / (1024 * 1024));
            Console.WriteLine("消耗的内存总大小为:{0}M", processes.TotalMemory() / (1024 * 1024));

  好了,运行一下我们的代码,结果和上面的一样,是不是很爽,在添加扩展方法的时候,我们要注意两点:首先是this关键字必须加载方法的第一个参数前面,这个参数的类型就是我们要扩展的类型。其次,扩展方法和其所属的类必须用static修饰:我把整个静态类代码贴出来。

Static Class Program
   //必须为静态类
    static class Program
    {
        static void Main(string[] args)
        {
            //DisplayProcess(process => process.WorkingSet64 >= 20 * 1024 * 1024);

            //这里我们就不再过滤了,列出所有的进程
            DisplayProcess(process => process.WorkingSet64 > 0);

            Console.ReadKey();
        }

        /// <summary>
        /// 将过滤条件作为参数传进去
        /// </summary>
        /// <param name="match">过滤条件</param>
        static void DisplayProcess(Predicate<Process> match)
        {
            var processes = new List<ProcessData>();
            foreach (var process in Process.GetProcesses())
            {
                if (match(process))
                {
                    processes.Add(new ProcessData
                    {
                        Id = process.Id,
                        Name = process.ProcessName,
                        Memory = process.WorkingSet64
                    });
                }
            }

            //遍历processes,输出到控制台
            foreach (var data in processes)
            {
                Console.Write("Id = {0}\tName = {1}\tMemory = {2}", data.Id, data.Name, data.Memory);
                Console.WriteLine();
            }
            Console.WriteLine();

            //统计占用的内存空间
            //Console.WriteLine("消耗的内存总大小为:{0}M", TotalMemory(processes) / (1024 * 1024));
            Console.WriteLine("消耗的内存总大小为:{0}M", processes.TotalMemory() / (1024 * 1024));
        }

        /// <summary>
        /// 为IEnumerable<ProcessData>泛型添加扩展方法
        /// </summary>
        /// <param name="processes">所有的进程集合</param>
        /// <returns>返回值,消耗内存空间</returns>
        static Int64 TotalMemory(this IEnumerable<ProcessData> processes)
        {
            Int64 result = 0;

            foreach (var process in processes)
            {
                result += process.Memory;
            }

            return result;
        }

    }

  嗯,扩展方法就告一段落了,想要继续深入的话,自学,别忘了MSDN是一个自学的非常好的资源。下面就开始今天的第三个重点:匿名类型

 

重点三:匿名类型

  这里为了方便,我对前面的例子做一个小小的改动,我们摈弃了过滤器的功能,还有并没有定义ProcessData这个实体类,同样我们完成输出所有的进程:

DisplayProcess
        //展示进程的方法
        static void DisplayProcess()
        {
            var processes = new List<Object>();

            foreach (var process in Process.GetProcesses())
            {
                //匿名类型
                processes.Add(new
                {
                    Id = process.Id,
                    Name = process.ProcessName,
                    Memory = process.WorkingSet64
                });
            }

            //控制台输出
            ObjectDumper.Write(processes);
        }

  这里用到了一个ObjectDumper的helper类,他的功能主要是在控制台显示数据:代码我贴在下面

ObjectDumper
public class ObjectDumper
{
    public static void Write(object element)
    {
        Write(element, 0);
    }

    public static void Write(object element, int depth)
    {
        Write(element, depth, Console.Out);
    }

    public static void Write(object element, int depth, TextWriter log)
    {
        ObjectDumper dumper = new ObjectDumper(depth);
        dumper.writer = log;
        dumper.WriteObject(null, element);
    }

    TextWriter writer;
    int pos;
    int level;
    int depth;

    private ObjectDumper(int depth)
    {
        this.depth = depth;
    }

    private void Write(string s)
    {
        if (s != null)
        {
            writer.Write(s);
            pos += s.Length;
        }
    }

    private void WriteIndent()
    {
        for (int i = 0; i < level; i++) writer.Write("  ");
    }

    private void WriteLine()
    {
        writer.WriteLine();
        pos = 0;
    }

    private void WriteTab()
    {
        Write("  ");
        while (pos % 8 != 0) Write(" ");
    }

    private void WriteObject(string prefix, object element)
    {
        if (element == null || element is ValueType || element is string)
        {
            WriteIndent();
            Write(prefix);
            WriteValue(element);
            WriteLine();
        }
        else
        {
            IEnumerable enumerableElement = element as IEnumerable;
            if (enumerableElement != null)
            {
                foreach (object item in enumerableElement)
                {
                    if (item is IEnumerable && !(item is string))
                    {
                        WriteIndent();
                        Write(prefix);
                        Write("...");
                        WriteLine();
                        if (level < depth)
                        {
                            level++;
                            WriteObject(prefix, item);
                            level--;
                        }
                    }
                    else
                    {
                        WriteObject(prefix, item);
                    }
                }
            }
            else
            {
                MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
                WriteIndent();
                Write(prefix);
                bool propWritten = false;
                foreach (MemberInfo m in members)
                {
                    FieldInfo f = m as FieldInfo;
                    PropertyInfo p = m as PropertyInfo;
                    if (f != null || p != null)
                    {
                        if (propWritten)
                        {
                            WriteTab();
                        }
                        else
                        {
                            propWritten = true;
                        }
                        Write(m.Name);
                        Write("=");
                        Type t = f != null ? f.FieldType : p.PropertyType;
                        if (t.IsValueType || t == typeof(string))
                        {
                            WriteValue(f != null ? f.GetValue(element) : p.GetValue(element, null));
                        }
                        else
                        {
                            if (typeof(IEnumerable).IsAssignableFrom(t))
                            {
                                Write("...");
                            }
                            else
                            {
                                Write("{ }");
                            }
                        }
                    }
                }
                if (propWritten) WriteLine();
                if (level < depth)
                {
                    foreach (MemberInfo m in members)
                    {
                        FieldInfo f = m as FieldInfo;
                        PropertyInfo p = m as PropertyInfo;
                        if (f != null || p != null)
                        {
                            Type t = f != null ? f.FieldType : p.PropertyType;
                            if (!(t.IsValueType || t == typeof(string)))
                            {
                                object value = f != null ? f.GetValue(element) : p.GetValue(element, null);
                                if (value != null)
                                {
                                    level++;
                                    WriteObject(m.Name + ": ", value);
                                    level--;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void WriteValue(object o)
    {
        if (o == null)
        {
            Write("null");
        }
        else if (o is DateTime)
        {
            Write(((DateTime)o).ToShortDateString());
        }
        else if (o is ValueType || o is string)
        {
            Write(o.ToString());
        }
        else if (o is IEnumerable)
        {
            Write("...");
        }
        else
        {
            Write("{ }");
        }
    }
}

  然后我们再Main方法中调用DisplayProcess方法:

        static void Main(string[] args)
        {
            DisplayProcess();
            Console.ReadKey();
        }

  输出结果:(部分)

  好的,我主要来分析一下DisplayProcess方法中的代码:

//匿名类型
processes.Add(new
{
     Id = process.Id,
     Name = process.ProcessName,
     Memory = process.WorkingSet64
}            

  你们有没有发现,new关键字后面并没有跟具体的类型名称,这就是我们的匿名类型语法,并不需要指定类型名称,编译器自动的帮我们生成这样的一个类,使用匿名类型的好处就是,如果某类型在代码中只出现一次,并没有多次使用,而且我们在程序中可以自由的组合某个类的结构,减少代码量,灵活性更好。

  哎呀,累死了,说了这么长时间,今天的内容有点枯燥,我也不知道如何讲才能说的生动点,后面的文章我尽量说得生动些,大家就凑合看吧!如果你觉得这篇文章对你有所帮助,那么我的效果就达到了,另外,请不要吝啬你的支持,好文要顶。thank you...

技术讨论QQ群:159227188  上海-Michael

技术博客网站:http://www.kencery.com/