学生时期,有过小组作业,当时分工一人做那么两三个页面,然而在前端差不多的时候,我和另一个同学发生了争执。当时用的是简单的三层架构(DLL、BLL、UI),我个人觉得各写各的吧,到时候合并,而他觉得应该把底层先写好,他好直接调用中间层的方法。

  到出来工作之后,接触接口,想整理一下这个:接口到底是个什么概念呢?

  需要说明一点的是,我这里说的接口,不是API那个接口,而是“暂时没实现”那个接口。

  刚接触接口类型的时候,还不太熟练,看到返回接口类型的方法,总在奇怪,这个返回的对象怎么知道它取哪个实现?可以看一个简单的例子:

报错  

(无法创建抽象类或接口的实例  

    var test = new ITestInterface();

正确   

ITestInterface infa = new TestInterface();

infa.Func1();

  也即,返回的类型总是具类,是确定的,方法已经实现的。

ITestInterface infa = new TestInterface();

  其中的 ITestInterface 更像一个模具,对应这个模具造型的内容,由TestInerface提供。

  那么,接口到底如何使用?

  接口的使用,要这样看:“具备某种特征(功能)”。

  例如看 ITestInterface infa = new TestInterface(); 其中,TestInterface具备有ITestInterface的特征,而ITestInterface作为有某种特征(功能)的标记,它对具体如何达到这种特征(功能)是不感兴趣的,有标记就有特征。这种标记的体现,在C#里面就是继承。

  说到这里,老朋友IEnumerable是一定要介绍的。

一、迭代器 IEnumerable

  集合这种数据结构是很常见的,通常的操作是对集合的内容做筛选,或排序。IEnumerable接口描述的是返回可循环访问集合的枚举数,继承这个接口,需要实现 public IEnumerator GetEnumerator() {} 方法。

  那么,IEnumerator是个什么er?继承这个接口之后,IDE提示需要实现的方法——

    public class Iterator : IEnumerator
{
public object Current => throw new NotImplementedException();
public bool MoveNext() { … }
public void Reset() { … }
}

  有一个当前对象,一个是否能指向下一个的判断,还有一个重置。那么,可以想象迭代器应该是这样用的:

    Iterator iterator = new Iterator();
while (iterator.MoveNext())
{
// Get iterator.Current to do something..
Console.WriteLine(iterator.Current.ToString());
}

  但这看起来,并不太聪明,或者这样使用比较“合理”:

  是不是get到了某种真相?foreach里面接受的是IEnumerable对象,并且会在此处调用到GetEnumerator去得到Enumerator。那么到底public IEnumerator GetEnumerator(){}要怎么实现呢,C# 2已经提供了yield语句简化迭代器。

    public class IterationSample : IEnumerable
{
public IEnumerator GetEnumerator()
{
for (int index = ; index < values.Length; index++)
{
yield return values[(index + startingPoint) % values.Length];
}
} public object[] values;
public int startingPoint; public IterationSample(object[] values, int startingPoint)
{
this.values = values;
this.startingPoint = startingPoint;
}
}

  再来使用Enumerator:

    object[] objs = new object[]{"a", "b", "c", "d"};
IterationSample sam = new IterationSample(objs, );
foreach (var str in sam)
{
// do something..
}

  可以想象,yield是个怎么样的存在,“一次一次返回”这是我对yield的第一印象描述。但总觉得还是有些说不清楚,这种时候还是得看看书:

  “yield return 语句指表示 ’暂时地’ 退出方法——事实上,可以把它当做暂停”,

  既然有这种说法,那还得给出个demo[1],关于怎么个“暂停”。

  (这里悄咪咪用C# 6的新语法using static System.Console; 实在懒得打 Console.WriteLine();)

    class Program
{
static void Main(string[] args)
{
IEnumerable<int> iterable = CreateEnumerable();
IEnumerator<int> iterator = iterable.GetEnumerator();
WriteLine("Starting to iterate");
while (true)
{
WriteLine("Calling MoveNext()..");
bool result = iterator.MoveNext();
WriteLine($"MoveNext result = {result}");
if (!result) break;
WriteLine("Fetching Current..");
WriteLine($"..Current result = {iterator.Current.ToString()}");
}
ReadLine();
} static readonly string Padding = new string(' ', ); static IEnumerable<int> CreateEnumerable()
{
WriteLine("Start of CreateEnumerable()");
for (int i = ; i < ; i++)
{
WriteLine($"{Padding} About to yield {i}");
yield return i;
WriteLine($"{Padding} After yield");
}
WriteLine($"{Padding} Yielding final value");
yield return -; WriteLine($"{Padding} End of CreateEnumerable");
}
}

  此处可以留意“After yield”是什么时候出现的,就会发现[1]:

   l   在第一次调用MoveNext之前,CreateEnumerable中的代码不会被调用;

   l   当调用MoveNext时,Current也同时变化;

   l   在yield return的位置,代码就停止执行,在下一次调用MoveNext时又继续执行(再return一次)

  yield的故事还没有完,此处就简短介绍。

  yield return提供了逐个返回的条件,对于仅是取集合当中符合筛选条件的一项,用yield是方便的,逐个返回的情况下,不会占用过多的存储空间。但如果涉及到排序(或者比大小、最值)的问题,那必然要求集合当中的所有数据处于可用状态,这里也出现了一些传值的概念。

  yield return属于延迟执行(Deferred Execution),延迟执行再区分为惰性求值(Lazy Evaluation)和热情求值(Eager Evaluation)。

Deferred but eager execution

Deferred and lazy execution

IEnumerable<int> GetComputation(int maxIndex)

{

var result = new int[maxIndex];

for(int i = 0; i < maxIndex; i++)

{

result[i] = Computation(i);

}

foreach(var value in result)

{

yield return value;

}

}

IEnumerable<int> GetComputation(int maxIndex)

{

for(int i = 0; i < maxIndex; i++)

{

yield return Computation(i);

}

}

  详见:https://stackoverflow.com/questions/2515796/deferred-execution-and-eager-evaluation

  下面这个例子,是惰性求值,迭代器返回的值受lambda表达式控制,并且是在每一次访问到这一个“点”的时候,再去返回 “点”的处理结果。热情求值是直接返回“点”,没有再过处理。两相比较,还得看具体的编程情况以作选择,此处不赘述。

    static void Main(string[] args)
{
var sequence = Generate(, () => DateTime.Now);
foreach (var value in sequence)
WriteLine($"{value:T}");
} static IEnumerable<TResult> Generate<TResult>(int number, Func<TResult> generator)
{
for (var i = ; i < number; i++)
{
Sleep();
yield return generator();
}
}

  (为了逻辑上的全面性,)与延迟执行相对的是立即执行(Immediately Execution),是一次返回就完成函数的操作。

二、迭代器 IQueryable

  LINQ to Object 是针对本地数据存储(local data store)来执行查询的,系统会根据lambda表达式里面的逻辑创建匿名的委托,并执行代码;

  LINQ to SQL 针对的是在数据库执行的,会把查询条件解析成T-SQL,并且把SQL语句发送给数据库引擎。

  关于,自动生成SQL语句这一点,可以做个尝试,例如:创建了一个EF,调试监控连接数据库后返回的变量类型。

    var dbcontext = new CM_FORTESTEntities();
var tb1 = dbcontext.tblEmployees;
var tb2 = dbcontext.tblEmployees.Where(a => a.Id == );
var tb3 = dbcontext.tblEmployees.Where(a => a.Gender == "Male").OrderByDescending(a => a.Id);

  咋一看,怎么还能是不同类型?但是再看类成员,会发现一些端倪:

public abstract class DbSet : DbQuery, IInternalSetAdapter
public abstract class DbQuery : IOrderedQueryable, IQueryable, IEnumerable, IListSource, IInternalQueryAdapter public interface IOrderedQueryable : IQueryable, IEnumerable

  好了,终于引入到这个朋友——IQueryable,IQueryable有些什么必要实现的方法呢?

    public class QueryableSample : IQueryable
{
public Expression Expression => throw new NotImplementedException();
public Type ElementType => throw new NotImplementedException();
public IQueryProvider Provider => throw new NotImplementedException();
public IEnumerator GetEnumerator()
{ throw new NotImplementedException(); }
}

  IQueryable是IEnumerable的孩子(IQueryable : IEnumerable),它是一个有自己花样的迭代器。这个花样如何体现呢?关键还在于Expression、IQueryProvider上。

  从字面上来看,Expression是查询条件的表达式树;那么Provider就是提供数据的成员了。

    public class QueryableSample : IQueryable
{
public Expression Expression { get; }
public Type ElementType => typeof(ModelItem);
public IQueryProvider Provider { get; } public IEnumerator GetEnumerator()
{
return Provider.Execute<IEnumerable>(Expression).GetEnumerator();
} IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
} public QueryableSample(IQueryProvider provider, Expression expression)
{
if (provider == null)
throw new ArgumentNullException("provider");
if (expression == null)
throw new ArgumentNullException("expression"); Provider = provider;
Expression = expression;
}
}

  预感中,Provider会是个重要角色:

public class QueryProvider : IQueryProvider

IQueryable CreateQuery(Expression expression)

return new QueryableSample(this, expression);

IQueryable<TElement> CreateQuery<TElement>(Expression expression)

return (IQueryable<TElement>) new QueryableSample(this, expression);

object Execute(Expression expression)

return QueryResult.Execute(expression, false);

TResult Execute<TResult>(Expression expression)

bool IsEnumerable = (typeof(TResult).Name == "IEnumerable`1");

return (TResult)QueryResult.Execute(expression, IsEnumerable);

    public class QueryProvider : IQueryProvider
{
public IQueryable CreateQuery(Expression expression)
{
return new QueryableSample(this, expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return (IQueryable<TElement>) new QueryableSample(this, expression);
}
public object Execute(Expression expression)
{
return QueryResult.Execute(expression, false);
}
public TResult Execute<TResult>(Expression expression)
{
bool IsEnumerable = (typeof(TResult).Name == "IEnumerable`1");
return (TResult)QueryResult.Execute(expression, IsEnumerable);
}
}
public sealed class QueryResult
{
public static object Execute(Expression expression, bool isEnumerable)
{ // 利用expression得到数据结果,设其为records
QueryableSample records = null;
if (isEnumerable)
return records.Provider.CreateQuery(expression);
else
return records.Provider.Execute(expression);
}
}

  在github上找到了个详尽些的QueryableDemo可以看: https://github.com/andreychizhov/NestQueryableProvider

三、IEnumerable 与 IQueryable

  下面以一个例子比较二者最大的区别[2]:

var q = from c in dbContext.Customers

where c.City == "London"

select c;

var finalAnswer = from c in q

orderby c.Name

select c;

使用IQueryable<T>所内置的LINQ to SQL机制。

(LINQ to SQL程序库会把相关的查询操作合起来执行,仅向数据库发出一次调用,即where和orderby都是在同一次SQL查询中完成。)

var q = (from c in dbContext.Customers

where c.City == "London"

select c).AsEnumerable();

var finalAnswer = from c in q

orderby c.Name

select c;

把数据库对象强制转换成IEnumerable形式的序列,并把排序等工作放在本地完成。

(即会把where字句后得到的结果转换成IEnumerable<T>的序列,再采用LINQ to Objects机制完成后续,排序是通过委托在本地执行。)

  注意:

  两种不同的数据处理方式,依循着两套完全不同的流程。无论是用lambda表达式来撰写查询逻辑还是以函数参数的形式来表示这些逻辑,针对IEnumerable<T>所设计的那些扩展方法都将其视为委托。反之,针对IQueryable<T>的那些扩展方法用的则是表达式树。【表达式树 可以把各种逻辑合并起来成一条SQL语句。】

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

  如果使用IEnumerable<T>,则必须在本地进行。系统把lambda表达式编译到方法里,在本地计算机上运行,这意味着无论有待处理的数据在不在本地,都必须先获取过来才行。

  同时,用来支持IQueryable的那些Provider未必能够完全解析每一种查询,通常这些Provider只能解读几种固定的(.NET Framework已经实现)的运算符(方法),如果要在查询操作里面调用除此之外的其它方法,那可能就得把序列当成IEnumerable来查询。

吐槽    :emmmmmm,,,本来是想写我与接口二三事,结果竟然如此跑偏,太多细节能扣啦,知识点冥冥间也有关联,慢慢捋吧~

立Flag:本月开启机器学习,今年要把C#基础篇搞定。

注释:

[1] 自《深入理解C#》(第3版)Jon Skeet 著  姚琪琳 译

[2] 自《Effective C#》(第3版) 比尔·瓦格纳 著

【C#夯实】我与接口二三事:IEnumerable、IQueryable 与 LINQ的更多相关文章

  1. Java并发编程二三事

    Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...

  2. Java中的匿名内部类及内部类的二三事

    匿名内部类适合创建那些只需要使用一次的类,它的语法有些奇怪,创建匿名内部类会立即创建一个该类的实例,这个类定义立即消失,且不能重复使用. 定义匿名类的格式如下: new 实现接口() |父类构造器(实 ...

  3. linux杂记(十二?) 关于账号和密码的二三事

    关于密码的二三事 关于账号和密码的二三事 久了不更linux的相关知识,实在是懒得想内容点(纯粹是懒).那么今天就来谈谈关于linux密码和账号的重要概念. 假如你的主机遭到入侵,那么对方的第一个侵入 ...

  4. MySQL5.7关于密码二三事

    MySQL5.7关于密码二三事 第一个:update user set password=password('root') where user='root' and host='localhost' ...

  5. Emacs 启动优化二三事

    Emacs 启动优化二三事 */--> div.org-src-container { font-size: 85%; font-family: monospace; } p {font-siz ...

  6. WinForm二三事(三)Control.Invoke&Control.BeginInvoke

    http://www.cnblogs.com/yuyijq/archive/2010/01/11/1643802.html 这个系列从2009年写到2010年,差点又成太监文.随着WPF/Silver ...

  7. iOS7下滑动返回与ScrollView共存二三事

    [转载请注明出处] = =不是整篇复制就算注明出处了亲... iOS7下滑动返回与ScrollView共存二三事 [前情回顾] 去年的时候,写了这篇帖子iOS7滑动返回.文中提到,对于多页面结构的应用 ...

  8. 一只代码小白git托管路上的二三事

    [经验]一只代码小白git托管路上的二三事 写在前面的话 寒假的时候,娄老师给我们布置了代码托管的作业,并要求把托管地址发给学委.因假期的时候没有带电脑回家,所以只是在手机上草草注册了,也稀里糊涂就将 ...

  9. Hadoop序列化与Writable接口(二)

    Hadoop序列化与Writable接口(二) 上一篇文章Hadoop序列化与Writable接口(一)介绍了Hadoop序列化,Hadoop Writable接口以及如何定制自己的Writable类 ...

随机推荐

  1. python基础(27):类成员的修饰符、类的特殊成员

    1. 类成员的修饰符 类的所有成员在上一步骤中已经做了详细的介绍,对于每一个类的成员而言都有两种形式: 公有成员,在任何地方都能访问 私有成员,只有在类的内部才能方法 私有成员和公有成员的定义不同:私 ...

  2. Winform中怎样获取项目图片资源并转换为Image对象

    场景 DevExpress的TreeList怎样给树节点设置图标: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/10274554 ...

  3. SSH免密码登录和Git免密操作

    SSH免密码登录和Git免密操作 每次打完包后都需要把包传到对应的服务器上从而让测试人员下载安装,但是每次ssh或scp时都需要重新输入密码:使用git代码托管平台只要修改了密码就需要输入密码.本文主 ...

  4. openssl rsa加密,解密以及X509证书的使用

    Openssl的相关使用 生成证书 生成证书见:使用 openssl 生成证书 代码实现 Cert.h #ifndef _CERT_H #define _CERT_H ///header files ...

  5. 关于tomcat的环境参数配置

    在linux里 export CATALINA_OPTS=”-Xms(min heap)m -Xmx(max heap)m” 修改堆内存: -XX:MaxPermSize=128m 修改最大持久带内存 ...

  6. mongodb数据库的存储问题

    MongoDB在Windows中默认的数据库目录是c:\data.如果在没有该目录的情况下,直接运行mongod.exe,就会报如下错误(并没有把mongodb设置为服务,所以通过命令行的形式启动,注 ...

  7. Scrapy-splash

    Scrapy-splash Splash是一个javascript渲染服务.它是一个带有HTTP API的轻量级Web浏览器,使用Twisted和QT5在Python 3中实现.QT反应器用于使服务完 ...

  8. day46_9_5前端(3)

    一.调节长宽. 在css中可以对块级标签设置长和宽,但是对行内标签无效,其属性如下: 1.height:80px 高度. 2.width:80px 宽度. 二.字体属性. 设置一个标签中的字体.比如黑 ...

  9. day4_7.2

    流程语句 1.if判断语句 在python中if语句可以依据判断的条件,决定执行哪个语句.其格式如下: if 条件: 代码1 else: 代码2 当满足条件1时,执行代码1,否则执行代码2.所以条件语 ...

  10. Markdown & LaTex 常用语法

    目录 blog 的目录 博客园自带目录 用 javascript 自定义目录 主标题 副标题 h1,一级标题 h2,二级标题 h3,三级标题 注释 常用的符号及文本形式 如果你想在markdown中文 ...