本篇文章将从三个方面来进行LINQ扩展的阐述:扩展查询操作符、自定义查询操作符和简单模拟LINQ to SQL。

1.扩展查询操作符

在实际的使用过程中,Enumerable或Queryable中的扩展方法有时并不能满足我们的需要,我们需要自己扩展一些查询操作符以满足需要。例如,下面的例子:

var r = Enumerable.Range(1, 10).Zip(Enumerable.Range(11, 5), (s, d) => s + d);
foreach (var i in r)
{
Console.WriteLine(i);
}
//output:
//12
//14
//16
//18
//20

Enumerable.ZIP扩展是用来将指定函数应用于两个序列的对应元素,以生成结果序列,这里是将序列[1...10]与序列[11...15]相对应位置的元素做加法而生成一个新的序列。内部实现如下:

static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>(IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) {
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}

很明显,取得是交集,即两个序列对应位置要都有元素才进行处理,所以上面的输出结果也是理所当然的。然而有时候,我们想以第一个序列为主序列,即结果序列的长度总是等于第一个序列的长度。我们来扩展一个查询操作符,取名为LeftZip,实现如下:

	/// <summary>
/// Merge right sequence into left sequence by using the specified predicate function.
/// </summary>
/// <typeparam name="TLeft"></typeparam>
/// <typeparam name="TRight"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="lefts"></param>
/// <param name="rights"></param>
/// <param name="resultSelector"></param>
/// <returns></returns>
public static IEnumerable<TResult> LeftZip<TLeft, TRight, TResult>(this IEnumerable<TLeft> lefts,
IEnumerable<TRight> rights, Func<TLeft, TRight, TResult> resultSelector)
{
if(lefts == null)
throw new ArgumentNullException("lefts");
if(rights == null)
throw new ArgumentNullException("rights");
if (resultSelector == null)
throw new ArgumentNullException("resultSelector");
return LeftZipImpl(lefts, rights, resultSelector);
}
/// <summary>
/// The Implementation of LeftZip
/// </summary>
/// <typeparam name="TLeft"></typeparam>
/// <typeparam name="TRight"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="lefts"></param>
/// <param name="rights"></param>
/// <param name="resultSelector"></param>
/// <returns></returns>
private static IEnumerable<TResult> LeftZipImpl<TLeft, TRight, TResult>(this IEnumerable<TLeft> lefts,
IEnumerable<TRight> rights, Func<TLeft, TRight, TResult> resultSelector)
{
using (var left = lefts.GetEnumerator())
{
using (var right = rights.GetEnumerator())
{
while (left.MoveNext())
{
if (right.MoveNext())
{
yield return resultSelector(left.Current, right.Current);
}
else
{
do
{
yield return resultSelector(left.Current, default(TRight));
} while (left.MoveNext());
yield break;
}
}
}
}
}

调用LeftZip,代码如下:

var r = Enumerable.Range(1, 10).LeftZip(Enumerable.Range(11, 5), (s, d) => s + d);
foreach (var i in r)
{
Console.WriteLine(i);
}
//output:
//12
//14
//16
//18
//20
//6
//7
//8
//9
//10

2.自定义查询操作符

之前,我们在实现枚举器的时候有一种自实现形式,即不继承IEnumerable和IEnumerator接口,自定义一个实现GetEnumerator()的类和一个实现Current和MoveNext的类,即可使用foreach进行迭代。我们还知道LINQ语句转换成了扩展方法的链式调用,标准查询操作符转换了同名扩展方法(首字母大写)。那么,如果我们自己去实现标准查询操作符的同名扩展方法,会不会得到执行呢?

开始尝试,创建一个静态类LinqExtensions,实现Where扩展方法,如下:

	/// <summary>
/// Filters a sequence of values based on a predicate.
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static IEnumerable<TResult> Where<TResult>(this IEnumerable<TResult> source,
Func<TResult, bool> predicate)
{
if (source == null)
throw new ArgumentNullException("source");
if (predicate == null)
throw new ArgumentNullException("predicate");
return WhereImpl(source, predicate);
}
/// <summary>
/// The implementation of Where
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
private static IEnumerable<TResult> WhereImpl<TResult>(this IEnumerable<TResult> source,
Func<TResult, bool> predicate)
{
using (var e = source.GetEnumerator())
{
while (e.MoveNext())
{
if (predicate(e.Current))
yield return e.Current;
}
}
}

