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.

Fowl
  • 4,940
  • 2
  • 26
  • 43

2 Answers2

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.

AaronLS
  • 37,329
  • 20
  • 143
  • 202
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));
}
user1793607
  • 531
  • 2
  • 6
  • How does that help? Is still means you have the same code twice. – svick Dec 19 '12 at 15:01
  • @svick 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 solution – user1793607 Dec 19 '12 at 15:08
  • 2
    But I believe the underlying reason for doing that is to avoid code duplication. – svick Dec 19 '12 at 15:24