深入理解C#(第3版)-- 【C#3】第10章 扩展方法(学习笔记)

10.1   未引入扩展方法之前的状态

代码清单10-1   为流提供附加功能的一个简单的工具类

using System.IO;

public static class StreamUtil
{
    constint BufferSize = 8192;
    
    public static void Copy(Stream input, Stream output)
    {
        byte[] buffer = new byte[BufferSize];
        int read;
        while((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            output.Write(buffer, 0, read);
        }
    }
    
    public static byte[] ReadFully(Stream input)
    {
        using(MemoryStream tempStream = new MemoryStream())
        {
            Copy(input, tempStream);
            return tempStream.ToArray();
        }
    }
}

代码清单10-2  用StreamUtil将Web响应流复制到一个文件

WebRequest request = WebRequest.Create("http://manning.com");
using (WebResponse response = request.GetResponse())
    using (Stream responseStream = response.GetResponseStream())
        using (FileStream output = File.Create("response.dat"))
        {
            StreamUtil.Copy(responseStream, output);
        }

10.2   扩展方法的语法

10.2.1   声明扩展方法

并不是任何方法都能作为扩展方法使用——它必须具有以下特征:
 它必须在一个非嵌套的、非泛型的静态类中(所以必须是一个静态方法);
 它至少要有一个参数;
 第一个参数必须附加this关键字作为前缀;
 第一个参数不能有其他任何修饰符(比如out 或ref );
 第一个参数的类型不能是指针类型。

我们将第一个参数的类型称为方法的扩展类型(extended type),即指该方法扩展了该类型——在本例中我们扩展了Stream。这不是语言规范中的官方术语,但这样方便记忆。

代码清单10-3   包含扩展方法的 StreamUtil类

public static class StreamUtil
{
    constint BufferSize = 8192;
    