调用部分代码如下:

var r = from e in Enumerable.Range(1, 10)
where e%2 == 0
select e;
foreach (var i in r)
{
Console.WriteLine(i);
}
//output:
//2
//4
//6
//8
//10

如何判断Where扩展方法被调用了呢?调试、在VS中选中Where然后F12转到定义和在Where扩展方法实现中打印输出都可以判断。你可能会有疑问?方法签名是不是要一致呢?答案是否定的。你可以将Where扩展方法改名为Select,然后调用改为如下:

var r = from e in Enumerable.Range(1, 10)
//where e % 2 == 0
select e % 2 == 0;
//output:
//2
//4
//6
//8
//10

最后结合枚举器,举一个例子:

public class Collection<T>
{
private T[] items; public Collection()
{ } public Collection(IEnumerable<T> collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
items = new T[collection.Count()];
Array.Copy(collection.ToArray(), items, collection.Count());
} public static implicit operator Collection<T>(T[] arr)
{
Collection<T> collection = new Collection<T>();
collection.items = new T[arr.Length];
Array.Copy(arr, collection.items, arr.Length);
return collection;
} public ItemEnumerator GetEnumerator()
{
return new ItemEnumerator(items);
} #region Item Enumerator
public class ItemEnumerator : IDisposable
{
private T[] items;
private int index = -1; public ItemEnumerator(T[] arr)
{
this.items = arr;
}
/// <summary>
/// Current属性
/// </summary>
public T Current
{
get
{
if (index < 0 || index > items.Length - 1)
throw new InvalidOperationException();
return items[index];
}
}
/// <summary>
/// MoveNext方法
/// </summary>
/// <returns></returns>
public bool MoveNext()
{
if (index < items.Length - 1)
{
index++;
return true;
}
else
{
return false;
}
} public void Reset()
{
index = -1;
}
#region IDisposable 成员 public void Dispose()
{
index = -1;
} #endregion
}
#endregion
} public static class EnumerableExtensions
{
public static Collection<T> Where<T>(this Collection<T> source, Func<T, bool> predicate)
{
if (source == null)
throw new ArgumentNullException("source");
if (predicate == null)
throw new ArgumentNullException("predicate");
return WhereImpl(source, predicate).ToCollection();
} private static IEnumerable<T> WhereImpl<T>(this Collection<T> source, Func<T, bool> predicate)
{
using (var e = source.GetEnumerator())
{
while (e.MoveNext())
{
if (predicate(e.Current))
{
yield return e.Current;
}
}
}
} public static Collection<TResult> Select<T, TResult>(this Collection<T> source, Func<T, TResult> selector)
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
return SelectImpl(source, selector).ToCollection();
} private static IEnumerable<TResult> SelectImpl<T, TResult>(this Collection<T> source, Func<T, TResult> selector)
{
using (var e = source.GetEnumerator())
{
while (e.MoveNext())
{
yield return selector(e.Current);
}
}
} public static Collection<T> ToCollection<T>(this IEnumerable<T> source)
{
if (source == null)
throw new ArgumentNullException("source");
return new Collection<T>(source);
}
}

包含两个类,一个是作为数据源,一个是用于扩展,调用方法如下:

Collection<int> collection = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var r = from c in collection
where c % 2 == 0
select c;
foreach (var i in r)
{
Console.WriteLine(i);
} //output:
//2
//4
//6
//8
//10

3.简单模拟LINQ to SQL

在LINQ之路(2)中,我们简单介绍了LINQ to SQL的原理。在这里,我们通过简单模拟LINQ to SQL来更一步了解LINQ to SQL原理。

首先创建数据源,创建Query类,实现IQueryable接口:

