开发者

InvalidCastException: Unable To Cast Objects of type [base] to type [subclass]

I have a custom CustomMembershipUser that inherits from MembershipUser.

public class ConfigMembershipUser : MembershipUser
{
    // custom stuff
}

I am using Linq-to-SQL to read from a database and get a User entity; to make this function as a MembershipUser I have defined an explicit conversion:

public static explicit operator MembershipUser(User user)
{
    DateTime now = DateTime.Now;

    if (user == null) return null;

    return new MembershipUser("MagicMembershipProvider", 
                              user.DisplayName, user.Id, 
                              user.Email, "", "", true, false, 
                              now, now, now, now, now);
}

This cast works fine:

MembershipUser memUser = (MembershipUser) entityUser;

However, the second cast to CustomMembershipUser fails:

MembershipUser memUser = (MembershipUser) entityUser;
CustomMembershipUser custUser = (CustomMembershipUser) memUser;

If I change the cast to

CustomMembershipUser custUser = memUser;

I get an intellisense error telling me an implicit cast won't work but an explicit cast exists.

... and to top it all off, I can't apparently define a cast from a base class to a subclass. I tried it and it failed. The thing I don't understand most of all is why would a cast from a base class to a subclass ever fail? By definition the subclass has all of the properties of the base class, so what's the problem.

EDIT

I tried to define an explicit cast from MembershipUser to CustomMembershipUser (first I defined a private constructor for the cast):

private ConfigMembershipUser(MembershipUser user)
    : base(user.ProviderName, user.UserName, user.ProviderUserKey, user.Email,
           user.PasswordQuestion, user.Comment, user.IsApproved, user.IsLockedOut,
           user.CreationDate, user.LastLoginDate, user.LastActivityDate, 
           user.LastPasswordChangedDate, user.LastLockoutDate)
    {
        // initialize extended CustomMembershipUser stuff here
    }

Then I defined my custom cast:

public static explicit开发者_StackOverflow社区 operator CustomMembershipUser(MembershipUser user)
{
     return new CustomMembershipUser(user);
}

and I got the following error:

'CustomMembershipUser.explicit operator CustomMembershipUser (System.Web.Security.MembershipUser)': user-defined conversions to or from a base class are not allowed.

So... I can't cast from a base class to a subclass?


You've got it in reverse: A cast from an object of a base class to a subclass will always fail, because the base class has only the properties of the the base class (not the subclass).

Since, as you say, the subclass has all the properties of the base class (it "is-a" base-class object), then a cast from the subclass to the base class will always succeed, but never the reverse.

In other words, you can think of all leopards as cats, but you cannot take an arbitrary cat and treat it like a leopard (unless it's already a leopard to begin with).

You need to either return a CustomMembershipUser object instead of a MembershipUser object, or define another explicit cast separate function which converts MembershipUsers to CustomMembershipUsers by making a new CustomMembershipUser object. You cannot get a CustomMembershipUser object out of nowhere; it has be created first, either directly or by instantiating a subclass of CustomMembershipUser (not a base-class).

Edit:

I was wrong about defining an explicit cast to the subclass. This is not possible (as the error you see indicates). You now seem to be in the exact same situation as the asker of this question. Casting is not really the way to go here -- either create CustomMembershipUser objects to begin with (which are usable directly as MembershipUser objects), or write a conversion method which accepts a MembershipUser and creates a CustomMembershipUser.

The only time it makes sense to cast from a base object to a subclass object is when it is already a subclass object (but the variable holding it is of the base class type).


A variable of type MembershipUser can hold an object of type CustomMembershipUser, because the subtype is an instance of the supertype. But the converse is not true.

The CustomMembershipUser could have members that are not on MembershipUser. Therefore a variable of type CustomMembershipUser cannot hold an object of type MembershipUser. Otherwise, the code may try to access one of those member that it does not contain.

This fails:

CustomMembershipUser custUser = memUser; 

because you may follow it up with this:

custUser.CustomStuff();   // Oops! Can't call CustomStuff() on a MembershipUser object!

"Explicit Cast Exists" message

The reason you are getting the "an explicit cast exists" message is not because you have created a cast from User to MembershipUser. (The User type is not involved at all here.) It is because an explicit cast always exists from a supertype to subtype. That is part of the language design. This is to support the scenerio in which you know that the object is of the subtype and you want to use a variable that matches. But if you use that explicit cast on an object that is not of the target type, then you get a runtime error (as you have experienced).

Further explaination about why the cast fails

In C# every object has a type. That type can never be changed for the lifetime of the object. Once you create an Employee (for example), it will always be an Employee forever and ever, or until garbage collection, amen.

public class Person
{
    public string Name {get; private set;}
    public Person(string name)
    {  Name = name; }
}
public class Employee : Person
{
    public DateTime HireDate {get; private set;}
    public Employee(string name, DateTime hireDate)
        : base (name)
    {    HireDate = hireDate;  }
}

If you have a variable of type Person, then that variable can hold an Employee object, because an Employee is a Person.

Employee mike = new Employee("Michael", DateTime.Now);
Person myBestBud = mike;

This is an imlicit cast, because it always works. A Person variable can always hold an Employee object. The reason for this, is because the system knows that every member of Person that it tries to use is going to be available, because of inheritence.

Console.WriteLine("Dude's name: " + myBestBud.Name);

Now, let's try it the other way.

Person johnny = new Person("Johnny Johnson");
Employee newHire = johnny;  // ERROR - Attempt to assign...etc.  An explicit cast is available...

This causes an error. There is no implicit cast from Person to Employee, because the compiler cannot guaruntee that a Person variable contains an Employee object. So that causes a compile-time error. So, let's try the explicit cast.

Employee newHire = (Employee)johnny;

This will compile just fine. This is allowed by the compiler, because sometimes a Person variable will hold an Employee object. But this will fail at runtime. The reason this will fail, is because the variable johnny does not have an employee, so it cannot be treated like one. So a invalid cast exception is thrown.

If it didn't throw an invalid cast exception, then we could try to do something like this:

Console.WriteLine("Hired on: " + newHire.HireDate);

But the property doesn't exist, because the object is really a Person, not an Employee.

So you can see that there is an implict cast from subtype to supertype, because that always succeeds and causes no problems. There is an explicit cast from supertype to subtype, because that only works if the runtime type of the object is assignment compatible with the variable. The programmer is expected to know when it works and when it doesn't, and only do the cast when it will work. Otherwise, the runtime will detect the invalid cast and throw an exception.

Now sometimes a user can create a custom conversion operator that can be used to cast from one type to another. When that happens, then an entirely new object is created of the target type. However, this cannot be done up or down an inheritence hierarchy, because the casts for those are already supplied by the C# compiler. In order to do a custom conversion operator, the source or target type must not be an ancestor or decendent of the other type.


It's possible to make a cast, however you need to de-proxy the object, the easiest way to do that is to create a call to itself that returns the same object, as described here: Issue with Casting proxies when using NHibernate table per subclass inheritance strategy

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