Creating an instance of a derived class from an instance of its base class #9805
-
|
It seems the only way to do this is to use composition and then forward every member. Is there really no way to do this for example: public class EnhancedStudent : Student
{
public EnhancedStudent(Student _student)
{
base = _student; // wouldn't this be nice...
}
public DateTime CreatedDateTime
{
get
{
return DateTime.Parse(base.CreatedDateTime);
}
}
}The use case here is that instances of The class If The assignment: base = student;would only be permitted within a constructor and the RHS expression must be the same type as the base, that becomes a kind of shorthand for: base.field1 = student.field1;
base.field2 = student.field2;
// etc, etc
base.field123 = student.field123;I refer to fields because that's all we care about and setting all the fields in the base from the fields in the source object is always safe, by definition the values in all the fields (even private) of a Thus |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 11 replies
-
|
You can write a helper method to shallow-copy the fields via reflection. A source generator could write that for you. The compiler itself couldn't emit that code because you wouldn't have access to the |
Beta Was this translation helpful? Give feedback.
-
|
Your example would be easily solved with an extension method. |
Beta Was this translation helpful? Give feedback.
-
|
It is not possible. A valid state of fields of base class may not be valid state for derived class. Example: class Base
{
private int a;
public virtual int A
{
get => a;
set => a = value;
}
}
class Derived : Base
{
public override int A
{
get => base.A;
set
{
if (value < 0) throw new Exception();
base.A = value;
}
}
}You should not be able to create a This is the common case of classes, and PODs are special. The proposed features are more suitable to records. |
Beta Was this translation helpful? Give feedback.
-
|
This all arose because a vendor's OData service is flawed, fails to include a time zone in Edm.DateTimeOffset OData members, that causes MS OData code to (justifiably) throw a format exception. The most reasonable "fix" is to edit the OData metadata file and replace that date type with string. Then regen the code and it works, but we lose the date type and need to convert the string ourselves when we need it. Hence the custom derived classes, created as the need is encountered and letting us access all the original properties but "hide" the date members with members that have the correct date type. |
Beta Was this translation helpful? Give feedback.
-
|
I got Copilot to draft the code: public class B
{
public int BaseInt;
public string BaseString;
}
public class D : B
{
public double DerivedDouble;
public D(B source)
{
FieldCopier<D>.PopulateBase(source, this);
}
}
public static class FieldCopier<TTarget>
where TTarget : class
{
private static readonly List<(Func<object, object> getter, Action<TTarget, object> setter)> _fieldAccessors;
static FieldCopier()
{
_fieldAccessors = new List<(Func<object, object>, Action<TTarget, object>)>();
Type baseType = typeof(TTarget).BaseType!;
var fields = baseType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields)
{
var baseParam = Expression.Parameter(typeof(object), "baseInstance");
var baseCast = Expression.Convert(baseParam, baseType);
var fieldAccess = Expression.Field(baseCast, field);
var getter = Expression.Lambda<Func<object, object>>(
Expression.Convert(fieldAccess, typeof(object)),
baseParam
).Compile();
var targetParam = Expression.Parameter(typeof(TTarget), "target");
var valueParam = Expression.Parameter(typeof(object), "value");
var targetFieldAccess = Expression.Field(Expression.Convert(targetParam, baseType), field);
var assign = Expression.Assign(targetFieldAccess, Expression.Convert(valueParam, field.FieldType));
var setter = Expression.Lambda<Action<TTarget, object>>(assign, targetParam, valueParam).Compile();
_fieldAccessors.Add((getter, setter));
}
}
public static void PopulateBase(object baseInstance, TTarget target)
{
foreach (var (getter, setter) in _fieldAccessors)
{
setter(target, getter(baseInstance));
}
}
}I'll road test this later... |
Beta Was this translation helpful? Give feedback.
-
|
Here's the final code that seems to work fine: public static class FieldCopier
{
private static readonly ConcurrentDictionary<Type, List<(Func<object, object> getter, Action<object, object> setter)>> _cache
= new();
public static void PopulateBase(object baseInstance, object target)
{
var targetType = target.GetType();
var accessors = _cache.GetOrAdd(targetType, type =>
{
var baseType = type.BaseType!;
var list = new List<(Func<object, object>, Action<object, object>)>();
var fields = baseType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields)
{
// Getter
var baseParam = Expression.Parameter(typeof(object), "baseInstance");
var baseCast = Expression.Convert(baseParam, baseType);
var fieldAccess = Expression.Field(baseCast, field);
var getter = Expression.Lambda<Func<object, object>>(Expression.Convert(fieldAccess, typeof(object)), baseParam).Compile();
// Setter
var targetParam = Expression.Parameter(typeof(object), "target");
var valueParam = Expression.Parameter(typeof(object), "value");
var targetCast = Expression.Convert(targetParam, baseType);
var targetField = Expression.Field(targetCast, field);
var assign = Expression.Assign(targetField, Expression.Convert(valueParam, field.FieldType));
var setter = Expression.Lambda<Action<object, object>>(assign, targetParam, valueParam).Compile();
list.Add((getter, setter));
}
return list;
});
foreach (var (getter, setter) in accessors)
{
var value = getter(baseInstance);
setter(target, value);
}
}
}We just do this in the derived class constructor: public ProjectedStudent(Student source)
{
FieldCopier.PopulateBase(source, this);
} |
Beta Was this translation helpful? Give feedback.
You can write a helper method to shallow-copy the fields via reflection. A source generator could write that for you. The compiler itself couldn't emit that code because you wouldn't have access to the
privatefields ofStudentfromEnhancedStudent, and ifStudentis defined in a different assembly the schema could be different at runtime.