public class Query<T> : IQueryable<T>
{
#region 字段 private QueryProvider provider;
private Expression expression; #endregion #region 属性 #endregion #region 构造函数 public Query(QueryProvider provider)
{
if (provider == null)
throw new ArgumentNullException("provider");
this.provider = provider;
this.expression = Expression.Constant(this);
} public Query(QueryProvider provider, Expression expression)
{
if (provider == null)
throw new ArgumentNullException("provider");
if (expression == null)
throw new ArgumentNullException("expression");
if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type))
throw new ArgumentOutOfRangeException("expression");
this.provider = provider;
this.expression = expression;
}
#endregion #region 方法 public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>) this.provider.Execute(this.expression)).GetEnumerator();
} #endregion #region IEnumerable<T> 成员 IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return this.GetEnumerator();
} #endregion #region IEnumerable 成员 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
} #endregion #region IQueryable 成员 Type IQueryable.ElementType
{
get { return typeof(T); }
} Expression IQueryable.Expression
{
get { return this.expression; }
} IQueryProvider IQueryable.Provider
{
get { return this.provider; }
} #endregion
}

这个类比较简单,实现接口并初始化参数。

再来看Provider,创建QueryProvider类,实现IQueryProvider接口:

public class QueryProvider:IQueryProvider
{
#region 字段 private IDbConnection dbConnection; #endregion #region 属性 #endregion #region 构造函数
public QueryProvider(IDbConnection dbConnection)
{
this.dbConnection = dbConnection;
}
#endregion #region 方法 #endregion #region IQueryProvider 成员 public IQueryable<TElement> CreateQuery<TElement>(System.Linq.Expressions.Expression expression)
{
return new Query<TElement>(this, expression);
} public IQueryable CreateQuery(System.Linq.Expressions.Expression expression)
{
var type = expression.Type;
try
{
return (IQueryable) Activator.CreateInstance(typeof (Query<>).MakeGenericType(type), this, expression);
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
} TResult IQueryProvider.Execute<TResult>(System.Linq.Expressions.Expression expression)
{
return (TResult) this.Execute(expression);
} object IQueryProvider.Execute(System.Linq.Expressions.Expression expression)
{
return this.Execute(expression);
} public virtual object Execute(Expression expression)
{
if(expression == null)
throw new ArgumentNullException("expression");
return ExecuteImpl(expression);
} private IEnumerable ExecuteImpl(Expression expression)
{
//var type = expression.Type;
//var entityType = type.GetGenericArguments()[0];
List<Product> products = new List<Product>();
QueryTranslator queryTranslator = new QueryTranslator();
var cmdText = queryTranslator.Translate(expression);
IDbCommand cmd = dbConnection.CreateCommand();
cmd.CommandText = cmdText;
using (IDataReader dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
Product product = new Product();
product.ID = dataReader.GetInt32(0);
product.Name = dataReader.GetString(1);
product.Type = dataReader.GetInt32(2);
products.Add(product);
}
}
return products;
}
#endregion
}

再来看看查询翻译类,创建QueryTranslator类,继承自ExpressionVisitor抽象类:

