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来实践“解释查询”,学习这些技术 ...
随机推荐
- 《Java并发编程实战》第七章 取消与关闭 读书笔记
Java没有提供不论什么机制来安全地(抢占式方法)终止线程,尽管Thread.stop和suspend等方法提供了这种机制,可是因为存在着一些严重的缺陷,因此应该避免使用. 但它提供了中断In ...
- 如何用C#使用java
如何使用C#调用Java 今天需要使用C#调用Java的包,研究了一下,大体是以下几种解决方案: 把Java包转换为DLL或者EXE后注册为com组件,之后调用. 使用web service 比如:H ...
- linuxserver启动过程
随着Linux的应用日益广泛.特别是在网络应用方面,有大量的网络server使用Linux操作系统.因为Linux的桌面应用和Windows相比另一 定的差距.所以在企业应用中往往是Linux和Win ...
- windows phone 7,sliverlight 下载网页的解析,关于wp7 gb2312编码
原文:windows phone 7,sliverlight 下载网页的解析,关于wp7 gb2312编码 关于silverlight和wp7(windows phone 7)是默认不支持gb2312 ...
- Windows phone 8 学习笔记(3) 通信
原文:Windows phone 8 学习笔记(3) 通信 Windows phone 8 可利用的数据通信方式比较广泛,在硬件支持的前提下,我们可以利用WiFi.蓝牙.临近感应等多种方式.数据交互一 ...
- New Hire Training Experience
Game Description: 1. In a closed door, there will be a circle, and 30 numbers in the circle. 2. Each ...
- matlab secant method
% Matlab script to illustrate the secant method % to solve a nonlinear equation % this particular sc ...
- C#语音录制
客服小妹是如何泡到手的——C#定时提醒·语音录制·语音播放·文件转录Demo——倾情奉献! 一.需求提出 客服小妹跟我说,每天要统计新加好友数,得先记下昨天的数目,然后查看今天的数目,还要相减,打 ...
- 基于Office 365 无代码工作流分析-需求基本分析!
客户需求分析: 嘉昊信息是一家IT创业型公司,因为公司初创,有较多的招聘员工的需求,公司近期购买了Office 365,因为招聘工作繁琐,HR人员须要做非常多反复繁琐工作,HR主管提议开发一个招 ...
- 泛泰A870L/K/S第三版官方4.4.2原来的系统卡刷机包 (愿自己主动ROOT)
采用suky大神ota具,来自官方的拍摄OTA包裹,未做任何改动, 使用官方recovery,还是我的头发中国TWRP 2.7.1.1 for A870 (己主动ROOT. 自己主动ROOT完后就自己 ...