LINQ之路(3):LINQ扩展
本篇文章将从三个方面来进行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扩展的更多相关文章
- LINQ之路 4:LINQ方法语法
书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression). LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方 ...
- LINQ之路(2):LINQ to SQL本质
LINQ之路(2):LINQ to SQL本质 在前面一篇文章中回顾了LINQ基本语法规则,在本文将介绍LINQ to SQL的本质.LINQ to SQL是microsoft针对SQL Server ...
- LINQ之路系列文章导读
本系列文章将会分为3篇来进行阐述,如下: LINQ之路(1):LINQ基础 LINQ之路(2):LINQ to SQL本质 LINQ之路(3):LINQ扩展
- LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法
本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators).Zip操作符.转换方法(Conversion Methods).生成器方法(Generation M ...
- LINQ之路 7:子查询、创建策略和数据转换
在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍 ...
- [转]LINQ之路系列博客导航
分享一个学习Linq的好博客:Linq之路
- LINQ学习系列-----1.3 扩展方法
这篇内容继续接着昨天的Lambda表达式的源码继续下去.昨天讲了Lambda表达式,此篇讲扩展方法,这两点都是Linq带来的新特性. 一.扩展方法介绍 废话不多说,先上源码截图: 上图中Ge ...
- LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法
本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...
- LINQ之路10:LINQ to SQL 和 Entity Framework(下)
在本篇中,我们将接着上一篇“LINQ to SQL 和 Entity Framework(上)”的内容,继续使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术 ...
随机推荐
- Android四个多线程分析:MessageQueue实现
Android四个多线程分析:MessageQueue的实现 罗朝辉 (http://blog.csdn.net/kesalin) CC 许可,转载请注明出处 在前面两篇文章<Android多线 ...
- 共享库方案解决WAS中JAR包冲突
实现步骤: 1. 准备共享库JAR包 commons-httpclient-3.1.jar httpclient-4.3.3.jar httpcore-4.3.2.jar httpmim ...
- 有关信息ACM/ICPC竞争环境GCC/G++叠插件研究记录的扩展
0.起因 有时.DFS总是比BFS受人喜爱--毕竟DFS简单粗暴,更,而有些东西BFS不要启动,DFS它似乎是一个可行的选择-- 但是有一个问题,DFS默认直接写入到系统堆栈.系统堆栈和足够浅,此时O ...
- Ajax—初识
看DRP的过程.又一次学习了一遍Ajax.更深刻的体会到了Ajax所具有的魅力.Ajax是一种技术或者方法,更是一 种艺术.它让我们的程序以一种更完美的姿态呈如今用户面前.以下就让我们一起走进Ajax ...
- 自己动手写CPU之第六阶段(2)——移动操作指令实现思路
将陆续上传本人写的新书<自己动手写CPU>(尚未出版),今天是第21篇,我尽量每周四篇 6.2 移动操作指令实现思路 6.2.1 实现思路 这6条移动操作指令能够分为两类:一类是不涉及特殊 ...
- HibernateReview Day2–Hibernate体系结构
本文摘自 李刚 著 <Java EE企业应用实战> 现在我们知道了一个概念Hibernate Session,只有处于Session管理下的POJO才具有持久化操作能力.当应用程序对于处于 ...
- 【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对照度
本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...
- Date()创建日期
哎. .这些人变得懒惰个月...经过以上撰写博客的工作 上周五测试报告的问题.上述日期的计算是有问题.两个日期之间的差异原创1天,然而,系统将计算的差31天. (就在那一天2014年10月31日本 星 ...
- BZOJ 2002 HNOI2010 弹飞羊 块
标题效果,LCT解释版本:见 http://blog.csdn.net/popoqqq/article/details/38849471 如今,用一只手滑动块,并再次改写这个问题0.0 块短啊 将进入 ...
- C语言 cgi(3)
1cs3157 – Advanced ProgrammingSummer 2014, Project 1, 150 pointsJune 17, 2014Follow these step-by-st ...