Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

.net

casting

I'm trying to do some unit testing on a project that unfortunately has high level of unit interdependence. Currently, a lot of our classes look to a custom UserIdentity object to determine authentication, but that object has a lot of internal hoop-jumping that I would just as soon avoid when trying to test individual unit functionality.

To work around some of this, I'm trying to create a "mock" version of this UserIdentity that can be plugged in with a more tightly-controlled variable environment.

Long story short, we have a UserIdentity class with several public read-only properties and a static CurrentIdentity (IIdentity) placeholder. I'm able work around just about everything with a "mock" IIdentity implementation, but I'm running into a wall when I reach a point where the CurrentIdentity is cast as a UserIdentity.

It's a pretty straight-forward method:

internal static UserIdentity GetCurrentIdentity()
{
    UserIdentity currentIdentity = ApplicationContext.User.Identity as UserIdentity;
    return currentIdentity;
}

I've set up my mock object to create a member of the UserIdentity type, and then do something like this:

    public static implicit operator UserIdentity(MockUserIdentity src)
    {
        return src.UserIdentity;
    }

or this

    public static explicit operator UserIdentity(MockUserIdentity src)
    {
        return src.UserIdentity;
    }

The problem is that as far as I can tell, that 'as' doesn't seem to invoke either an implicit or explicit conversion operation on my mock object. My question(s) is(are?), am I missing something simple here or will this not work because (I'm guessing) the 'as' operation looks directly to class inheritance (which my object does not do...)?

Also, a bit off topic maybe, but why can there not be simultaneous explicit and implicit operators of the same resultant type within a class? Unless I'm missing something silly, the compiler balks if I try to have both conversion operators at once. I have to pick one or the other.

UPDATE

Okay, now I'm thoroughly confused. Maybe I'm getting sloppy, but I've tried doing the direct cast, and I can't seem to get that to work either. I read up on the operator at MSDN, and the example shows the operator going in the resultant class rather than the source class, but I'm not sure if that matters or not (I tried both places in the code below). Either way, I tried to set up a simple test bed to see what I might be doing wrong, but I can't get that to work either...Here's what I've got

class Program
{
    // Shared Interface
    public interface IIdentity { }

    // "real" class (not conducive to inheritence)
    public class CoreIdentity : IIdentity
    {
        internal CoreIdentity() { }

        // Just in case (if this has to be here, that seems unfortunate)
        public static explicit operator CoreIdentity(ExtendedIdentity src)
        {
            return src.Identity;
        }
    }

    // "mock" class (Wraps core object)
    public class ExtendedIdentity : IIdentity
    {
        public CoreIdentity Identity { get; set; }
        public ExtendedIdentity()
        {
            Identity = new CoreIdentity();
        }

        // This is where the operator seems like it should belong...
        public static explicit operator CoreIdentity(ExtendedIdentity src)
        {
            return src.Identity;
        }
    }

    // Dummy class to obtain "current core identity"
    public class Foo
    {
        public IIdentity Identity { get; set; }
        public CoreIdentity GetCoreIdentity()
        {
            return (CoreIdentity)Identity;
        }
    }

    static void Main(string[] args)
    {
        ExtendedIdentity identity = new ExtendedIdentity();
        Foo foo = new Foo();
        foo.Identity = identity;
        CoreIdentity core = foo.GetCoreIdentity();
    }
}

But that throws the following exception when I invoke foo.GetCoreIdentity():

Unable to cast object of type 'ExtendedIdentity' to type 'CoreIdentity'.

and I can't catch either of my explicit operators with a break point, so it looks like it's making this determination without even "trying" the conversion routes I've provided.

Surely I'm missing something obvious. Does the fact that I have my Identity (in Foo) defined as IIdentity somehow prevent resolution of the cast using the explicit operators of the implementing type? That would strike me as odd.

UPDATE (#2)

I feel like I'm spamming my post with all these updates (maybe I should get my act together before being so trigger-happy :) ) Anyway, I modified my Foo's GetCoreIdentityMethod to do this instead:

public CoreIdentity GetCoreIdentity()
{
    ExtendedIdentity exId = Identity as ExtendedIdentity;
    if (exId != null)
        return (CoreIdentity)exId;

    return (CoreIdentity)Identity;
}

and (after having to clean up the ambiguous reference caused by having the operator in both classes), it did step into my explicit conversion operator code, and it did work as expected. So I guess it looks like the explicit operators are not resolved polymorphically (is that the correct understanding?), and the fact that my property was typed as an IIdentity rather than an ExtendedIdentity prevented it from invoking the cast logic even though it was of the ExtendedIdentity type at the time it was invoked. That strikes me as very peculiar and unexpected....and kind of unfortunate.

I don't want to have to re-write the keeper of the CurrentIdentity object to make it aware of my special test cast mocks. I wanted to encapsulate that "special" logic into the mock itself, so this really throws me for a loop.

like image 393
Steven Avatar asked Jan 21 '23 07:01

Steven


2 Answers

Does the fact that I have my Identity (in Foo) defined as IIdentity somehow prevent resolution of the cast using the explicit operators of the implementing type?

Here's a hint: how do you define an explicit (or implicit, for that matter) conversion operator? (I know you know this since you already did it; I am asking the question to illustrate a point.)

public static explicit operator UserIdentity(MockUserIdentity src)
{
    return src.UserIdentity;
}

There's something very important to realize here. The C# designers made the wise choice of making all operators static. So the explicit operator defined above translates to essentially a static method call looking something like this:

public static UserIdentity op_Explicit(MockUserIdentity src)
{
    return src.UserIdentity;
}

Now, here is what I'm getting at. The behavior that perplexed you in your question because it seemed to fail in the polymorphism department was really the result of C#'s system of method overload resolution.

If I have two methods:

void Write(string s) { Console.WriteLine("string"); }
void Write(object o) { Console.WriteLine("object"); }

...and then I have this program:

object x = "Hello!";
Write(x);

What will the output be?

The answer is "object" because the Write(object) overload was selected by the compiler -- as well it should have been. Write is not an instance method to be overridden by some derived type according to normal polymorphism; it is a static method, with overloads between which the compiler must make a choice. Since x in the above code is declared to be of type object, that choice is unambiguously Write(object).

So in the case of your code, where you have this:

public IIdentity Identity { get; set; }
public CoreIdentity GetCoreIdentity()
{
    return (CoreIdentity)Identity;
}

The compiler must investigate: is there an op_Explicit overload which accepts an IIdentity parameter? No, there is not. There's one that accepts a UserIdentity parameter, but that's too specific (just as Write(string) was too specific for x in the example above).

So the reason your explicit operator was not called in your initial tests was that the compiler will not resolve (CoreIdentity)Identity to that particular overload. This is also why your modified version does work:

public CoreIdentity GetCoreIdentity()
{
    ExtendedIdentity exId = Identity as ExtendedIdentity;

    if (exId != null)
    {
        // Since exId is actually declared to be of type ExtendedIdentity,
        // the compiler can choose the operator overload accepting
        // an ExtendedIdentity parameter -- so this will work.
        return (CoreIdentity)exId;
    }

    return (CoreIdentity)Identity;
}
like image 185
Dan Tao Avatar answered May 14 '23 01:05

Dan Tao


as does not invoke conversion operators. See: http://msdn.microsoft.com/en-us/library/cscsdfbt(v=VS.100).aspx

Use a (cast).

like image 38
Ray Henry Avatar answered May 14 '23 03:05

Ray Henry