Gigi
Gigi

Reputation: 338

How to instantiate a derived class from a base class instance?

I want to use an instance of a base class to an initialized/instantiated derived class like below:

class B
{
  //...
}

class  D : B
{
   bool DMember; // dummy example
   //...
}

B b =  new B();

// fill b members with values, do stuff
D d = (D)b.Clone(); // wrong, of course

d.DMember = true;

We already have the mechanism to clone a B class by using MemberwiseClone.

I actually try to enrich a class instance at some point of the execution, without modifying much the existing code.

Upvotes: 1

Views: 2740

Answers (1)

Dai
Dai

Reputation: 155708

Yes, but it's tedious in C# 1.0 through C# 7.3...

C# does not support implicit copy-constructors the way that C++ does. You need to manually define a copy-constructor yourself.

...yes, this is tedious and annoying.

Something like this:

class PersonCommonData
{
    public PersonCommonData(
        Int32 personId,
        String firstName,
        String lastName,
        Date dob
    )
    {
        this.PersonId = personId;
        this.FirstName = firstName;
        this.LastName = lastName;
        this.DoB = dob;
    }

    // Copy-constructor:
    protected PersonCommonData( PersonCommonData copy )
        : this(
            personId: copy.PersonId,
            firstName: copy.FirstName,
            lastName: copy.LastName,
            dob: copy.DoB
        )
    {
    }

    public Int32  PersonId  { get; }
    public String FirstName { get; }
    public String LastName  { get; }
    public Date   DoB       { get; }
}

class EmployeePerson : PersonCommonData
{
    public EmployeePerson( PersonCommonData copy, Int32 employeeId )
        : base( copy )
    {
        this.EmployeeId = employeeId;
    }

    public Int32 EmployeeId { get; }
}

usage:

PersonCommonData common = GetCommonData( personId: 123 );

EmployeePerson e = new EmployeePerson( common, employeeId: 456 );

Better idea for C# 7: Composition

If you instead compose the base common-data object in the derived class then that simplifies things:

(Note that this is probably only a good idea if your classes are immutable, because having mutable data, or even possibly mutable data in an object-graph is rarely a good idea - the good news is that C# 9.0's record types reduce a lot of the tedium, though they don't eliminate it).

class PersonCommonData
{
    public PersonCommonData(
        Int32 personId,
        String firstName,
        String lastName,
        Date dob
    )
    {
        this.PersonId = personId;
        this.FirstName = firstName;
        this.LastName = lastName;
        this.DoB = dob;
    }

    public Int32  PersonId  { get; }
    public String FirstName { get; }
    public String LastName  { get; }
    public Date   DoB       { get; }
}

class EmployeePerson
{
    private readonly PersonCommonData common;

    public EmployeePerson( PersonCommonData common, Int32 employeeId )
    {
        this.common = common ?? throw new ArgumentNullException(nameof(common));
        this.EmployeeId = employeeId;
    }

    public Int32 EmployeeId { get; }

    // Tedium: need to repeat every member as a property-forwarder:
    public Int32  PersonId  => this.common.PersonId;
    public String FirstName => this.common.FirstName;
    public String LastName  => this.common.LastName;
    public Date   DoB       => this.common.DoB;
}

Even better in C# 9.0 with record types and default-interface-implementation...

If you're only using immutable data, then with C# 9.0 you can use record types which eliminate a lot of the tedium. I feel they work best with composition though:

public record PersonCommonData(
    Int32  PersonId,
    String FirstName,
    String LastName,
    Date   DoB
);

public interface IHasPersonCommonData
{
    PersonCommonData Common { get; }

    Int32  PersonId  => this.Common.PersonId;
    String FirstName => this.Common.FirstName;
    String LastName  => this.Common.LastName;
    Date   DoB       => this.Common.DoB;
}

public interface IEmployeePerson : IHasPersonCommonData
{
    Int32 EmployeeId { get; }
}

public record EmployeePerson(
    PersonCommonData Common,
    Int32 EmployeeId
) : IEmployeePerson;

public interface IStudentPerson : IHasPersonCommonData
{
    Int32 StudentId { get; }
}

public partial StudentPerson( PersonCommonData Common, Int32 StudentId ) : IStudentPerson;

But there are limitations - C# still doesn't support true mixins (interface default implementations only work when the type is accessed through the interface, members aren't inherited boo).

Upvotes: 2

Related Questions