public class QueryTranslator:ExpressionVisitor
{
#region 字段 private StringBuilder sb; #endregion #region 属性 #endregion #region 构造函数
public QueryTranslator()
{ } #endregion #region 方法 public string Translate(Expression expression)
{
this.sb = new StringBuilder();
this.Visit(expression);
return this.sb.ToString();
} private static Expression StripQuotes(Expression e)
{
while (e.NodeType == ExpressionType.Quote)
{
e = ((UnaryExpression) e).Operand;
}
return e;
} protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof (Queryable) &&
node.Method.Name == "Where")
{
sb.Append("SELECT * FROM (");
this.Visit(node.Arguments[0]);
sb.Append(") AS T WHERE ");
LambdaExpression lambda = (LambdaExpression) StripQuotes(node.Arguments[1]);
this.Visit(lambda.Body);
return node;
}
throw new NotSupportedException(string.Format("The Method '{0}' is not supported", node.Method.Name));
} protected override Expression VisitBinary(BinaryExpression node)
{
sb.Append("(");
this.Visit(node.Left);
switch (node.NodeType)
{
case ExpressionType.Equal:
sb.Append(" = ");
break;
case ExpressionType.NotEqual:
sb.Append(" <> ");
break;
case ExpressionType.GreaterThan:
sb.Append(" > ");
break;
case ExpressionType.GreaterThanOrEqual:
sb.Append(" >= ");
break;
case ExpressionType.LessThan:
sb.Append(" < ");
break;
case ExpressionType.LessThanOrEqual:
sb.Append(" <= ");
break;
default:
throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", node.NodeType));
}
this.Visit(node.Right);
sb.Append(")");
return node;
} protected override Expression VisitConstant(ConstantExpression node)
{
IQueryable q = node.Value as IQueryable;
if (q != null)
{
sb.Append("SELECT * FROM ");
sb.Append(DataContext.MetaTables.FirstOrDefault(f => f.Type == q.ElementType).TableName);
return node;
}
else if(node.Value == null)
{
sb.Append("NULL");
}
else
{
switch (Type.GetTypeCode(node.Value.GetType()))
{
case TypeCode.Boolean:
sb.Append(((bool) node.Value) ? 1 : 0);
break;
case TypeCode.String:
sb.AppendFormat("'{0}'", node.Value);
break;
case TypeCode.Object:
throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", node.Value));
default:
sb.Append(node.Value);
break;
}
}
return node;
} protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression != null && node.Expression.NodeType == ExpressionType.Parameter)
{
sb.Append(node.Member.Name);
return node;
}
throw new NotSupportedException(string.Format("The member '{0}' is not supported", node.Member.Name));
} #endregion
}

重写Visit相关方法,以Visitor模式解析表达式目录树。

最后来看下DataContext的实现:

public class DataContext : IDisposable
{
#region 字段 private IDbConnection dbConnection;
private static List<MetaTable> metaTables;
#endregion #region 属性 public TextWriter Log { get; set; } public IDbConnection DbConnection
{
get { return this.dbConnection; }
} public static List<MetaTable> MetaTables
{
get { return metaTables; }
}
#endregion #region 构造函数
public DataContext(string connString)
{
if (connString == null)
throw new ArgumentNullException(connString);
dbConnection = new SqlConnection(connString);
dbConnection.Open();
InitTables();
}
#endregion #region 方法 private void InitTables()
{
metaTables = new List<MetaTable>();
var props = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
var propType = prop.PropertyType;
if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof (Query<>))
{
var entityType = propType.GetGenericArguments()[0];
var entityAttr = entityType.GetCustomAttribute<MappingAttribute>(true);
if (entityAttr != null)
{
var metaTable = new MetaTable();
metaTable.Type = entityType;
metaTable.TableName = entityAttr.Name;
metaTable.MappingAttribute = entityAttr;
var columnProps = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var columnProp in columnProps)
{
var columnPropAttr = columnProp.GetCustomAttribute<MappingAttribute>(true);
if (columnPropAttr != null)
{
MetaColumn metaColumn = new MetaColumn();
metaColumn.MappingAttribute = columnPropAttr;
metaColumn.ColumnName = columnPropAttr.Name;
metaColumn.PropertyInfo = columnProp;
metaTable.MetaColumns.Add(metaColumn);
}
}
metaTables.Add(metaTable);
}
}
}
}
#endregion #region IDisposable 成员 protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
if (dbConnection != null)
dbConnection.Close();
} public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
} #endregion
}
[Database(Name = "IT_Company")]
public class QueryDataContext : DataContext
{
public QueryDataContext(string connString)
: base(connString)
{
QueryProvider provider = new QueryProvider(DbConnection);
Products = new Query<Product>(provider);
}
public Query<Product> Products
{
get;
set;
}
}

调用如下:

class Program
{
private static readonly string connString =
"Data Source=.;Initial Catalog=IT_Company;Persist Security Info=True;User ID=sa;Password=123456";
static void Main(string[] args)
{
using (var context = new QueryDataContext(connString))
{
var query = from product in context.Products
where product.Type == 1
select product;
foreach (var product in query)
{
Console.WriteLine(product.Name);
}
Console.ReadKey();
}
}
}
//output:
//MG500
//MG1000

