0

After struggling with downcasting (see [my original post]) and with making [deep copies], I found [this eponymous article] where a suggestion is made in C++ as to how to deal with the issue. With much excitement I implemented it in C# as follows:

public partial class User
{
virtual public Employer GetEmployer() { return null; }
...
}

public partial class Employer
{
public override Employer GetEmployer() { return this; }
...
}

which I then used like this:

User u = GetUser();
Employer e = u.GetEmployer();

However (I suppose, not surprisingly), the override is never called and a null is returned.


The problem I'm trying to solve is what I gather would be a very common use-case: I get a bit of data I need to store but it's incomplete. Later I get more data and I use it to refine (downcast) my understanding of the world.

In this particular case, I get an e-mail address from someone using my website so I know they are a User, but in general I don't know anything else about them. Later (when they fill out a form), I know that they are actually an Employer, so I need to downcast my User.

What is the right approach here?

Community
  • 1
  • 1
ekkis
  • 9,804
  • 13
  • 55
  • 105

3 Answers3

1

I answered one of your former questions. You are trying to solve something which cannot be solved. The solution here is not to use inheritance. Why because inheritance is is a relationship. So if you have:

public class User { }

public class Employee : User { }

You have this relations Employeee is User but you don't have reverse relationship User is Employee. But that is exectly what you are trying to do with casting from User to Employee. Instance of User cannot be cast to Employee (except the situation I mentioned in my previous answer - but you don't have this situation).

Use this approach and you will solve it in object oriented way without needs for inheritance casting or what ever else.

public class User 
{
    public virtual EmployeeData { get; set; }
}

public class EmployeeData { }

The approach changes your design from is a to has a relationship. EmployeeData in this case is separate entity mapped in 1 - 0..1 relation (that will result in two tables in the database). You can also use 1 - 1 relation or ComplexType if you are happy with the fact that both User and EmployeeData will be stored in the same table.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • yes you did answer my other question and thank you for that. Your suggestion that I destroy the old object and create a new one seems sensible and logically consistent (it turns out that cloning serialization isn't going to work and the alternative is arbitrarily complex), however it really seems to me that the OO approach must in some more natural way be able to model the real world where entites transmute without a destroy/recreate cycle, hence my current question – ekkis Sep 12 '11 at 00:57
0

The only missing part of your example is that Employer doesn't inherit from User.

Change your declaration to:

public partial class Employer : User
{
    // ...
}

And you should be good to go. I'm not sure I would go this route though. I would probably simply use the as keyword to do my casting safely:

var user = GetUser();
var employer = user as Employer;

// if the cast failed, employer will be null
if(employer != null)
{
    // Work with employer
}
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
  • I'm sorry, `Employer` does inherit from `User` - it's just that what I posted is a partial declaration (because the classes are actually defined by EF). as for the use of `as`, yes I get its use... however, merely casting (with `()` or `as`) isn't sufficient, as you can see from my 2 previous postings... your thoughts? – ekkis Sep 07 '11 at 07:27
0

You should follow the State Pattern:

This is a clean way for an object to partially change its type at runtime.

You can then mix-and-match with the Prototype Pattern:

[Use] a prototypical instance, which is cloned to produce new objects.

You could thus get something like this:

// State pattern: public "wrapper"
public class User {
    UserState state = UserState.CreateUser();

    public void SetIsEmployer ()
    {
        // Use UserState.IsEmployer() method to transition to the
        // Employer state
        state = state.IsEmployer ();
    }

    public User Employer {
        get {return state.Employer.User;}
    }
}

// State pattern: User state
internal class UserState {
    // protected so that only CreateUser() can create instances.
    protected UserState ()
    {
    }

    public User User {
        get {/* TODO */}
    }

    public virtual UserState Employer {
        get {return null;}
    }

    // Creates a default prototype instance
    public static UserState CreateUser ()
    {
         return new UserState ();
    }

    // Prototype-ish method; creates an EmployerState instance
    public virtual UserState IsEmployer ()
    {
        return new EmployerState (/* data to copy...*/);
    }
}

// State pattern: Employer state
class EmployerState : UserState {
    internal EmployeeState ()
    {
    }

    public override UserState Employer {
        get {return this;}
    }
}

If your public User type needs more "state transitions," you need only provide more classes (one for each state) and prototype-ish methods on the UserState type to create the appropriate target state type. User.state is always the current state of the User. This setup allows you to change the apparent runtime type of an instance at runtime.

jonp
  • 13,512
  • 5
  • 45
  • 60
  • very interesting. I don't understand it enough, maybe a couple of answers could help: given a user then, how do I access/cast the employer. I'm going to pull a user out of the database: `User u = db.Users.Find(3)` and will now want to convert it to an employer `u.SetIsEmployer()` but I still can't access employer-specific data e.g. `u.Manager`... Is it maybe? `u.Employer.Manager`? – ekkis Sep 12 '11 at 01:43
  • my second question: the implementations of `IsEmployer and `User.User.get` is where the magic happens, but what do they look like? – ekkis Sep 12 '11 at 01:46
  • one other question (and maybe this belongs as as separate question): since `User` is actually defined by EF and persisted in the database, adding a `state` means storing it. Is there any way I can indicate to EF that this property should be excluded from storage? – ekkis Sep 12 '11 at 03:44
  • incidentally, why do you prefer to provide a static method for creation and to protect the constructure, and therefore do `UserState state = UserState.CreateUser();` instead of `UserState state = new UserState();` ? – ekkis Sep 12 '11 at 21:10
  • as a corollary to the above, if I access `User.Employer` what I have is actually a `User`, but then I'm in the same place where I started: I have a `User` but I need an `Employer` - where am I going wrong here? – ekkis Sep 12 '11 at 22:10