Taking advantage of PLINQ with custom Enumerable Extensions
Asked Answered
A

2

7

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.

Again answered 19/12, 2012 at 1:56 Comment(3)
what is the actual question?Gran
Umm, err. got a bit carried away: "Is there any way of writing a LINQ extension that also works with PLINQ?".Again
Are all your extension methods this short? How many of them do you have?Loutitia
S
1

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.

Sejant answered 19/12, 2012 at 2:53 Comment(0)
M
1

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));
}
Multiply answered 19/12, 2012 at 14:54 Comment(3)
How does that help? Is still means you have the same code twice.Loutitia
@Loutitia ala 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 & "Is there any way of writing a LINQ extension that also works with PLINQ?" his actual question is not writing it twice but having a single signature that will be clever depending on the enumeration behaviour, for which this is a solutionMultiply
But I believe the underlying reason for doing that is to avoid code duplication.Loutitia

© 2022 - 2024 — McMap. All rights reserved.