1

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.

Gigi
  • 158
  • 11
  • 1
    Fun fact: there are very few legitimate reasons to use `MemberwiseClone()` - and this is not one of them. – Dai Jun 16 '21 at 09:52
  • Please share your **actual** use-case - your contrived example with `class B` and `class D` is not helpful because the _best_ solution to this problem is very context-sensitive. – Dai Jun 16 '21 at 09:54
  • 1
    Does this answer your question? [Convert base class to derived class](https://stackoverflow.com/questions/12565736/) and [Copying the contents of a base class from a derived class](https://stackoverflow.com/questions/14613919/) and [Why downcasting is not allowed in C#?](https://stackoverflow.com/questions/58728416/) and [How to create a new object instance of class being the same type of another instance by only knowing it as base class type?](https://stackoverflow.com/questions/65913153/) –  Jun 16 '21 at 09:54
  • @OlivierRogier I don't think the OP is asking how to convert an instance, just how to construct a new separate instance, just using common data. – Dai Jun 16 '21 at 09:55
  • Consider using composition instead of inheritance (so `class D` constains an instance of `B`), btw. – Dai Jun 16 '21 at 09:57
  • @Dai Suggested links explain the problem as well as expose solutions. –  Jun 16 '21 at 09:59
  • @OlivierRogier The links point to questions where people want to convert an existing instance - or where a derived-type is unknown - I don't see a similarity with the OP's problem (well, except this one: https://stackoverflow.com/questions/14613919/copying-the-contents-of-a-base-class-from-a-derived-class ) – Dai Jun 16 '21 at 10:01
  • @Dai I don't think duplicates can be viewed in such a reductive way, and there are several solutions there, including auto-mapping and conversion constructor. –  Jun 16 '21 at 10:05

1 Answers1

2

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).

Dai
  • 141,631
  • 28
  • 261
  • 374
  • 1
    The problem is that whenever somebody will modify the base class, the copy constructor might have to be updated as well. On the other hand, the composition won't easily fit the existing design (B class itself is built on an interface, etc.) – Gigi Jun 16 '21 at 10:11
  • @Gigi The copy constructor is defined *by* the base-class - so there's no _locality_ problem here: if the base class changes then nothing needs to change to any derived types. – Dai Jun 16 '21 at 10:26
  • @Gigi If `B` implements an interface then `D` can also implement the same interface and forward the implementation to its inner instance of `B` - that's _exactly_ how composition is meant to work. – Dai Jun 16 '21 at 10:27