Downcasting failing from common interface/abstract class

118 views Asked by At

I have two classes - Expert and Farmer, that both share some common characteristics and thus implement an abstract class User. User implements an interface IUser. In my application initially common fields have been filled out but then the user indicates whether they are an Expert/Farmer.

At this point in time, I want to cast my current object into either an Expert or a Farmer to fill in the individually unique fields. My current approach is to declare IUser user = new Farmer(); and then try and cast appropriately at the appropriate time: Farmer farmer = user as Farmer OR Expert expert = user as Expert. However, the cast to an Expert always fails, although they both implement the same interface and abstract class.

  1. Why is this cast failing?
  2. What is the correct way to solve this issue neatly using inheritance? Obviously one could create functions createUser() or createFarmer() defined in the User abstract class or just instantiate a User and a Farmer and fill the initial fields in both, taking whichever instance is required at the split time, but neither of these are particularly elegant.

Definition of Expert:

public partial class Expert : User
    {

        public override Guid Id { get; set; }
        [StringLength(200)]
        public override string Name { get; set; }
        [Key]
        [StringLength(10)]
        public override string phone { get; set; }
        [StringLength(50)]
        public override string state { get; set; }
        [StringLength(50)]
        public override string district { get; set; }
        [StringLength(6)]
        public override string PIN { get; set; }
        public override double geolat { get; set; }
        public override double geolong { get; set; }
        public override int v { get; set; }
        public override int h { get; set; }
        public override string AzureImageURL { get; set; }

        //IParty properties
        public override string ServiceUrl { get; set; }
        public override string ChannelId { get; set; }
        public override string PartyChannelAccountId { get; set; }
        public override string PartyChannelAccountName { get; set; }
        public override string BotChannelAccountId { get; set; }
        public override string BotChannelAccountName { get; set; }
        public override string ConversationAccountId { get; set; }
        public override string ConversationAccountName { get; set; }


        //Expert specific properties
        public double price { get; set; }
        public int totalRating { get; set; }
        public int numberOfRating { get; set; }
        public string description { get; set; }
        public string languages { get; set; }
        double _sortIndex = double.Nan
    }

Definition of Farmer:

public partial class Farmer : User, IVisualizable
    {
        //User override fields
        [Key]
        public override Guid Id { get; set; }
        [StringLength(200)]
        public override string Name { get; set; }
        [StringLength(10)]
        public override string phone { get; set; }
        [StringLength(50)]
        public override string state { get; set; }
        [StringLength(50)]
        public override string district { get; set; }
        [StringLength(6)]
        public override string PIN { get; set; }
        public override double geolat { get; set; }
        public override double geolong { get; set; }
        public override int v { get; set; }
        public override int h { get; set; }
        public override string AzureImageURL { get; set; }

        //IParty properties
        public override string ServiceUrl { get; set; }
        public override string ChannelId { get; set; }
        public override string PartyChannelAccountId { get; set; }
        public override string PartyChannelAccountName { get; set; }
        public override string BotChannelAccountId { get; set; }
        public override string BotChannelAccountName { get; set; }
        public override string ConversationAccountId { get; set; }
        public override string ConversationAccountName { get; set; }

        //Farmer specific properties
        [StringLength(12)]
        public string AadhaarNum { get; set; }
        public int cropId { get; set; }
        [StringLength(1)]
        public string acreage { get; set; }

        /// <summary>
        /// Constructor calling base - sets Guid of farmer
        /// </summary>
        public Farmer() : base()
        {

        }

    }

Definition of User:

public abstract class User : IUser
    {
        public abstract Guid Id { get; set; }
        public abstract string Name { get; set; }
        public abstract string phone { get; set; }
        public abstract string state { get; set; }
        public abstract string district { get; set; }
        public abstract string PIN { get; set; }
        public abstract double geolat { get; set; }
        public abstract double geolong { get; set; }
        public abstract int v { get; set; }
        public abstract int h { get; set; }
        public abstract string AzureImageURL { get; set; }
        public abstract string ServiceUrl { get; set; }
        public abstract string ChannelId { get; set; }
        public abstract string PartyChannelAccountId { get; set; }
        public abstract string PartyChannelAccountName { get; set; }
        public abstract string BotChannelAccountId { get; set; }
        public abstract string BotChannelAccountName { get; set; }
        public abstract string ConversationAccountId { get; set; }
        public abstract string ConversationAccountName { get; set; }

        /// <summary>
        /// Creates a new User with a new Guid already set.
        /// </summary>
        public User()
        {
            Id = Guid.NewGuid();
        }

    }

Definition of IUser:

public interface IUser : IParty
    {
        Guid Id { get; set; }
        string Name { get; set; }
        string state { get; set; }
        string district { get; set; }
        string PIN { get; set; }
        double geolat { get; set; }
        double geolong { get; set; }
        int v { get; set; }
        int h { get; set; }
        string AzureImageURL { get; set; }

    }

Thanks!

1

There are 1 answers

6
MakePeaceGreatAgain On BEST ANSWER

How would you expect a farmer to become an expert? Even if they implement the same common interface, they are completely different. In particular your classes have some uncommon members:

class Expert
{
    public string languages { get; set; }
}
class Farmer
{
    public string AadhaarNum { get; set; }
}

How should the languages be transformed into a AadhaarNum?. That´s impossible and thus there´s no automatic cast. You´d have to create some own cast or transformation, e.g.

var farmer = new Farmer(expert);

This assumes you have a copy-constructor within both classes:

class Farmer
{
    public Farmer(IUser user)
    {
        this.Name = user.Name;
        ...
        // also set the members that exist only on farmer
    }
}

and for the Expert-class accordingly.

Alternativly you can introduce some factory-methods within your abstract base-class. However this has the disadvantage that your base-class needs to know all of it´s possible sub-types. Introducing new sub-classes thus leads to changing the base-class.