LINQ之路(3):LINQ扩展的更多相关文章

  1. LINQ之路 4:LINQ方法语法

    书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression). LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方 ...

  2. LINQ之路(2):LINQ to SQL本质

    LINQ之路(2):LINQ to SQL本质 在前面一篇文章中回顾了LINQ基本语法规则,在本文将介绍LINQ to SQL的本质.LINQ to SQL是microsoft针对SQL Server ...

  3. LINQ之路系列文章导读

    本系列文章将会分为3篇来进行阐述,如下: LINQ之路(1):LINQ基础 LINQ之路(2):LINQ to SQL本质 LINQ之路(3):LINQ扩展

  4. LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法

    本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators).Zip操作符.转换方法(Conversion Methods).生成器方法(Generation M ...

  5. LINQ之路 7:子查询、创建策略和数据转换

    在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍 ...

  6. [转]LINQ之路系列博客导航

    分享一个学习Linq的好博客:Linq之路

  7. LINQ学习系列-----1.3 扩展方法

    这篇内容继续接着昨天的Lambda表达式的源码继续下去.昨天讲了Lambda表达式,此篇讲扩展方法,这两点都是Linq带来的新特性.    一.扩展方法介绍   废话不多说,先上源码截图: 上图中Ge ...

  8. LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法

    本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...

  9. LINQ之路10:LINQ to SQL 和 Entity Framework(下)

    在本篇中,我们将接着上一篇“LINQ to SQL 和 Entity Framework(上)”的内容,继续使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术 ...

随机推荐

  1. 《Java并发编程实战》第七章 取消与关闭 读书笔记

        Java没有提供不论什么机制来安全地(抢占式方法)终止线程,尽管Thread.stop和suspend等方法提供了这种机制,可是因为存在着一些严重的缺陷,因此应该避免使用. 但它提供了中断In ...

  2. 如何用C#使用java

    如何使用C#调用Java 今天需要使用C#调用Java的包,研究了一下,大体是以下几种解决方案: 把Java包转换为DLL或者EXE后注册为com组件,之后调用. 使用web service 比如:H ...

  3. linuxserver启动过程

    随着Linux的应用日益广泛.特别是在网络应用方面,有大量的网络server使用Linux操作系统.因为Linux的桌面应用和Windows相比另一 定的差距.所以在企业应用中往往是Linux和Win ...

  4. windows phone 7,sliverlight 下载网页的解析,关于wp7 gb2312编码

    原文:windows phone 7,sliverlight 下载网页的解析,关于wp7 gb2312编码 关于silverlight和wp7(windows phone 7)是默认不支持gb2312 ...

  5. Windows phone 8 学习笔记(3) 通信

    原文:Windows phone 8 学习笔记(3) 通信 Windows phone 8 可利用的数据通信方式比较广泛,在硬件支持的前提下,我们可以利用WiFi.蓝牙.临近感应等多种方式.数据交互一 ...

  6. New Hire Training Experience

    Game Description: 1. In a closed door, there will be a circle, and 30 numbers in the circle. 2. Each ...

  7. matlab secant method

    % Matlab script to illustrate the secant method % to solve a nonlinear equation % this particular sc ...

  8. C#语音录制

    客服小妹是如何泡到手的——C#定时提醒·语音录制·语音播放·文件转录Demo——倾情奉献!   一.需求提出 客服小妹跟我说,每天要统计新加好友数,得先记下昨天的数目,然后查看今天的数目,还要相减,打 ...

  9. 基于Office 365 无代码工作流分析-需求基本分析!

     客户需求分析: 嘉昊信息是一家IT创业型公司,因为公司初创,有较多的招聘员工的需求,公司近期购买了Office 365,因为招聘工作繁琐,HR人员须要做非常多反复繁琐工作,HR主管提议开发一个招 ...

  10. 泛泰A870L/K/S第三版官方4.4.2原来的系统卡刷机包 (愿自己主动ROOT)

    采用suky大神ota具,来自官方的拍摄OTA包裹,未做任何改动, 使用官方recovery,还是我的头发中国TWRP 2.7.1.1 for A870 (己主动ROOT. 自己主动ROOT完后就自己 ...