    public static void CopyTo(this Stream input, Stream output)
    {
        byte[] buffer = new byte[BufferSize];
        int read;
        while((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            output.Write(buffer, 0, read);
        }
    }
    
    public static byte[] ReadFully(this Stream input)
    {
        using(MemoryStream tempStream = new MemoryStream())
        {
            CopyTo(input, tempStream);
            return tempStream.ToArray();
        }
    }
}

10.2.2   调用扩展方法

代码清单10-4   用扩展方法复制一个流 

WebRequest request = WebRequest.Create("http://manning.com");

using (WebResponse response = request.GetResponse())
    using (Stream responseStream = response.GetResponseStream())
        using (FileStream output = File.Create("response.dat"))
        {
            responseStream.CopyTo(output);
        }

虽然扩展方法完全可以作为普通的静态方法来使用,但是移植大量代码的时候扩展方法就很有用了。

你可能已经注意到,在这些方法调用中,没有任何迹象表明我们使用的是扩展方法,而不是Stream 类的普通实例方法。

10.2.3   扩展方法是怎样被发现的

如果编译器认为一个表达式好像是要使用一个实例方法,但没有找到与这个方法调用兼容的实例方法(如不存在具有该名称的方法,或者没有重载的版本能匹配给定的实参),就会查找一个合适的扩展方法。它会检查导入的所有命名空间和当前命名空间中的所有扩展方法,并匹配那些从表达式类型到扩展类型存在着隐式转换的扩展方法。

如果存在多个适用的扩展方法,它们可应用于不同的扩展类型(使用隐式转换),那么将使用在重载的方法中应用的“更好的转换”规则(参见9.4.4 节),来选择最合适的方法。

如果存在适当的实例方法,则实例方法肯定会先于扩展方法使用。

10.2.4   在空引用上调用方法

代码清单10-5   在空引用上调用扩展方法

using System;

public static class NullUtil
{
    public static bool IsNull(this object x)
    {
        return x == null;
    }
}

public class Test
{
    static void Main()
    {
        object y = null;
        Console.WriteLine(y.IsNull());
        y = new object();
        Console.WriteLine(y.IsNull());
    }
}
View Code

代码清单10-5 的输出先是True,然后是False 。如果IsNull 是一个普通的实例方法,Main的第2行就会抛出一个异常。但是,这里的null是IsNull的实参。在扩展方法问世前,y.Isnull()这样的写法虽然可读性更好,却不合法,只能采用NullUtil.IsNull(y) 这样的写法。

在框架中,有一个特别明显的例子可以证明这种写法的好处:string.IsNullOrEmpty。

10.3   .NET 3.5 中的扩展方法

10.3.1  从Enumerable 开始起步

延迟执行 构造的可枚举的实例并不会做大部分工作。它只是将东西准备好,使数据能在适当的位置以一种“just-in-time ”的方式提供。这称为延迟执行,是LINQ的一个

核心部分。

10.3.2  用Where 过滤并将方法调用链接到一起

代码清单10-8  用Lambda表达式作为Where 方法的参数,从而只保留奇数

var collection = Enumerable.Range(0, 10)
                 .Where(x  => x % 2 !=   0)
                 .Reverse();
                 
foreach (var element in collection)
{
    Console.WriteLine(element);
}

扩展方法允许将静态方法调用链接到一起。这其实是扩展方法存在的主要原因之一。

10.3.3   插曲:似曾相识的Where 方法

public static IEnumerable<T> Where<T>(this  IEnumerable<T> source,
                                      Func<T, bool> predicate)
{
    if (source == null || predicate == null)
    {
        thrownew ArgumentNullException();
    }
    return WhereImpl(source, predicate);
}

private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source,
        Func<T, bool>   predicate)
{
    foreach (T item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}
View Code

10.3.4  用Select方法和匿名类型进行投影

Enumerable中最重要的投影方法就是Select ——它操纵一个IEnumerable<TSource>,把它投影成一个IEnumerable<TResult>。具体的投影是通过一个Func<TSource, TResult>来完成的,它代表要在每个元素上执行的转换,采用的是委托的形式。

10.3.5  用OrderBy方法进行排序

对数据排序是处理数据时的一项常规要求。在LINQ中,这一般是通过OrderBy或OrderByDescending方法来实现的。如果需要根据数据的多个属性排序,那么后面还可以跟随ThenBy 或ThenByDescending 。

需要注意的是,排序不会改变原有集合——它返回的是新的序列,所产生的数据与输入序列相同,当然除了顺序。

10.3.6   涉及链接的实际例子

1. 聚合:工资汇总

company.Departments
.Select(dept => new
{
    dept.Name,
    Cost = dept.Employees.Sum(person => person.Salary)
})
.OrderByDescending(deptWithCost => deptWithCost.Cost);

2. 分组:统计分配给开发者的bug 数量

bugs.GroupBy(bug => bug.AssignedTo)
.Select(list => new
{
    Developer = list.Key, Count = list.Count()
})
.OrderByDescending(x => x.Count);

10.4   使用思路和原则

我隐藏真相是为了让你看到更大的真相。

10.4.1   “扩展世界”和使接口更丰富

http://blogs.msdn.com/b/wesdyer/

Extending the World

对于一个给定的问题,程序员通常习惯于构建一个解决方案,直到最终能满足需求。现在,我们可以扩展世界来迎合解决方案,而不是一直构建方案,直到最终满足需求。如果一个库没有提供你需要的,就扩展这个库来满足你的需求。

10.4.2   流畅接口

过去英国有一个电视节目叫做Catchphrase (名言)。节目内容是,参赛者将看一个屏幕,屏幕上的一段动画会演示具有一定含义的短语或俗语,参赛者通过这些猜测其真正的含义。

FluentInterface

在框架中,流畅接口的一个很好的例子就是OrderBy和ThenBy 方法:用Lambda表达式稍加诠释,代码准确地描述了它要做的事情。

posted @ 2019-10-16 16:39  FH1004322  阅读(125)  评论(0)    收藏  举报