Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Taking advantage of PLINQ with custom Enumerable Extensions

Tags:

c#

linq

plinq

Many custom Enumerable extensions can be implemented in terms of other builtin operations - for example this trivial convenience method:

public static bool AnyOf<TElement>(this TElement item, IEnumerable<TElement> items)
{
    return items.Any(a => EqualityComparer<TElement>.Default.Equals(a, item));
}

Now this will force any PLINQ query back to sequential operation even though PLINQ also has a Any - and is equivalent with just just a signature change:

public static bool AnyOf<T>(this T item, ParallelQuery<T> items)
{
    return items.Any(a => EqualityComparer<T>.Default.Equals(a, item));
}

But duplicating it like this seems messy to me.

At first I thought something like the below might work, but of course it does not^ because extension methods are static methods and therefore the decision to call Enumerable.Any as opposed to ParallelQuery.Any is made at compile time based on signature.

public static bool AnyOf<TElement, TEnumerable>(this TElement item, TEnumerable items)
    where TEnumerable : class, IEnumerable<TElement>
{
    return items.Any(a => EqualityComparer<TElement>.Default.Equals(a, item));
}

I've come to the conclusion it's impossible without creating a copy of each method with a different signature, but maybe there's something I've missed. (Gee always with the impossible questions!)


Perhaps a better example of a helper that would benefit from parallelization (can obviously be chained, etc. ) is something like this.

public static IEnumerable<string> ToStrings(this IEnumerable<object> ienum)
{
    return ienum.Select(a=> a.ToString());
}

^ Compiler error:

 The type 'ParallelQuery<TElement>' cannot be used as type parameter
 'TEnumerable' in the generic type or method
 'AnyOf<TElement,TEnumerable>(TElement, TEnumerable)'. There is no
 implicit reference conversion from 'ParallelQuery<TElement>' to
 'IEnumerable<TElement>'

Also worth considering is that not all of the ParallelQuery/Enumerable methods are equivalent, even if they do compile.

like image 923
Fowl Avatar asked Dec 19 '12 01:12

Fowl


2 Answers

I have done similar for writing IQueryable/IEnumerable extensions. Trying to factor out the common bits involved declaring static variable holding an Expression, and then referencing that expression from the two different versions of the function. I don't have the code anymore, and when I was done, it was very ugly and I wasn't satisfied with it. Here is a trivial example.

Expression<Func<PersonHistory, bool>> IsCurrent = (p) => p.Ends > DateTime.Now && p.Starts <= DateTime.Now;

//Then in each Extension method:
var query = db.PersonHistories.Where(IsCurrent);

Ultimately the level of de-duplication was not good at all, and would be made more complicated by generic parameters. Maybe this will give you an idea though.

Looking forward to seeing others ideas.

like image 69
AaronLS Avatar answered Oct 02 '22 14:10

AaronLS


You could do this by using checked casting inside the method (i.e. runtime switching) like so:

public static bool AnyOf<TElement>(this TElement item, IEnumerable<TElement> items)
{
    var parallelItems = items as ParallelQuery<TElement>
    if(parallelItems != null)
    {
         return parallelItems.Any(a => EqualityComparer<TElement>.Default.Equals(a, item))
    }
    //other runtime checks
    ....
    //else return default IEnumerable implementation
    return items.Any(a => EqualityComparer<TElement>.Default.Equals(a, item));
}
like image 32
user1793607 Avatar answered Oct 02 '22 15:10

user1793607