FilteredCollection.cs:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace TestExtendingIQueryable
{
//NOTE: This is a proof of concept – not designed to be production code
public class RandomThing
{
public int SomeVal;
public RandomThing(int x) { SomeVal = x; }
}
public class CollectionExtendingIQueryable<T> : ICollection<T>, IQueryable<T>
{
public CollectionExtendingIQueryable()
{ _internalList = new List<T>(); _ex = System.Linq.Expressions.Expression.Constant(this); }
protected Expression _ex;
protected List<T> _internalList;
internal List<T> UnderlyingList { get { return _internalList; } }
public void RemoveBottomItem()
{
_internalList.RemoveAt(0);
}
public void Add(T item)
{
_internalList.Add(item);
}
public int FilteredCount
{
get
{
int cnt = 0;
foreach (T item in this)
cnt++;
return cnt;
}
}
public int UnfilteredCount
{
get
{
int cnt = 0;
foreach (T item in _internalList)
cnt++;
return cnt;
}
}
#region IQueryable<T> Members
IQueryable<TElement> IQueryable<T>.CreateQuery<TElement>(Expression expression)
{
MethodCallExpression mex = expression as MethodCallExpression;
switch(mex.Method.Name)
{
case "Where":
return (IQueryable<TElement>) new ViewOnCollectionExtendingIQueryable<T>(expression, this);
case "Select":
UnaryExpression selectHolder = mex.Arguments[1] as UnaryExpression;
LambdaExpression theSelect = selectHolder.Operand as LambdaExpression;
Expression<Func<T, TElement>> selectorLambda
= Expression.Lambda<Func<T, TElement>>(theSelect.Body,theSelect.Parameters);
Func<T, TElement> selector = selectorLambda.Compile();
return this.Select<T, TElement>(selector).AsQueryable<TElement>();
default:
return null;
}
}
TResult IQueryable<T>.Execute<TResult>(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
MethodCallExpression mex = _ex as MethodCallExpression;
UnaryExpression whereHolder = mex.Arguments[1] as UnaryExpression;
LambdaExpression theWhere = whereHolder.Operand as LambdaExpression;
Expression<Func<T, bool>> theParmedWhere
= Expression.Lambda<Func<T, bool>>(theWhere.Body, theWhere.Parameters);
Func<T, bool> filter = theParmedWhere.Compile();
//if we had indexes in this collection, they would be used here
foreach (T item in _internalList)
if (filter(item))
yield return item;
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
MethodCallExpression mex = _ex as MethodCallExpression;
UnaryExpression whereHolder = mex.Arguments[1] as UnaryExpression;
LambdaExpression theWhere = whereHolder.Operand as LambdaExpression;
Expression<Func<T, bool>> theParmedWhere = Expression.Lambda<Func<T, bool>>(theWhere.Body, theWhere.Parameters);
Func<T, bool> filter = theParmedWhere.Compile();
foreach (T item in this)
if (filter(item))
yield return item;
}
#endregion
#region IQueryable Members
IQueryable IQueryable.CreateQuery(Expression expression)
{
_ex = expression;
return this;
}
Type IQueryable.ElementType
{
get { return typeof(T); }
}
object IQueryable.Execute(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}
Expression IQueryable.Expression
{
get { return _ex; }
}
#endregion
#region ICollection<T> Members
void ICollection<T>.Add(T item)
{
_internalList.Add(item);
}
void ICollection<T>.Clear()
{
_internalList.Clear();
}
bool ICollection<T>.Contains(T item)
{
return _internalList.Contains(item);
}
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
{
_internalList.CopyTo(array,arrayIndex);
}
int ICollection<T>.Count
{
get { return _internalList.Count; }
}
bool ICollection<T>.IsReadOnly
{
get { return false; }
}
bool ICollection<T>.Remove(T item)
{
return(_internalList.Remove(item));
}
#endregion
}
public class ViewOnCollectionExtendingIQueryable<T> : CollectionExtendingIQueryable<T>, IQueryable<T>
{
protected Expression _specificEx;
public ViewOnCollectionExtendingIQueryable(Expression ex, CollectionExtendingIQueryable<T> baseCollection)
{
_internalList = baseCollection.UnderlyingList;
_specificEx = ex;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
MethodCallExpression mex = _specificEx as MethodCallExpression;
UnaryExpression whereHolder = mex.Arguments[1] as UnaryExpression;
LambdaExpression theWhere = whereHolder.Operand as LambdaExpression;
Expression<Func<T, bool>> theParmedWhere
= Expression.Lambda<Func<T, bool>>(theWhere.Body, theWhere.Parameters);
Func<T, bool> filter = theParmedWhere.Compile();
//if we had indexes in this collection, they would be used here
foreach (T item in _internalList)
if (filter(item))
yield return item;
}
#region IQueryable<T> Members
IQueryable<TElement> IQueryable<T>.CreateQuery<TElement>(Expression expression)
{
MethodCallExpression mex = expression as MethodCallExpression;
switch (mex.Method.Name)
{
case "Where":
_specificEx = expression;
return (IQueryable<TElement>) this;
case "Select":
UnaryExpression selectHolder = mex.Arguments[1] as UnaryExpression;
LambdaExpression theSelect = selectHolder.Operand as LambdaExpression;
Expression<Func<T, TElement>> selectorLambda
= Expression.Lambda<Func<T, TElement>>(theSelect.Body, theSelect.Parameters);
Func<T, TElement> selector = selectorLambda.Compile();
return this.Select<T, TElement>(selector).AsQueryable<TElement>();
default:
return null;
}
}
TResult IQueryable<T>.Execute<TResult>(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
#region IQueryable Members
IQueryable IQueryable.CreateQuery(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}
Type IQueryable.ElementType
{
get { return typeof(T); }
}
object IQueryable.Execute(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}
Expression IQueryable.Expression
{
get { return _specificEx; }
}
#endregion
}
}
Program.cs:
using System;
using System.Linq;
using System.Collections.Generic;
namespace TestExtendingIQueryable
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Demonstration of using LINQ to generate filtered results, using a");
Console.WriteLine("collection class specifically designed to filter rather than project");
Console.WriteLine("by default.");
Console.WriteLine("");
Console.WriteLine("We will generate a collection with 100 random numbers, between 1 and 300");
Console.WriteLine("We will then generate two views on the numbers, one for those below 100,");
Console.WriteLine("the other for those below 200. Removing the bottom most item, which is");
Console.WriteLine("the only item that wont be random (fixed at 42, to fit in both ranges)");
Console.WriteLine("will affect the count of all three collections (original, filteredview,");
Console.WriteLine("and different filtered view.");
Console.WriteLine("");
Console.WriteLine("Lastly, we will do a typical projection, which will demonstrate that");
Console.WriteLine("the filtering logic gets out of the way when you select something that");
Console.WriteLine("is not amenable to filtering.");
CollectionExtendingIQueryable<RandomThing> random = new CollectionExtendingIQueryable<RandomThing>();
Random rnd = new Random();
random.Add(new RandomThing(42)); //first one has to be under 100 to run our removal tests correctly
for (int i = 0; i < 99; i++)
random.Add(new RandomThing(rnd.Next(300)));
var filteredResult = from r in random
where r.SomeVal < 100
select r;
var differentFilteredResult = from r in random
where r.SomeVal < 200
select r;
Console.WriteLine("Filtered results (random numbers under 100)");
foreach (var x in filteredResult)
Console.Write(x.SomeVal + ",");
Console.WriteLine("");
Console.WriteLine("———————————-");
Console.WriteLine("Filtered result Count = " + ((CollectionExtendingIQueryable<RandomThing>)filteredResult).FilteredCount);
Console.WriteLine("Different filtered result Count = " + ((CollectionExtendingIQueryable<RandomThing>)differentFilteredResult).FilteredCount);
Console.WriteLine("Now we are going to remove the bottom item from the first filtered result, which should reduce the count of all filtered results by one");
Console.WriteLine("Press any key to continue…");
Console.ReadKey();
Console.WriteLine("Count of original collection = " + random.UnfilteredCount);
((CollectionExtendingIQueryable<RandomThing>)filteredResult).RemoveBottomItem();
Console.WriteLine("Count of original collection after removal from filtered list = " + random.UnfilteredCount);
Console.WriteLine("Count in the filtered list = " + ((CollectionExtendingIQueryable<RandomThing>)filteredResult).FilteredCount);
Console.WriteLine("Count in the different filtered result = " + ((CollectionExtendingIQueryable<RandomThing>)differentFilteredResult).FilteredCount);
Console.WriteLine("Press any key to test projection…");
Console.ReadKey();
var projectedResult = from r in random
where r.SomeVal < 100
select r.SomeVal;
foreach (var x in projectedResult)
Console.Write(x + ",");
Console.WriteLine("");
Console.WriteLine("———————");
Console.WriteLine("Press any key to exit the demo…");
Console.ReadKey();
}
}
}