Question

This is more a question out of curiosity than necessity and came about having had to deal with Active Directory (MS) types such as SearchResultCollection (in the System.DirectoryServices namespace).

Frequently when dealing with AD in code, I find that I'm having to check values for null, Count, [0] .. whatever and convert what I get out .. all the while hoping that the underlying AD object via COM doesn't go poof etc.

After having a play about with Parallel.ForEach recently - and having to pass in an IEnumerable<T>, I thought, maybe it would be fun to see how I could cast a SearchResultCollection to an IEnumerable of my own custom type. In this type I would pull out all the values from the SearchResult object and stick them in my own, .NET managed code. Then I'd do away with the DirectoryEntry, DirectorySearcher etc. etc.

So, having already worked out that it's a good idea to do searchResultCollection.Cast() in order to supply Parallel.ForEach with it's source, I added an explicit operator for casting to my own type (let's just call it 'Item').

I tested this out within the ParallelForEach, var myItem = (Item)currentSearchResult.

Good times, my cast operator is called and it all works. I then thought, it would be nice to do something like searchResultCollection.Cast<Item>(). Sadly this didn't work, didn't hit any breakpoints in the cast operator.

I did some Googling and discovered a helpful post which Jon Skeet had answered:

IEnumerable.Cast<>

The crux of it, use .Select(...) to force the explicit cast operation. OK, but, hmmm.

I went off and perhaps disassembled System.Core -> System.Linq.Enumerable.Cast<TResult>, I noticed that this 'cast' is actually doing an 'as' keyword conversion under the hood:

public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
    IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
    if (enumerable != null)
    {
        return enumerable;
    }
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    return CastIterator<TResult>(source);
}

I read some more and found this:

Implicit/Explicit conversion with respect to the "as" keyword

The top answer here states that 'as' doesn't invoke any conversion operators .. use a (cast). Semantically I find this a little weird, since the extension method is called cast.. Shouldn't it be casting? There will no doubt be a really good reason why this doesn't happen, anyone know what it is?

Was it helpful?

Solution

Even if it would have used the cast operator instead of as it still wouldn't be invoking user defined explicit operators, so it wouldn't matter. The only difference would be the type of exception thrown.

Explicit operators aren't known at all by the runtime. According to the CLR there is no way to cast your search result to Item. When the compiler notices that there is a cast that matches a given explicit operator it injects at compile time a call to that explicit operator (which is basically a static method) into the code. Once you get to runtime there is no remaining knowledge of the cast, there is simply a method call in place to handle the conversion.

Because this is how explicit operators are implemented, rather than providing knowledge to the runtime of how to do the conversion, there is no way for Cast to inject the explicit operator's call into the code. It's already been compiled. When it was compiled there was no knowledge of any explicit operator to inject, so none was injected.

OTHER TIPS

Semantically I find this a little weird, since the extension method is called cast.. Shouldn't it be casting?

It's casting each element if it needs to, within CastIterator... although using a generic cast, which won't use explicit conversion operator you've defined anyway. You should think of the explicit conversion operator as a custom method with syntactic sugar over the top, and not something the CLR cares about in most cases.

For for the as operator: that's just used to say "If this is already a sequence of the right type, we can just return." It's used on the sequence as a whole, not each element.

This can actually cause problems in some slightly bizarre situations where the C# conversion and the CLR conversions aren't aligned, although I can't remember the example I first came upon immediately.

See the Cast/OfType post within Edulinq for more details.

If I understand correctly you need a DynamicCast which I wrote sometime ago.

Runtime doesn't know about implicit and explicit casting; it is the job of the compiler to do that. but using Enumerable.Cast<> you can't get that because Enumerable.Cast involves casting from System.Object to Item where there is no conversion available(you have conversion from X to Item, and not Object to Item)

Take the advantage of dynamic in .Net4.0

public static class DynamicEnumerable
{
    public static IEnumerable<T> DynamicCast<T>(this IEnumerable source)
    {
        foreach (dynamic current in source)
        {
            yield return (T)(current);
        }
    }
}

Use it as

var result = collection.DynamicCast<Item>();

It is casting. See the implementation of CastIterator.

static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source) {
    foreach (object obj in source) yield return (TResult)obj; 
} 

The use of the as keyword here is only to check if the entire collection can be casted to your target instead of casting item by item. If the as returns something that isn't null, then the entire collection is returned and we skip the whole iteration process to cast every item.

For example this trivial example would return a casted collection instead of iterating over every item

int?[] collection = ...;
var castedCollection = collection.Cast<int?>()

because the as works right off the bat, no need to iterate over every item.

In this example, the as gives a null result and we have to use the CastIterator to go over every object

int?[] collection = ...;
var castedCollection = collection.Cast<object>()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top