第十四章 支持标准查询操作符的集合接口

来源:互联网 发布:怎么更换淘宝的实名制 编辑:程序博客网 时间:2024/06/05 09:23

1、集合接口

1.1 IEnumerator和IEnumerable

1.2.1 IEnumerator接口

IEnumerator接口定义了遍历集合的基本方法,MoveNext()方法从集合的一个元素移动到下一个元素,如果越过集合末尾则返回false。Current是只读成员,用于返回当前元素。Reset()方法一般会抛出异常,所以一般不会调用。

public Interface IEnumerator

{

    bool MoveNext();

    object Current {get;}

    void Reset();

}

1.2.2 IEnumerable接口

IEnumerable接口只有一个GetEnumerator()方法,用于获取枚举器。集合类不直接支持IEnumerator接口,而是支持IEnumerable接口。通过GetEnumerator获取枚举器,然后由其他类维持游标状态。

public interface IEnumerable

{

    IEnumerator GetEnumerator();

}

IEnumerable接口的作用:

  • 支持foreach语句;
  • 支持集合初始化;
  • 作为一个标准的集合类与其他类库进行交互

1.3 ICollection<T>和ICollection

ICollection直接继承IEnumerable。ICollection除了具备IEnumerable的遍历集合的功能外,还增加维护集合的功能:

  • 统计集合的元素个数;
  • 获取元素下标;
  • 判断元素是否存在;
  • 添加、移除元素。

ICollection<T>和ICollection略有不同,ICollection不提供编辑集合的功能,即Add、Remove以及检查元素是否存在。

1.4 IList<T>和IList

IList直接继承ICollection和IEnumerable,IList除了包括两者的功能外,还支持了根据下标访问和处理元素的功能,如,IndexOf、Insert、RemoveAt等。

1.5 IReadOnlyList<T>

IReadOnlyList是.Net4.5中新增的接口,可以看作IList<T>的缩减版,该接口去掉了所有可能修改集合的功能。

1.6 IDictionary<TKey, TValue>

IDictionary继承自ICollection<T>和IEnumerable。IDictionary提供了键值对访问集合的功能,扩展了通过Key值操作数据的方法。

 

2、标准查询操作符

IEnumerable<T>上每个方法都是一个标准查询操作符,用于为所操作的集合提供查询功能。

IEnumerable<T>除了提供GetEnumerator()方法外,还提供了多个标准查询方法,这些方法由扩展类System.Linq.Enumerable类提供,所以若要使用这些扩展方法需要引用System.Linq。

2.1 Where()筛选

Where()方法中可以传递一个Lambda表达式或Lambda语句,根据Lambda表达式的筛选条件返回一个新的集合。

实例:筛选出集合中每个属性YearOfPublication由"18"开头上午对象。

IEnumerable<Patent> patents = PatentData.Patents;

patents = patents.Where(p => p.YearOfPublication.StartsWith("18"));

注意Where() 方法的表达式实参并非一定是在赋值时求值的,只有在需要遍历集合项时,才会对表达式求值。

2.2 Select()投射

Select()与Where()类似,可以按照元素所在位置判断处理,而Select()所指定的处理式Selector必须返回一个对象,该对象可以是指定类型,也可以是匿名类型。

实例:根据筛选后的结果返回新的对象

IEnumerable<Patent> patents = PatentData.Patents;

patents = patents.Where(p => p.YearOfPublication.StartsWith("18")).Select(p => new Patent {Title = p.Title, YearOfPublication = p.YearOfPublication, InventorIds = p.InventorIds});

2.3 Count()计数

 Count()的作用是统计元素个数,如果集合提供Count属性,则首选是使用Count属性。ICollection<T>有Count属性,而IEnumerable<T>会枚举整个集合

Console.WriteLine("Count={0}", patents.Count(p => p.YearOfPublication.StartsWith("18")));

注意:如果只是要判断集合元素个数是否大于0,可以使用Any()方法,Any()遍历到集合中的第一个元素就返回,而Count()遍历所有元素。

2.4 OrderBy()和ThenBy()排序

OrderBy()根据传入的Lambda表达式提供的参数对集合进行排序,OrderBy()只会获取第一个参数来排序,如果增加第二个或更多排序因素,则要使用ThenBy()。

OrderBy()返回的是IOrderedEnumerable<T>接口,该接口继承自IEnumerable<T>接口,ThenBy()是IOrderedEnumerable<T>接口的扩展方法。

OrderByDescending()和ThenByDescending()提供相同的功能,只是变成了按降序排序。

实例:对集合进行排序,首先是按YearOfPublication排序,再按Title排序

IEnumerable<Patent> patents = PatentData.Patents;

patents = patents.OrderBy(p => p.YearOfPublication).ThenBy(p => p.Title);

注意:当访问集合成员时才开始排序。调用OrderBy()在调用ThenBy()会导致前面的OrderBy()再次被调用。

2.5 Join()内部联接

Join()的作用是根据一定条件将两个集合的成员联接成一个新的成员。Join()有四个参数,第一个参数是要联接的集合,第二和第三个参数是Lambda表达式,分别是两个集合联接的键值,第四个参数是也是Lambda表达式,用于返回新成员。

var items = employees.Join(

      departments,

      emp=>emp.departmentId,

      dep=>dep.Id,

      (emp, dep) => new

      {

          emp.id,

          emp.name,

          emp.title,

          Department = dep

      });

2.6 GroupBy()分组

GroupBy()的作用是根据Lamdba表达式对集合进行分组,返回IGrouping<TKey, TElement>类型。

IEnumerable<IGrouping<int, Employee>> groupEmployees = employees.GroupBy(emp => emp.DepartmentId);

2.7 推迟执行——查询的重复执行

这是Linq一个重要的特性,Lamdba表达式除非被调用,否则其中的代码不会被执行,由于这个特性,在一些意想不到的地方还是对整个集合进行了遍历。

static void Main(string[] args)
        {
            int[] scores = new int[] { 97, 92, 79, 60 };
            bool result = false;

            IEnumerable<int> scoreQuery = scores.Where(
                score =>
                {
                    result = false;
                    if (score > 80)
                    {
                        result = true;
                        Console.WriteLine("Score = {0}", score);
                    }
                    return result;
                });

            Console.WriteLine("1)  Invoke empty foreeach, it will execute");
            foreach (int i in scoreQuery)
            {
            }

            Console.WriteLine("2)  Invoke IEnumerable Count, it will execute");
            scoreQuery.Count();

            Console.WriteLine("3)  Invoke IEnumerable ToArray, it will execute");

            scoreQuery = scoreQuery.ToArray();
            Console.ReadKey(); 
        }

输出结果:

1)  Invoke empty foreeach, it will execute

Score = 97

Score = 92

2)  Invoke IEnumerable Count, it will execute

Score = 97

Score = 92

3)  Invoke IEnumerable ToArray, it will execute

Score = 97

Score = 92
注意,Console.WriteLine("1)  Invoke ... ..")是先于Lamdba表达式执行的,因为在这之前并未调用Lamdba表达式,这就是延迟执行的概念。

以下是会触发遍历集合的情况:

  • foreach:foreach 的本质是调用 MoveNext(),这是会触发遍历的开关。
  • Count:在前面讨论过,IEnumerable<T>的Count并不是一个属性,而是一个函数。
  • ToArray等:ToXXX是个好东西,可以把集合变成可以安全操作的对象,但它同样会触发遍历,而且会把结果都加载到内存中。
原创粉丝点击