Reputation: 338
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
Reputation: 155708
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 );
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;
}
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